diff --git a/.claude/skills/angular-modernization/SKILL.md b/.claude/skills/angular-modernization/SKILL.md new file mode 100644 index 00000000000..187f715bde2 --- /dev/null +++ b/.claude/skills/angular-modernization/SKILL.md @@ -0,0 +1,156 @@ +--- +name: angular-modernization +description: Modernizes Angular code such as components and directives to follow best practices using both automatic CLI migrations and Bitwarden-specific patterns. YOU must use this skill when someone requests modernizing Angular code. DO NOT invoke for general Angular discussions unrelated to modernization. +allowed-tools: Read, Write, Glob, Bash(npx ng generate:*) +--- + +# Angular Modernization + +Transforms legacy Angular components to modern architecture using a two-step approach: + +1. **Automated migrations** - Angular CLI schematics for standalone, control flow, and signals +2. **Bitwarden patterns** - ADR compliance, OnPush change detection, proper visibility, thin components + +## Workflow + +### Step 1: Run Angular CLI Migrations + +**⚠️ CRITICAL: ALWAYS use Angular CLI migrations when available. DO NOT manually migrate features that have CLI schematics.** + +Angular provides automated schematics that handle edge cases, update tests, and ensure correctness. Manual migration should ONLY be used for patterns not covered by CLI tools. + +**IMPORTANT:** + +- Always run the commands using `npx ng`. +- All the commands must be run on directories and NOT files. Use the `--path` option to target directories. +- Run migrations in order (some depend on others) + +#### 1. Standalone Components + +```bash +npx ng generate @angular/core:standalone --path= --mode=convert-to-standalone +``` + +NgModule-based → standalone architecture + +#### 2. Control Flow Syntax + +```bash +npx ng generate @angular/core:control-flow +``` + +`*ngIf`, `*ngFor`, `*ngSwitch` → `@if`, `@for`, `@switch` + +#### 3. Signal Inputs + +```bash +npx ng generate @angular/core:signal-input-migration +``` + +`@Input()` → signal inputs + +#### 4. Signal Outputs + +```bash +npx ng generate @angular/core:output-migration +``` + +`@Output()` → signal outputs + +#### 5. Signal Queries + +```bash +npx ng generate @angular/core:signal-queries-migration +``` + +`@ViewChild`, `@ContentChild`, etc. → signal queries + +#### 6. inject() Function + +```bash +npx ng generate @angular/core:inject-migration +``` + +Constructor injection → `inject()` function + +#### 7. Self-Closing Tag + +```bash +npx ng generate @angular/core:self-closing-tag +``` + +Updates templates to self-closing syntax + +#### 8. Unused Imports + +```bash +npx ng generate @angular/core:unused-imports +``` + +Removes unused imports + +### Step 2: Apply Bitwarden Patterns + +See [migration-patterns.md](migration-patterns.md) for detailed examples. + +1. Add OnPush change detection +2. Apply visibility modifiers (`protected` for template access, `private` for internal) +3. Convert local component state to signals +4. Keep service observables (don't convert to signals) +5. Extract business logic to services +6. Organize class members correctly +7. Update tests for standalone + +### Step 3: Validate + +- Fix linting and formatting using `npm run lint:fix` +- Run tests using `npm run test` + +If any errors occur, fix them accordingly. + +## Key Decisions + +### Signals vs Observables + +- **Signals** - Component-local state only (ADR-0027) +- **Observables** - Service state and cross-component communication (ADR-0003) +- Use `toSignal()` to bridge observables into signal-based components + +### Visibility + +- `protected` - Template-accessible members +- `private` - Internal implementation + +### Other Rules + +- Always add OnPush change detection +- No TypeScript enums (use const objects with type aliases per ADR-0025) +- No code regions (refactor instead) +- Thin components (business logic in services) + +## Validation Checklist + +Before completing migration: + +- [ ] OnPush change detection added +- [ ] Visibility modifiers applied (`protected`/`private`) +- [ ] Signals for component state, observables for service state +- [ ] Class members organized (see [migration-patterns.md](migration-patterns.md#class-member-organization)) +- [ ] Tests updated and passing +- [ ] No new TypeScript enums +- [ ] No code regions + +## References + +### Bitwarden ADRs + +- [ADR-0003: Observable Data Services](https://contributing.bitwarden.com/architecture/adr/observable-data-services) +- [ADR-0025: No TypeScript Enums](https://contributing.bitwarden.com/architecture/adr/no-enums) +- [ADR-0027: Angular Signals](https://contributing.bitwarden.com/architecture/adr/angular-signals) +- [Bitwarden Angular Style Guide](https://contributing.bitwarden.com/contributing/code-style/web/angular) + +### Angular Resources + +- [Angular Style Guide](https://angular.dev/style-guide) +- [Angular Migrations](https://angular.dev/reference/migrations) +- [Angular CLI Schematics](https://angular.dev/tools/cli/schematics) diff --git a/.claude/skills/angular-modernization/migration-patterns.md b/.claude/skills/angular-modernization/migration-patterns.md new file mode 100644 index 00000000000..284f90a410f --- /dev/null +++ b/.claude/skills/angular-modernization/migration-patterns.md @@ -0,0 +1,253 @@ +# Angular Migration Patterns Reference + +## Table of Contents + +- [Component Architecture](#component-architecture) +- [Dependency Injection](#dependency-injection) +- [Reactivity Patterns](#reactivity-patterns) +- [Template Syntax](#template-syntax) +- [Type Safety](#type-safety) + +## Component Architecture + +### Standalone Components + +Angular defaults to standalone components. Components should omit `standalone: true`, and any component specifying `standalone: false` SHALL be migrated to standalone. + +```typescript +@Component({ + selector: "app-user-profile", + imports: [CommonModule, ReactiveFormsModule, AsyncPipe], + templateUrl: "./user-profile.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UserProfileComponent {} +``` + +### Class Member Organization + +```typescript +@Component({...}) +export class MyComponent { + // 1. Inputs (public) + @Input() data: string; + + // 2. Outputs (public) + @Output() valueChange = new EventEmitter(); + + // 3. ViewChild/ContentChild + @ViewChild('template') template: TemplateRef; + + // 4. Injected dependencies (private/protected) + private userService = inject(UserService); + protected dialogService = inject(DialogService); + + // 5. Public properties + public formGroup: FormGroup; + + // 6. Protected properties (template-accessible) + protected isLoading = signal(false); + protected items$ = this.itemService.items$; + + // 7. Private properties + private cache = new Map(); + + // 8. Lifecycle hooks + ngOnInit() {} + + // 9. Public methods + public save() {} + + // 10. Protected methods (template-accessible) + protected handleClick() {} + + // 11. Private methods + private processData() {} +} +``` + +## Dependency Injection + +### Modern inject() Function + +**Before:** + +```typescript +constructor( + private userService: UserService, + private route: ActivatedRoute +) {} +``` + +**After:** + +```typescript +private userService = inject(UserService); +private route = inject(ActivatedRoute); +``` + +## Reactivity Patterns + +### Signals for Component State (ADR-0027) + +```typescript +// Local state +protected selectedFolder = signal(null); +protected isLoading = signal(false); + +// Derived state +protected hasSelection = computed(() => this.selectedFolder() !== null); +``` + +### Prefer computed() Over effect() + +Use `computed()` for derived values. Use `effect()` only for side effects (logging, analytics, DOM sync). + +**❌ Bad:** + +```typescript +constructor() { + effect(() => { + const id = this.selectedId(); + this.selectedItem.set(this.items().find(i => i.id === id) ?? null); + }); +} +``` + +**✅ Good:** + +```typescript +selectedItem = computed(() => this.items().find((i) => i.id === this.selectedId()) ?? null); +``` + +### Observables for Service Communication (ADR-0003) + +```typescript +// In component +protected folders$ = this.folderService.folders$; + +// Template +//
+ +// For explicit subscriptions +constructor() { + this.userService.user$ + .pipe(takeUntilDestroyed()) + .subscribe(user => this.handleUser(user)); +} +``` + +### Bridging Observables to Signals + +Use `toSignal()` to convert service observables to signals in components. Keep service state as observables (ADR-0003). + +**Before:** + +```typescript +private destroy$ = new Subject(); +users: User[] = []; + +ngOnInit() { + this.userService.users$.pipe(takeUntil(this.destroy$)) + .subscribe(users => this.users = users); +} + +ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); +} +``` + +**After:** + +```typescript +protected users = toSignal(this.userService.users$, { initialValue: [] }); +``` + +## Template Syntax + +### New Control Flow + +**Before:** + +```html +
+

{{ item.name }}

+
+Loading... +``` + +**After:** + +```html +@if (user$ | async; as user) { @for (item of user.items; track item.id) { +

{{ item.name }}

+} } @else { +

Loading...

+} +``` + +### Prefer Class/Style Bindings Over ngClass/ngStyle + +Use `[class.*]` and `[style.*]` bindings instead of `ngClass`/`ngStyle`. + +**❌ Bad:** + +```html +
+
+
+``` + +**✅ Good:** + +```html +
+
+
+``` + +## Type Safety + +### No TypeScript Enums (ADR-0025) + +**Before:** + +```typescript +enum CipherType { + Login = 1, + SecureNote = 2, +} +``` + +**After:** + +```typescript +export const CipherType = Object.freeze({ + Login: 1, + SecureNote: 2, +} as const); +export type CipherType = (typeof CipherType)[keyof typeof CipherType]; +``` + +### Reactive Forms + +```typescript +protected formGroup = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + email: new FormControl('', { validators: [Validators.email] }), +}); +``` + +## Anti-Patterns to Avoid + +- ❌ Manually refactoring when CLI migrations exist +- ❌ Manual subscriptions without `takeUntilDestroyed()` +- ❌ TypeScript enums (use const objects per ADR-0025) +- ❌ Mixing constructor injection with `inject()` +- ❌ Signals in services shared with non-Angular code (ADR-0003) +- ❌ Business logic in components +- ❌ Code regions +- ❌ Converting service observables to signals (ADR-0003) +- ❌ Using `effect()` for derived state (use `computed()`) +- ❌ Using `ngClass`/`ngStyle` (use `[class.*]`/`[style.*]`) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ed7fcac96e6..89fff27b217 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,7 +8,9 @@ apps/desktop/desktop_native @bitwarden/team-platform-dev apps/desktop/desktop_native/objc/src/native/autofill @bitwarden/team-autofill-desktop-dev apps/desktop/desktop_native/core/src/autofill @bitwarden/team-autofill-desktop-dev +apps/desktop/desktop_native/macos_provider @bitwarden/team-autofill-desktop-dev apps/desktop/desktop_native/core/src/secure_memory @bitwarden/team-key-management-dev + ## No ownership for Cargo.lock and Cargo.toml to allow dependency updates apps/desktop/desktop_native/Cargo.lock apps/desktop/desktop_native/Cargo.toml diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 6e142edf8a7..96e16776545 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -119,7 +119,7 @@ "rimraf", "ssh-encoding", "ssh-key", - "@storybook/web-components-webpack5", + "@storybook/web-components-vite", "tabbable", "tldts", "wait-on", @@ -197,7 +197,6 @@ "nx", "oo7", "oslog", - "parse5", "pin-project", "pkg", "postcss", @@ -215,6 +214,8 @@ "simplelog", "style-loader", "sysinfo", + "tokio", + "tokio-util", "tracing", "tracing-subscriber", "ts-node", @@ -261,6 +262,11 @@ groupName: "windows", matchPackageNames: ["windows", "windows-core", "windows-future", "windows-registry"], }, + { + // We need to group all tokio-related packages together to avoid build errors caused by version incompatibilities. + groupName: "tokio", + matchPackageNames: ["bytes", "tokio", "tokio-util"], + }, { // We group all webpack build-related minor and patch updates together to reduce PR noise. // We include patch updates here because we want PRs for webpack patch updates and it's in this group. @@ -311,26 +317,24 @@ "@compodoc/compodoc", "@ng-select/ng-select", "@storybook/addon-a11y", - "@storybook/addon-actions", "@storybook/addon-designs", - "@storybook/addon-essentials", - "@storybook/addon-interactions", + "@storybook/addon-docs", "@storybook/addon-links", "@storybook/test-runner", "@storybook/addon-themes", "@storybook/angular", - "@storybook/manager-api", - "@storybook/theming", "@types/react", "autoprefixer", "bootstrap", "chromatic", "ngx-toastr", + "path-browserify", "react", "react-dom", "remark-gfm", "storybook", "tailwindcss", + "vite-tsconfig-paths", "zone.js", "@tailwindcss/container-queries", ], diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 87ea808a97b..efb94e44c7a 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -175,9 +175,23 @@ jobs: - name: Check out repo uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false + - name: Free disk space for build + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/share/swift + sudo rm -rf /usr/local/.ghcup + sudo rm -rf /usr/share/miniconda + sudo rm -rf /usr/share/az_* + sudo rm -rf /usr/local/julia* + sudo rm -rf /usr/lib/mono + sudo rm -rf /usr/lib/heroku + sudo rm -rf /usr/local/aws-cli + sudo rm -rf /usr/local/aws-sam-cli + - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: @@ -186,7 +200,7 @@ jobs: node-version: ${{ env._NODE_VERSION }} - name: Cache Rust dependencies - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: | apps/desktop/desktop_native -> target @@ -195,7 +209,7 @@ jobs: - name: Set up environment run: | sudo apt-get update - sudo apt-get -y install pkg-config libxss-dev rpm musl-dev musl-tools flatpak flatpak-builder + sudo apt-get -y install pkg-config libxss-dev rpm flatpak flatpak-builder - name: Set up Snap run: sudo snap install snapcraft --classic @@ -248,14 +262,21 @@ jobs: env: PKG_CONFIG_ALLOW_CROSS: true PKG_CONFIG_ALL_STATIC: true - TARGET: musl + # Note: It is important that we use the release build because some compute heavy + # operations such as key derivation for oo7 on linux are too slow in debug mode run: | - rustup target add x86_64-unknown-linux-musl - node build.js --target=x86_64-unknown-linux-musl + node build.js --release - name: Build application run: npm run dist:lin + - name: Upload tar.gz artifact + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: bitwarden_${{ env._PACKAGE_VERSION }}_x64.tar.gz + path: apps/desktop/dist/bitwarden_desktop_x64.tar.gz + if-no-files-found: error + - name: Upload .deb artifact uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: @@ -335,7 +356,7 @@ jobs: node-version: ${{ env._NODE_VERSION }} - name: Cache Rust dependencies - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: | apps/desktop/desktop_native -> target @@ -344,7 +365,7 @@ jobs: - name: Set up environment run: | sudo apt-get update - sudo apt-get -y install pkg-config libxss-dev rpm musl-dev musl-tools flatpak flatpak-builder squashfs-tools ruby ruby-dev rubygems build-essential + sudo apt-get -y install pkg-config libxss-dev rpm flatpak flatpak-builder squashfs-tools ruby ruby-dev rubygems build-essential sudo gem install --no-document fpm - name: Set up Snap @@ -404,10 +425,10 @@ jobs: env: PKG_CONFIG_ALLOW_CROSS: true PKG_CONFIG_ALL_STATIC: true - TARGET: musl + # Note: It is important that we use the release build because some compute heavy + # operations such as key derivation for oo7 on linux are too slow in debug mode run: | - rustup target add aarch64-unknown-linux-musl - node build.js --target=aarch64-unknown-linux-musl + node build.js --release - name: Check index.d.ts generated if: github.event_name == 'pull_request' && steps.cache.outputs.cache-hit != 'true' @@ -483,7 +504,7 @@ jobs: node-version: ${{ env._NODE_VERSION }} - name: Cache Rust dependencies - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: | apps/desktop/desktop_native -> target @@ -562,7 +583,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + env: + MODE: ${{ github.event_name == 'workflow_call' && '--release' || '' }} + run: node build.js cross-platform "$env:MODE" - name: Build run: npm run build @@ -749,7 +772,7 @@ jobs: node-version: ${{ env._NODE_VERSION }} - name: Cache Rust dependencies - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: | apps/desktop/desktop_native -> target @@ -825,7 +848,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + env: + MODE: ${{ github.event_name == 'workflow_call' && '--release' || '' }} + run: node build.js cross-platform "$env:MODE" - name: Build run: npm run build @@ -994,13 +1019,13 @@ jobs: - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: - python-version: '3.14' + python-version: '3.14.2' - name: Set up Node-gyp - run: python3 -m pip install setuptools + run: python -m pip install setuptools - name: Cache Rust dependencies - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: | apps/desktop/desktop_native -> target @@ -1013,6 +1038,7 @@ jobs: rustup show echo "GitHub ref: $GITHUB_REF" echo "GitHub event: $GITHUB_EVENT" + xcodebuild -showsdks - name: Cache Build id: build-cache @@ -1180,7 +1206,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + env: + MODE: ${{ github.event_name == 'workflow_call' && '--release' || '' }} + run: node build.js cross-platform "$MODE" - name: Build application (dev) run: npm run build @@ -1231,13 +1259,13 @@ jobs: - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: - python-version: '3.14' + python-version: '3.14.2' - name: Set up Node-gyp - run: python3 -m pip install setuptools + run: python -m pip install setuptools - name: Cache Rust dependencies - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: | apps/desktop/desktop_native -> target @@ -1250,6 +1278,7 @@ jobs: rustup show echo "GitHub ref: $GITHUB_REF" echo "GitHub event: $GITHUB_EVENT" + xcodebuild -showsdks - name: Get Build Cache id: build-cache @@ -1401,7 +1430,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + env: + MODE: ${{ github.event_name == 'workflow_call' && '--release' || '' }} + run: node build.js cross-platform "$MODE" - name: Build if: steps.build-cache.outputs.cache-hit != 'true' @@ -1503,13 +1534,13 @@ jobs: - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: - python-version: '3.14' + python-version: '3.14.2' - name: Set up Node-gyp - run: python3 -m pip install setuptools + run: python -m pip install setuptools - name: Cache Rust dependencies - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: | apps/desktop/desktop_native -> target @@ -1522,6 +1553,7 @@ jobs: rustup show echo "GitHub ref: $GITHUB_REF" echo "GitHub event: $GITHUB_EVENT" + xcodebuild -showsdks - name: Get Build Cache id: build-cache @@ -1681,7 +1713,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + env: + MODE: ${{ github.event_name == 'workflow_call' && '--release' || '' }} + run: node build.js cross-platform "$MODE" - name: Build if: steps.build-cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 48d3eca2f4e..f2e6db96b30 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -52,6 +52,7 @@ jobs: ! -path "*/Cargo.lock" \ ! -path "./apps/desktop/macos/*" \ ! -path "*/CLAUDE.md" \ + ! -path "*/SKILL.md" \ > tmp.txt diff <(sort .github/whitelist-capital-letters.txt) <(sort tmp.txt) diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml index be93ee61479..fb1de5a1bc5 100644 --- a/.github/workflows/publish-web.yml +++ b/.github/workflows/publish-web.yml @@ -158,7 +158,7 @@ jobs: run: docker logout bitwarden-lite-build: - name: Trigger Bitwarden Lite build + name: Trigger Bitwarden lite build runs-on: ubuntu-22.04 needs: setup permissions: @@ -171,20 +171,29 @@ jobs: tenant_id: ${{ secrets.AZURE_TENANT_ID }} client_id: ${{ secrets.AZURE_CLIENT_ID }} - - name: Retrieve GitHub PAT secrets - id: retrieve-secret-pat + - name: Get Azure Key Vault secrets + id: get-kv-secrets uses: bitwarden/gh-actions/get-keyvault-secrets@main with: - keyvault: "bitwarden-ci" - secrets: "github-pat-bitwarden-devops-bot-repo-scope" + keyvault: gh-org-bitwarden + secrets: "BW-GHAPP-ID,BW-GHAPP-KEY" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main - - name: Trigger Bitwarden Lite build + - name: Generate GH App token + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: app-token + with: + app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} + private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + owner: ${{ github.repository_owner }} + repositories: self-host + + - name: Trigger Bitwarden lite build uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + github-token: ${{ steps.app-token.outputs.token }} script: | await github.rest.actions.createWorkflowDispatch({ owner: 'bitwarden', @@ -192,6 +201,7 @@ jobs: workflow_id: 'build-bitwarden-lite.yml', ref: 'main', inputs: { - use_latest_core_version: true + use_latest_core_version: true, + web_branch: process.env.GITHUB_REF } }); diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index c6b671ee5ea..2239cb1268f 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -98,6 +98,14 @@ jobs: working-directory: apps/desktop/artifacts run: mv "Bitwarden-${PKG_VERSION}-universal.pkg" "Bitwarden-${PKG_VERSION}-universal.pkg.archive" + - name: Rename .tar.gz to include version + env: + PKG_VERSION: ${{ steps.version.outputs.version }} + working-directory: apps/desktop/artifacts + run: | + mv "bitwarden_desktop_x64.tar.gz" "bitwarden_${PKG_VERSION}_x64.tar.gz" + mv "bitwarden_desktop_arm64.tar.gz" "bitwarden_${PKG_VERSION}_arm64.tar.gz" + - name: Create Release uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0 if: ${{ steps.release_channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} @@ -110,6 +118,7 @@ jobs: apps/desktop/artifacts/bitwarden_${{ env.PKG_VERSION }}_amd64.snap, apps/desktop/artifacts/bitwarden_${{ env.PKG_VERSION }}_arm64.snap, apps/desktop/artifacts/bitwarden_${{ env.PKG_VERSION }}_arm64.tar.gz, + apps/desktop/artifacts/bitwarden_${{ env.PKG_VERSION }}_x64.tar.gz, apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage, apps/desktop/artifacts/Bitwarden-Portable-${{ env.PKG_VERSION }}.exe, apps/desktop/artifacts/Bitwarden-Installer-${{ env.PKG_VERSION }}.exe, diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index faf119cce2b..0a343be878c 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -29,7 +29,7 @@ on: default: false target_ref: default: "main" - description: "Branch/Tag to target for cut" + description: "Branch/Tag to target for cut (ignored if not cutting rc)" required: true type: string version_number_override: @@ -102,11 +102,12 @@ jobs: with: app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + permission-contents: write # for committing and pushing to current branch - name: Check out branch uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: - ref: main + ref: ${{ github.ref }} token: ${{ steps.app-token.outputs.token }} persist-credentials: true @@ -467,6 +468,7 @@ jobs: with: app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + permission-contents: write # for creating and pushing new branch - name: Check out target ref uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 246e0d48c5d..2a27a9b3101 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -15,7 +15,7 @@ jobs: pull-requests: write steps: - name: 'Run stale action' - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: stale-issue-label: 'needs-reply' stale-pr-label: 'needs-changes' diff --git a/.github/workflows/test-browser-interactions.yml b/.github/workflows/test-browser-interactions.yml index dfc0f28b9c6..c8f4c959c52 100644 --- a/.github/workflows/test-browser-interactions.yml +++ b/.github/workflows/test-browser-interactions.yml @@ -75,7 +75,7 @@ jobs: - name: Trigger test-all workflow in browser-interactions-testing if: steps.changed-files.outputs.monitored == 'true' - uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0 + uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1 with: token: ${{ steps.app-token.outputs.token }} repository: "bitwarden/browser-interactions-testing" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f53bfc39d36..faee7220e7b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -148,7 +148,7 @@ jobs: components: llvm-tools - name: Cache cargo registry - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: workspaces: "apps/desktop/desktop_native -> target" diff --git a/.storybook/main.ts b/.storybook/main.ts index d3811bb178d..e1f3561a1b7 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -28,15 +28,13 @@ const config: StorybookConfig = { ], addons: [ getAbsolutePath("@storybook/addon-links"), - getAbsolutePath("@storybook/addon-essentials"), 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", + + name: getAbsolutePath("@storybook/addon-docs"), options: { mdxPluginOptions: { mdxCompileOptions: { @@ -60,6 +58,10 @@ const config: StorybookConfig = { webpackFinal: async (config, { configType }) => { if (config.resolve) { config.resolve.plugins = [new TsconfigPathsPlugin()] as any; + config.resolve.fallback = { + ...config.resolve.fallback, + path: require.resolve("path-browserify"), + }; } return config; }, diff --git a/.storybook/manager.js b/.storybook/manager.js index e0ec04fd375..7ba923a0b2d 100644 --- a/.storybook/manager.js +++ b/.storybook/manager.js @@ -1,5 +1,5 @@ -import { addons } from "@storybook/manager-api"; -import { create } from "@storybook/theming/create"; +import { addons } from "storybook/manager-api"; +import { create } from "storybook/theming"; const lightTheme = create({ base: "light", diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 0b14f9d7444..266cf79d8b1 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -28,7 +28,7 @@ const preview: Preview = { ], parameters: { a11y: { - element: "#storybook-root", + context: "#storybook-root", }, controls: { matchers: { @@ -49,7 +49,7 @@ const preview: Preview = { }, }, backgrounds: { - disable: true, + disabled: true, }, }, tags: ["autodocs"], diff --git a/angular.json b/angular.json index 87ee7dc57b2..e1cc2aad82a 100644 --- a/angular.json +++ b/angular.json @@ -220,5 +220,31 @@ } } } + }, + "schematics": { + "@schematics/angular:component": { + "type": "component" + }, + "@schematics/angular:directive": { + "type": "directive" + }, + "@schematics/angular:service": { + "type": "service" + }, + "@schematics/angular:guard": { + "typeSeparator": "." + }, + "@schematics/angular:interceptor": { + "typeSeparator": "." + }, + "@schematics/angular:module": { + "typeSeparator": "." + }, + "@schematics/angular:pipe": { + "typeSeparator": "." + }, + "@schematics/angular:resolver": { + "typeSeparator": "." + } } } diff --git a/apps/browser/package.json b/apps/browser/package.json index a6a88b53db0..cf2be624a22 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.11.1", + "version": "2025.12.0", "scripts": { "build": "npm run build:chrome", "build:bit": "npm run build:bit:chrome", diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 505a8404233..452cbafe6d5 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "تعديل" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "معرفة المزيد" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "مفتاح المصادقة (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 جيغابايت وحدة تخزين مشفرة لمرفقات الملفات." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "الوصول الطارئ." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "سنة الإنتهاء" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "تاريخ الانتهاء" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 086b66380b5..c4197f831d3 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Arxivlənmiş elementlər ümumi axtarış nəticələrindən və avto-doldurma təkliflərindən xaric ediləcək. Bu elementi arxivləmək istədiyinizə əminsiniz?" }, + "upgradeToUseArchive": { + "message": "Arxivi istifadə etmək üçün premium üzvlük tələb olunur." + }, "edit": { "message": "Düzəliş et" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Hamısına bax" }, + "showAll": { + "message": "Hamısını göstər" + }, "viewLess": { "message": "Daha azına bax" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Daha ətraflı" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Kimlik doğrulayıcı açarı (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş anbar sahəsi" }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Fövqəladə hal erişimi." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Son istifadə ili" }, + "monthly": { + "message": "ay" + }, "expiration": { "message": "Bitmə vaxtı" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Premium ilə şikayət göndərmə, fövqəladə hal erişimi və daha çox təhlükəsizlik özəlliyinin kilidini açın." + }, "freeOrgsCannotUseAttachments": { "message": "Ödənişsiz təşkilatlar qoşmaları istifadə edə bilməz" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "\"Premium\"a yüksəlt" }, - "upgradeCompleteSecurity": { - "message": "Tam təhlükəsizlik üçün yüksəldin" + "unlockAdvancedSecurity": { + "message": "Qabaqcıl təhlükəsizlik özəlliklərinin kilidini aç" }, - "premiumGivesMoreTools": { - "message": "Premium, güvəndə qalmağınız, səmərəli çalışmağınız və nəzarətə sahib olmağınız üçün daha çox alət verir." + "unlockAdvancedSecurityDesc": { + "message": "Premium abunəlik, güvəndə qalmağınız və nəzarəti əlinizdə saxlamağınız üçün sizə daha çox alət verir" }, "explorePremium": { "message": "Premium-u kəşf et" diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 16a6d739962..806aedfab27 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Рэдагаваць" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Даведацца больш" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Ключ аўтэнтыфікацыі (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 ГБ зашыфраванага сховішча для далучаных файлаў." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Экстранны доступ." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Год завяршэння" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Тэрмін дзеяння" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 3dba65d6aa7..76b20f8bd2e 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Архивираните елементи са изключени от общите резултати при търсене и от предложенията за автоматично попълване. Наистина ли искате да архивирате този елемент?" }, + "upgradeToUseArchive": { + "message": "За да се възползвате от архивирането, трябва да ползвате платен абонамент." + }, "edit": { "message": "Редактиране" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Показване на всички" }, + "showAll": { + "message": "Показване на всички" + }, "viewLess": { "message": "Преглед на по-малко" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Научете повече" }, + "migrationsFailed": { + "message": "Възникна грешка при обновяването на настройките за шифроване." + }, + "updateEncryptionSettingsTitle": { + "message": "Обновете настройките си за шифроване" + }, + "updateEncryptionSettingsDesc": { + "message": "Новите препоръчани настройки за шифроване ще подобрят сигурността на акаунта Ви. Въведете главната си парола, за да ги обновите сега." + }, + "confirmIdentityToContinue": { + "message": "Потвърдете самоличността си, за да продължите" + }, + "enterYourMasterPassword": { + "message": "Въведете главната си парола" + }, + "updateSettings": { + "message": "Обновяване на настройките" + }, + "later": { + "message": "По-късно" + }, "authenticatorKeyTotp": { "message": "Удостоверителен ключ (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB пространство за файлове, които се шифрират." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ пространство за файлове, които се шифрират.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Авариен достъп" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Година на изтичане" }, + "monthly": { + "message": "месец" + }, "expiration": { "message": "Изтичане" }, @@ -2437,7 +2476,7 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "Тази страница пречи на работата на Битуорден. Вмъкнатото меню на Битуорден е временно изключено, като мярка за сигурност." }, "setMasterPassword": { "message": "Задаване на главна парола" @@ -4902,6 +4941,9 @@ "premium": { "message": "Премиум" }, + "unlockFeaturesWithPremium": { + "message": "Отключете докладите, аварийния достъп и още функционалности свързани със сигурността, с платения план." + }, "freeOrgsCannotUseAttachments": { "message": "Безплатните организации не могат да използват прикачени файлове" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Надградете до Платения план" }, - "upgradeCompleteSecurity": { - "message": "Надградете, за да се възползвате от пълна защита" + "unlockAdvancedSecurity": { + "message": "Отключване на разширените функционалности по сигурността" }, - "premiumGivesMoreTools": { - "message": "Платеният план предоставя повече инструменти за защита, ефективна работа и контрол." + "unlockAdvancedSecurityDesc": { + "message": "Платеният абонамент предоставя повече инструменти за защита и управление" }, "explorePremium": { "message": "Разгледайте платения план" diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index d2519cb13e3..4845282e825 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "সম্পাদনা" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "আরও জানুন" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "প্রমাণীকরণকারী কী (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "ফাইল সংযুক্তির জন্য ১ জিবি এনক্রিপ্টেড স্থান।" }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "মেয়াদোত্তীর্ণ বছর" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "মেয়াদোত্তীর্ণতা" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 917180579f2..cff1dd150cb 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index a9ebdf139d7..ff0e2de51af 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edita" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Més informació" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Clau d'autenticació (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB d'emmagatzematge xifrat per als fitxers adjunts." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Accés d’emergència." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Any de venciment" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Caducitat" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index ca5d4b09f28..fa8dc426c05 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archivované položky jsou vyloučeny z obecných výsledků vyhledávání a z návrhů automatického vyplňování. Jste si jisti, že chcete tuto položku archivovat?" }, + "upgradeToUseArchive": { + "message": "Pro použití funkce Archiv je potřebné prémiové členství." + }, "edit": { "message": "Upravit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Zobrazit vše" }, + "showAll": { + "message": "Zobrazit vše" + }, "viewLess": { "message": "Zobrazit méně" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Dozvědět se více" }, + "migrationsFailed": { + "message": "Došlo k chybě při aktualizaci nastavení šifrování." + }, + "updateEncryptionSettingsTitle": { + "message": "Aktualizovat nastavení šifrování" + }, + "updateEncryptionSettingsDesc": { + "message": "Nové doporučené nastavení šifrování zlepší bezpečnost Vašeho účtu. Pokud chcete aktualizovat nyní, zadejte hlavní heslo." + }, + "confirmIdentityToContinue": { + "message": "Pro pokračování potvrďte svou identitu" + }, + "enterYourMasterPassword": { + "message": "Zadejte své hlavní heslo" + }, + "updateSettings": { + "message": "Aktualizovat nastavení" + }, + "later": { + "message": "Později" + }, "authenticatorKeyTotp": { "message": "Autentizační klíč (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB šifrovaného úložiště pro přílohy." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ šifrovaného úložiště pro přílohy.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Nouzový přístup" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Rok expirace" }, + "monthly": { + "message": "měsíčně" + }, "expiration": { "message": "Expirace" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Odemkněte hlášení, nouzový přístup a další bezpečnostní funkce s předplatným Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Volné organizace nemohou používat přílohy" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Aktualizovat na Premium" }, - "upgradeCompleteSecurity": { - "message": "Aktualizujte pro úplné zabezpečení" + "unlockAdvancedSecurity": { + "message": "Odemknout pokročilé bezpečnostní funkce" }, - "premiumGivesMoreTools": { - "message": "Verze Premium Vám poskytne více nástrojů k zabezpečení, efektivní práci a udržení kontroly." + "unlockAdvancedSecurityDesc": { + "message": "Prémiové předplatné Vám dává více nástrojů k bezpečí a kontrole" }, "explorePremium": { "message": "Objevit Premium" diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 1f83ff72f62..45f91f5ccc7 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Golygu" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Dysgu mwy" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "Storfa 1GB wedi'i hamgryptio ar gyfer atodiadau ffeiliau." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Blwyddyn dod i ben" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Dod i ben" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 96aa81ce876..50d79ff1f9d 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Redigér" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Lær mere" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Autentificeringsnøgle (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB krypteret lager til vedhæftede filer." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Nødadgang" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Udløbsår" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Udløb" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Gratis organisationer kan ikke bruge vedhæftninger" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index b32c9a68c06..969f8c63de3 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -562,7 +562,7 @@ "description": "Verb" }, "unArchive": { - "message": "Nicht mehr archivieren" + "message": "Wiederherstellen" }, "itemsInArchive": { "message": "Einträge im Archiv" @@ -574,10 +574,10 @@ "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Vorschlägen zum automatischen Ausfüllen ausgeschlossen." }, "itemWasSentToArchive": { - "message": "Eintrag wurde ins Archiv verschoben" + "message": "Eintrag wurde archiviert" }, "itemUnarchived": { - "message": "Eintrag wird nicht mehr archiviert" + "message": "Eintrag wurde wiederhergestellt" }, "archiveItem": { "message": "Eintrag archivieren" @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archivierte Einträge werden von allgemeinen Suchergebnissen sowie Vorschlägen zum automatischen Ausfüllen ausgeschlossen. Bist du sicher, dass du diesen Eintrag archivieren möchtest?" }, + "upgradeToUseArchive": { + "message": "Für die Nutzung des Archivs ist eine Premium-Mitgliedschaft erforderlich." + }, "edit": { "message": "Bearbeiten" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Alles anzeigen" }, + "showAll": { + "message": "Alles anzeigen" + }, "viewLess": { "message": "Weniger anzeigen" }, @@ -1044,7 +1050,7 @@ "message": "Eintrag gespeichert" }, "savedWebsite": { - "message": "Website gespeichert" + "message": "Gespeicherte Website" }, "savedWebsites": { "message": "Gespeicherte Websites ($COUNT$)", @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Erfahre mehr" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authentifizierungsschlüssel (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB verschlüsselter Speicherplatz für Dateianhänge." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ verschlüsselter Speicher für Dateianhänge.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Notfallzugriff." }, @@ -1704,7 +1740,7 @@ "message": "Auto-Ausfüllen bestätigen" }, "confirmAutofillDesc": { - "message": "Diese Website stimmt nicht mit deinen gespeicherten Zugangsdaten überein. Bevor du deine Zugangsdaten eingibst, stelle sicher, dass es sich um eine vertrauenswürdige Website handelt." + "message": "Diese Website stimmt nicht mit deinen gespeicherten Zugangsdaten überein. Stelle sicher, dass dies eine vertrauenswürdige Website ist, bevor du deine Zugangsdaten eingibst." }, "showInlineMenuLabel": { "message": "Vorschläge zum Auto-Ausfüllen in Formularfeldern anzeigen" @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Ablaufjahr" }, + "monthly": { + "message": "Monatlich" + }, "expiration": { "message": "Gültig bis" }, @@ -2437,7 +2476,7 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "Diese Seite beeinträchtigt die Nutzung von Bitwarden. Das Bitwarden Inline-Menü wurde aus Sicherheitsgründen vorübergehend deaktiviert." }, "setMasterPassword": { "message": "Master-Passwort festlegen" @@ -4066,7 +4105,7 @@ "message": "Kein Auto-Ausfüllen möglich" }, "cannotAutofillExactMatch": { - "message": "Die Standard-Übereinstimmungserkennung steht auf \"Exakte Übereinstimmung\". Die aktuelle Website stimmt nicht genau mit den gespeicherten Zugangsdaten für diesen Eintrag überein." + "message": "Die Standard-Übereinstimmungserkennung ist auf „Exakte Übereinstimmung“ eingestellt. Die aktuelle Website stimmt nicht genau mit den gespeicherten Zugangsdaten für diesen Eintrag überein." }, "okay": { "message": "Okay" @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Schalte mit Premium Berichte, Notfallzugriff und weitere Sicherheitsfunktionen frei." + }, "freeOrgsCannotUseAttachments": { "message": "Kostenlose Organisationen können Anhänge nicht verwenden" }, @@ -5653,7 +5695,7 @@ "message": "Phishing-Versuch erkannt" }, "phishingPageSummary": { - "message": "Die Website, die du versuchst zu öffnen, ist eine bekannte böswillige Website und ein Sicherheitsrisiko." + "message": "Die Website, die du öffnen möchtest, ist als böswillige Website bekannt und stellt ein Sicherheitsrisiko dar." }, "phishingPageCloseTabV2": { "message": "Diesen Tab schließen" @@ -5812,14 +5854,14 @@ "upgradeToPremium": { "message": "Upgrade auf Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade für umfassende Sicherheit" + "unlockAdvancedSecurity": { + "message": "Erweiterte Sicherheitsfunktionen freischalten" }, - "premiumGivesMoreTools": { - "message": "Premium gibt dir mehr Werkzeuge, um sicher zu bleiben, effizient zu arbeiten und die Kontrolle zu behalten." + "unlockAdvancedSecurityDesc": { + "message": "Mit einem Premium-Abonnement erhältst du mehr Werkzeuge für mehr Sicherheit und Kontrolle" }, "explorePremium": { - "message": "Premium erkunden" + "message": "Premium entdecken" }, "loadingVault": { "message": "Tresor wird geladen" diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index f4c3c0d53a5..2be53da65ec 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Επεξεργασία" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Μάθετε περισσότερα" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Κλειδί επαλήθευσης (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB κρυπτογραφημένο αποθηκευτικό χώρο για συνημμένα αρχεία." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Πρόσβαση έκτακτης ανάγκης." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Έτος λήξης" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Λήξη" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Οι δωρεάν οργανισμοί δεν μπορούν να χρησιμοποιήσουν συνημμένα" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 748a771cc5c..09ea964823c 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1457,6 +1457,15 @@ "attachmentSaved": { "message": "Attachment saved" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "file": { "message": "File" }, @@ -1466,6 +1475,9 @@ "selectFile": { "message": "Select a file" }, + "itemsTransferred": { + "message": "Items transferred" + }, "maxFileSize": { "message": "Maximum file size is 500 MB." }, @@ -1496,6 +1508,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1894,6 +1915,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -3228,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -5836,8 +5857,8 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" @@ -5867,7 +5888,141 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector":{ + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified":{ + "message": "Organization verified" + }, + "domainVerified":{ + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": { + "message": "Your organization has set the default session timeout to Immediately." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 96c3323faef..63d9214632a 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organisations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index b9f777148e3..9ef5cb2a061 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organisations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 92dbe15fad2..92f6226a3de 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Editar" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Más información" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Clave de autenticación (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB de espacio cifrado en disco para adjuntos." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Acceso de emergencia." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Año de expiración" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiración" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Las organizaciones gratis no pueden usar archivos adjuntos" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index bb029bf7777..bade6b0dff9 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Muuda" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Loe edasi" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Autentimise võti (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB ulatuses krüpteeritud salvestusruum." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Aegumise aasta" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Aegumine" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 06a4f8ea48d..90cfc13f6ef 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Editatu" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Gehiago ikasi" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Autentifikazio-gakoa (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "Eranskinentzako 1GB-eko zifratutako biltegia." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Iraungitze urtea" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Iraungitze data" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 33f4a02277d..7a4c8744429 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "ویرایش" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "بیشتر بدانید" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "کلید احراز هویت (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "۱ گیگابایت فضای ذخیره‌سازی رمزگذاری شده برای پیوست‌های پرونده." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "دسترسی اضطراری." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "سال انقضاء" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "انقضاء" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "پرمیوم" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "سازمان‌های رایگان نمی‌توانند از پرونده‌های پیوست استفاده کنند" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 9953782f504..d7f0f600b9e 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Muokkaa" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Näytä kaikki" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Lue lisää" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Todennusavain (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 Gt salattua tallennustilaa tiedostoliitteille." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Varmuuskäyttö" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Erääntymisvuosi" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Voimassaolo päättyy" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Ilmaiset organisaatiot eivät voi käyttää liitteitä" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 687863550a7..50964716ad0 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "I-edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Matuto nang higit pa" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Susi ng Authenticator (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage para sa mga file attachment." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Taon ng Pag-expire" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Pag-expire" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 87c1a20a38a..6ddf7ea5873 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Les éléments archivés sont exclus des résultats de recherche généraux et des suggestions de remplissage automatique. Êtes-vous sûr de vouloir archiver cet élément ?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Modifier" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Tout afficher" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "Afficher moins" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "En savoir plus" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Clé Authenticator (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 Go de stockage chiffré pour les fichiers joints." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Accès d'urgence." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Année d'expiration" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -2437,7 +2476,7 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "Cette page interfère avec l'expérience Bitwarden. Le menu en ligne de Bitwarden a été temporairement désactivé en tant que mesure de sécurité." }, "setMasterPassword": { "message": "Définir le mot de passe principal" @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Déverrouillez la journalisation, l'accès d'urgence et plus de fonctionnalités de sécurité avec Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Les organisations gratuites ne peuvent pas utiliser de pièces jointes" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Mettre à niveau vers Premium" }, - "upgradeCompleteSecurity": { - "message": "Mettre à niveau pour une sécurité complète" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium vous donne plus d'outils pour rester en sécurité, travailler efficacement et garder le contrôle." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explorer Premium" diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 9b35af1aad4..5695ae16035 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Editar" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Máis información" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Clave de autenticación (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB de almacenamento cifrado para arquivos anexos." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Acceso de emerxencia." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Ano de vencemento" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Vencemento" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Prémium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "As organizacións gratuitas non poden empregar anexos" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 7d1700cbbdc..67ca71338b2 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "פריטים בארכיון מוחרגים מתוצאות חיפוש כללי והצעות למילוי אוטומטי. האם אתה בטוח שברצונך להעביר פריט זה לארכיון?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "ערוך" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "הצג הכל" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "הצג פחות" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "למידע נוסף" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "מפתח מאמת (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 ג'יגה של מקום אחסון עבור קבצים מצורפים." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "גישת חירום." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "שנת תפוגה" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "תוקף" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "פרימיום" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "ארגונים חינמיים לא יכולים להשתמש בצרופות" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "שדרג לפרימיום" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 0af38bf6964..b839e31cd96 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "संपादन करें" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "अधिक जानें" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator Key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB of encrypted file storage." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration Year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "समय सीमा समाप्ति" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 9bb5ca08843..da78924b1eb 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Arhivirane stavke biti će izuzete iz rezultata općih pretraga i preporuka auto-ispune. Sigurno želiš arhivirati?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Uredi" }, @@ -594,8 +597,11 @@ "viewAll": { "message": "Vidi sve" }, + "showAll": { + "message": "Show all" + }, "viewLess": { - "message": "View less" + "message": "Vidi manje" }, "viewLogin": { "message": "Prikaži prijavu" @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Saznaj više" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Ključ autentifikatora (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB šifriranog prostora za pohranu podataka." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Pristup u nuždi." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Godina isteka" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Istek" }, @@ -2437,7 +2476,7 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "Ova stranica ometa Bitwarden iskustvo. Kao sigurnosna mjera, Bitwarden inline izbornik je privremeno onemogućen." }, "setMasterPassword": { "message": "Postavi glavnu lozinku" @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Besplatne organizacije ne mogu koristiti privitke" }, @@ -5812,14 +5854,14 @@ "upgradeToPremium": { "message": " Nadogradi na Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { - "message": "Explore Premium" + "message": "Provjeri Premium" }, "loadingVault": { "message": "Učitavanje trezora" diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 9b6a5d756d5..388a069e05a 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Az archivált elemek ki vannak zárva az általános keresési eredményekből és az automatikus kitöltési javaslatokból. Biztosan archiválni szeretnénk ezt az elemet?" }, + "upgradeToUseArchive": { + "message": "Az Archívum használatához prémium tagság szükséges." + }, "edit": { "message": "Szerkesztés" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Összes megtekintése" }, + "showAll": { + "message": "Összes megjelenítése" + }, "viewLess": { "message": "kevesebb megjelenítése" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Tudjon meg többet" }, + "migrationsFailed": { + "message": "Hiba történt a titkosítási beállítások frissítésekor." + }, + "updateEncryptionSettingsTitle": { + "message": "A titkosítási beállítások frissítése" + }, + "updateEncryptionSettingsDesc": { + "message": "Az új ajánlott titkosítási beállítások javítják a fiók biztonságát. Adjuk meg a mesterjelszót a frissítéshez most." + }, + "confirmIdentityToContinue": { + "message": "A folytatáshoz meg kell erősíteni a személyazonosságot." + }, + "enterYourMasterPassword": { + "message": "Mesterjelszó megadása" + }, + "updateSettings": { + "message": "Beállítások frissítése" + }, + "later": { + "message": "Később" + }, "authenticatorKeyTotp": { "message": "Hitelesítő kulcs (egyszeri időalapú)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB titkosított tárhely a fájlmellékleteknek." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ titkosított tárhely a fájlmellékletekhez.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Sürgősségi hozzáférés" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Lejárati év" }, + "monthly": { + "message": "hónap" + }, "expiration": { "message": "Lejárat" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Prémium" }, + "unlockFeaturesWithPremium": { + "message": "A Prémium segítségével feloldhatjuk a jelentés készítést, a vészhelyzeti hozzáférést és a további biztonsági funkciókat." + }, "freeOrgsCannotUseAttachments": { "message": "Az ingyenes szervezetek nem használhatnak mellékleteket." }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Áttérés Prémium csomagra" }, - "upgradeCompleteSecurity": { - "message": "Áttérés a teljes biztonságért" + "unlockAdvancedSecurity": { + "message": "Fejlett biztonsági funkciók feloldása" }, - "premiumGivesMoreTools": { - "message": "A Premium több eszközt ad a biztonság megőrzéséhez, a hatékony munkavégzéshez és az irányítás megőrzéséhez." + "unlockAdvancedSecurityDesc": { + "message": "A prémium előfizetés több eszközt biztosít a biztonság és az irányítás megőrzéséhez." }, "explorePremium": { "message": "Premium felfedezése" diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 85fdfbf9afe..d18b25c51ed 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Pelajari lebih lanjut" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Kunci Otentikasi (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB penyimpanan berkas yang dienkripsi." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Akses darurat." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Tahun Kedaluwarsa" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Masa Berlaku" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Organisasi gratis tidak dapat menggunakan lampiran" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index a76bb05d15a..9bb75d7f449 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -32,10 +32,10 @@ "message": "Usa il Single Sign-On" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "La tua organizzazione richiede un accesso Single Sign-On (SSO)." }, "welcomeBack": { - "message": "Bentornato/a" + "message": "Bentornato" }, "setAStrongPassword": { "message": "Imposta una password robusta" @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Gli elementi archiviati sono esclusi dai risultati di ricerca e suggerimenti di autoriempimento. Vuoi davvero archiviare questo elemento?" }, + "upgradeToUseArchive": { + "message": "Per utilizzare Archivio è necessario un abbonamento premium." + }, "edit": { "message": "Modifica" }, @@ -592,10 +595,13 @@ "message": "Visualizza" }, "viewAll": { - "message": "View all" + "message": "Mostra tutto" + }, + "showAll": { + "message": "Mostra tutto" }, "viewLess": { - "message": "View less" + "message": "Vedi meno" }, "viewLogin": { "message": "Visualizza login" @@ -800,10 +806,10 @@ "message": "Al blocco del computer" }, "onIdle": { - "message": "On system idle" + "message": "Quando il sistema è inattivo" }, "onSleep": { - "message": "On system sleep" + "message": "Quando il sistema è sospeso" }, "onRestart": { "message": "Al riavvio del browser" @@ -1044,10 +1050,10 @@ "message": "Elemento salvato" }, "savedWebsite": { - "message": "Saved website" + "message": "Sito Web salvato" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Siti Web salvati ( $COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Ulteriori informazioni" }, + "migrationsFailed": { + "message": "Si è verificato un errore durante l'aggiornamento delle impostazioni di cifratura." + }, + "updateEncryptionSettingsTitle": { + "message": "Aggiorna le impostazioni di crittografia" + }, + "updateEncryptionSettingsDesc": { + "message": "Le nuove impostazioni di crittografia consigliate miglioreranno la sicurezza del tuo account. Inserisci la tua password principale per aggiornare." + }, + "confirmIdentityToContinue": { + "message": "Conferma la tua identità per continuare" + }, + "enterYourMasterPassword": { + "message": "Inserisci la tua password principale" + }, + "updateSettings": { + "message": "Aggiorna impostazioni" + }, + "later": { + "message": "Più tardi" + }, "authenticatorKeyTotp": { "message": "Chiave di autenticazione (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB di spazio di archiviazione crittografato per gli allegati." }, + "premiumSignUpStorageV2": { + "message": "Archivio crittografato di $SIZE$ per allegati.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Accesso di emergenza." }, @@ -1645,7 +1681,7 @@ "message": "Devi aggiungere lo URL del server di base o almeno un ambiente personalizzato." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "Gli indirizzi devono usare il protocollo HTTPS." }, "customEnvironment": { "message": "Ambiente personalizzato" @@ -1701,28 +1737,28 @@ "message": "Disattiva il riempimento automatico" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Conferma il riempimento automatico" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Questo sito non corrisponde ai tuoi dati di accesso salvati. Prima di compilare le credenziali di accesso, assicurati che si tratti di un sito affidabile." }, "showInlineMenuLabel": { "message": "Mostra suggerimenti di riempimento automatico nei campi del modulo" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "In che modo Bitwarden ti protegge dai pericoli del phising?" }, "currentWebsite": { - "message": "Current website" + "message": "Sito web corrente" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Compila e aggiungi questo sito" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Compila senza salvare" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Non compilare con il riempimento automatico" }, "showInlineMenuIdentitiesLabel": { "message": "Mostra identità come consigli" @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Anno di scadenza" }, + "monthly": { + "message": "mese" + }, "expiration": { "message": "Scadenza" }, @@ -2437,7 +2476,7 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "Questa pagina sta interferendo con Bitwarden. Il menu in linea di Bitwarden è stato temporaneamente disabilitato come misura di sicurezza." }, "setMasterPassword": { "message": "Imposta password principale" @@ -3289,7 +3328,7 @@ "message": "Errore di decifrazione" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Errore: impossibile accedere ai dati per il riempimento automatico" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden non può decifrare gli elementi elencati di seguito." @@ -4063,13 +4102,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Impossibile usare il riempimento automatico" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "La corrispondenza predefinita è impostata su 'Corrispondenza esatta'. Il sito Web corrente non corrisponde esattamente ai dettagli di accesso salvati per questo elemento." }, "okay": { - "message": "Okay" + "message": "OK" }, "toggleSideNavigation": { "message": "Attiva/Disattiva navigazione laterale" @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Sblocca reportistica, accesso d'emergenza e altre funzionalità di sicurezza con Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Le organizzazioni gratis non possono utilizzare gli allegati" }, @@ -4987,7 +5029,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "Predefinito ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5786,58 +5828,58 @@ "message": "Conferma dominio Key Connector" }, "atRiskLoginsSecured": { - "message": "Great job securing your at-risk logins!" + "message": "Ottimo lavoro, i tuoi dati di accesso sono al sicuro!" }, "upgradeNow": { - "message": "Upgrade now" + "message": "Aggiorna ora" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "App di autenticazione integrata" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "Archiviazione sicura di file" }, "emergencyAccess": { - "message": "Emergency access" + "message": "Accesso di emergenza" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "Monitoraggio delle violazioni" }, "andMoreFeatures": { - "message": "And more!" + "message": "E molto altro!" }, "planDescPremium": { - "message": "Complete online security" + "message": "Sicurezza online completa" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Aggiorna a Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Sblocca funzionalità di sicurezza avanzate" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "Un abbonamento Premium ti offre più strumenti per rimanere sicuro e in controllo" }, "explorePremium": { - "message": "Explore Premium" + "message": "Scopri Premium" }, "loadingVault": { - "message": "Loading vault" + "message": "Caricamento cassaforte" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "Cassaforte caricata" }, "settingDisabledByPolicy": { "message": "Questa impostazione è disabilitata dalle restrizioni della tua organizzazione.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "CAP" }, "cardNumberLabel": { - "message": "Card number" + "message": "Numero di carta" }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Azione al timeout" } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 1294335481c..0b0883beaf3 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "編集" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "さらに詳しく" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "認証キー (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1GB の暗号化されたファイルストレージ" }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "緊急アクセス" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "有効期限年" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "有効期限" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "プレミアム" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "無料の組織は添付ファイルを使用できません" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "プレミアムにアップグレード" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 52e9fbc5229..ebb01f095f3 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "ჩასწორება" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "გაიგეთ მეტი" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "ვადა" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "პრემიუმი" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 7c4dbaf85dc..684a04d9175 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index d2ca68a0108..05ea413b522 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "ಎಡಿಟ್" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "ದೃಢೀಕರಣ ಕೀ (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "ಫೈಲ್ ಲಗತ್ತುಗಳಿಗಾಗಿ 1 ಜಿಬಿ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಸಂಗ್ರಹ." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "ಮುಕ್ತಾಯ ವರ್ಷ" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "ಮುಕ್ತಾಯ" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index c583e173d91..b35fe8283f7 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "보관된 항목은 일반 검색 결과와 자동 완성 제안에서 제외됩니다. 이 항목을 보관하시겠습니까?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "편집" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "더 알아보기" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "인증 키 (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1GB의 암호화된 파일 저장소." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "비상 접근" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "만료 연도" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "만료" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "프리미엄" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "무료 조직에서는 첨부 파일을 사용할 수 없습니다." }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 45ee71f75dd..7fcc2df0330 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Keisti" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Sužinoti daugiau" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Vienkartinio autentifikavimo raktas (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB užšifruotos vietos diske bylų prisegimams." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Galiojimo pabaigos metai" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Galiojimo pabaiga" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "„Premium“" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Nemokamos organizacijos negali naudoti priedų" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index d7e4b5eea9c..a719320fc8c 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Arhivētie vienumi netiek iekļauti vispārējās meklēšanas iznākumos un automātiskās aizpildes ieteikumos. Vai tiešām ahrivēt šo vienumu?" }, + "upgradeToUseArchive": { + "message": "Ir nepieciešama Premium dalība, lai izmantotu arhīvu." + }, "edit": { "message": "Labot" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Apskatīt visu" }, + "showAll": { + "message": "Rādīt visu" + }, "viewLess": { "message": "Skatīt mazāk" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Uzzināt vairāk" }, + "migrationsFailed": { + "message": "Atgadījās kļūda šifrēšanas iestatījumu atjaunināšanā." + }, + "updateEncryptionSettingsTitle": { + "message": "Atjaunini savus šifrēšanas iestatījumus" + }, + "updateEncryptionSettingsDesc": { + "message": "Jaunie ieteicamie šifrēšanas iestatījumi uzlabos Tava konta drošību. Jāievada sava galvenā parole, lai atjauninātu tagad." + }, + "confirmIdentityToContinue": { + "message": "Jāapliecina sava identitāte, lai turpinātu" + }, + "enterYourMasterPassword": { + "message": "Jāievada sava galvenā parole" + }, + "updateSettings": { + "message": "Atjaunināt Iestatījumus" + }, + "later": { + "message": "Vēlāk" + }, "authenticatorKeyTotp": { "message": "Autentificētāja atslēga (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB šifrētas krātuves datņu pielikumiem." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ šifrētas krātuves datņu pielikumiem.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Ārkārtas piekļuve" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Derīguma gads" }, + "monthly": { + "message": "mēnesī" + }, "expiration": { "message": "Derīgums" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Iegūsti piekļuvi atskaitēm, ārkārtas piekļuvei un citām drošības iespējām ar Premium!" + }, "freeOrgsCannotUseAttachments": { "message": "Bezmaksas apvienības nevar izmantot pielikumus" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Uzlabot uz Premium" }, - "upgradeCompleteSecurity": { - "message": "Uzlabo pilnīgas drošības iegūšanai" + "unlockAdvancedSecurity": { + "message": "Atslēdz papildu drošības iespējas" }, - "premiumGivesMoreTools": { - "message": "Premium sniedz vairāk rīku drošībai, darba ražīgumam un pārraudzībai." + "unlockAdvancedSecurityDesc": { + "message": "Premium abonements sniedz vairāk rīku drošības uzturēšanai un pārraudzībai" }, "explorePremium": { "message": "Izpētīt Premium" diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 6c022f0043f..b72d82cd93f 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "തിരുത്തുക" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "കൂടുതലറിവ് നേടുക" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "ഓതന്റിക്കേറ്റർ കീ (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "ഫയൽ അറ്റാച്ചുമെന്റുകൾക്കായി 1 ജിബി എൻക്രിപ്റ്റുചെയ്‌ത സംഭരണം." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "കാലാവതി കഴിയുന്ന വർഷം" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "കാലഹരണപ്പെടൽ" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index d34d1c87971..03c3b4a70ae 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 7c4dbaf85dc..684a04d9175 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 11bd78ed56c..6226d26312f 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Rediger" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Lær mer" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Autentiseringsnøkkel (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB med kryptert fillagring for filvedlegg." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Nødtilgang." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Utløpsår" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Utløp" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 7c4dbaf85dc..684a04d9175 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 8817e04b163..04e6e1bb7f3 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Gearchiveerde items worden uitgesloten van algemene zoekresultaten en automatische invulsuggesties. Weet je zeker dat je dit item wilt archiveren?" }, + "upgradeToUseArchive": { + "message": "Je hebt een Premium-abonnement nodig om te kunnen archiveren." + }, "edit": { "message": "Bewerken" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Alles weergeven" }, + "showAll": { + "message": "Alles weergeven" + }, "viewLess": { "message": "Minder weergeven" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Meer informatie" }, + "migrationsFailed": { + "message": "Er is een fout opgetreden bij het bijwerken van de versleutelingsinstellingen." + }, + "updateEncryptionSettingsTitle": { + "message": "Je versleutelingsinstellingen bijwerken" + }, + "updateEncryptionSettingsDesc": { + "message": "De nieuwe aanbevolen versleutelingsinstellingen verbeteren de beveiliging van je account. Voer je hoofdwachtwoord in om nu bij te werken." + }, + "confirmIdentityToContinue": { + "message": "Bevestig je identiteit om door te gaan" + }, + "enterYourMasterPassword": { + "message": "Voer je hoofdwachtwoord in" + }, + "updateSettings": { + "message": "Instellingen bijwerken" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticatiecode (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB versleutelde opslag voor bijlagen." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ versleutelde opslag voor bijlagen.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Noodtoegang." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Vervaljaar" }, + "monthly": { + "message": "maand" + }, "expiration": { "message": "Vervaldatum" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Ontgrendel tapporteren, noodtoegang en meer beveiligingsfuncties met Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Gratis organisaties kunnen geen bijlagen gebruiken" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Opwaarderen naar Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade voor volledige beveiliging" + "unlockAdvancedSecurity": { + "message": "Geavanceerde beveiligingsfuncties ontgrendelen" }, - "premiumGivesMoreTools": { - "message": "Premium geeft je meer tools om veilig te blijven, efficiënt te werken en in controle te blijven." + "unlockAdvancedSecurityDesc": { + "message": "Een Premium-abonnement geeft je meer tools om veilig en in controle te blijven" }, "explorePremium": { "message": "Premium verkennen" diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 7c4dbaf85dc..684a04d9175 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 7c4dbaf85dc..684a04d9175 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 41f679aba50..24729a2331b 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -32,7 +32,7 @@ "message": "Użyj logowania jednokrotnego" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Twoja organizacja wymaga logowania jednokrotnego." }, "welcomeBack": { "message": "Witaj ponownie" @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Zarchiwizowane elementy są wykluczone z wyników wyszukiwania i sugestii autouzupełniania. Czy na pewno chcesz archiwizować element?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edytuj" }, @@ -592,10 +595,13 @@ "message": "Pokaż" }, "viewAll": { - "message": "View all" + "message": "Pokaż wszystko" + }, + "showAll": { + "message": "Show all" }, "viewLess": { - "message": "View less" + "message": "Pokaż mniej" }, "viewLogin": { "message": "Pokaż dane logowania" @@ -743,7 +749,7 @@ "message": "Hasło główne jest nieprawidłowe" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Nieprawidłowe hasło główne. Sprawdź, czy Twój adres e-mail jest poprawny i czy Twoje konto zostało utworzone na $HOST$.", "placeholders": { "host": { "content": "$1", @@ -800,10 +806,10 @@ "message": "Po zablokowaniu urządzenia" }, "onIdle": { - "message": "On system idle" + "message": "Podczas bezczynności systemu" }, "onSleep": { - "message": "On system sleep" + "message": "Podczas uśpienia systemu" }, "onRestart": { "message": "Po uruchomieniu przeglądarki" @@ -1044,10 +1050,10 @@ "message": "Element został zapisany" }, "savedWebsite": { - "message": "Saved website" + "message": "Zapisana witryna" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Zapisane witryny ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Dowiedz się więcej" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Klucz uwierzytelniający (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB miejsca na zaszyfrowane załączniki." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Dostęp awaryjny." }, @@ -1573,7 +1609,7 @@ "message": "Odczytywanie klucza dostępu..." }, "passkeyAuthenticationFailed": { - "message": "Passkey authentication failed" + "message": "Uwierzytelnienie za pomocą klucza nie powiodło się" }, "useADifferentLogInMethod": { "message": "Użyj innej metody logowania" @@ -1645,7 +1681,7 @@ "message": "Musisz dodać podstawowy adres URL serwera lub co najmniej jedno niestandardowe środowisko." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "Adresy URL muszą używać protokołu HTTPS." }, "customEnvironment": { "message": "Niestandardowe środowisko" @@ -1701,28 +1737,28 @@ "message": "Wyłącz autouzupełnianie" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Potwierdź autouzupełnianie" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Ta witryna nie pasuje do Twoich zapisanych danych logowania. Zanim wpiszesz dane logowania, upewnij się, że jest to zaufana witryna." }, "showInlineMenuLabel": { "message": "Pokaż sugestie autouzupełniania na polach formularza" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "W jaki sposób Bitwarden chroni Twoje dane przed phishingiem?" }, "currentWebsite": { - "message": "Current website" + "message": "Aktualna witryna" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Wypełnij automatycznie i dodaj tę witrynę" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Automatyczne uzupełnianie bez dodawania" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Nie wypełniaj automatycznie" }, "showInlineMenuIdentitiesLabel": { "message": "Pokaż tożsamości w sugestiach" @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Rok wygaśnięcia" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Data wygaśnięcia" }, @@ -2437,7 +2476,7 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "Ta strona zakłóca działanie Bitwarden. Menu Bitwarden zostało tymczasowo wyłączone ze względów bezpieczeństwa." }, "setMasterPassword": { "message": "Ustaw hasło główne" @@ -3289,7 +3328,7 @@ "message": "Błąd odszyfrowywania" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Błąd podczas pobierania danych autouzupełniania" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden nie mógł odszyfrować poniższych elementów sejfu." @@ -4063,13 +4102,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Nie można automatycznie wypełnić" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Domyślnie dopasowanie jest ustawione na „Dokładne dopasowanie”. Aktualna strona internetowa nie jest dokładnie taka sama jak zapisane dane logowania dla tego elementu." }, "okay": { - "message": "Okay" + "message": "Ok" }, "toggleSideNavigation": { "message": "Przełącz nawigację boczną" @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Darmowe organizacje nie mogą używać załączników" }, @@ -4987,7 +5029,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "Domyślne ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5650,30 +5692,30 @@ "message": "Witaj w sejfie!" }, "phishingPageTitleV2": { - "message": "Phishing attempt detected" + "message": "Wykryto próbę phishingu" }, "phishingPageSummary": { - "message": "The site you are attempting to visit is a known malicious site and a security risk." + "message": "Witryna, którą próbujesz odwiedzić, jest znaną złośliwą witryną i zagrożeniem bezpieczeństwa." }, "phishingPageCloseTabV2": { "message": "Zamknij kartę" }, "phishingPageContinueV2": { - "message": "Continue to this site (not recommended)" + "message": "Przejdź do tej witryny (niezalecane)" }, "phishingPageExplanation1": { - "message": "This site was found in ", + "message": "Ta witryna została znaleziona w ", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name follows this." }, "phishingPageExplanation2": { - "message": ", an open-source list of known phishing sites used for stealing personal and sensitive information.", + "message": ", lista znanych witryn phishingowych, które służą do kradzieży danych osobowych i poufnych.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this." }, "phishingPageLearnMore": { - "message": "Learn more about phishing detection" + "message": "Dowiedz się więcej o wykrywaniu phishingu" }, "protectedBy": { - "message": "Protected by $PRODUCT$", + "message": "Chronione przez $PRODUCT$", "placeholders": { "product": { "content": "$1", @@ -5786,49 +5828,49 @@ "message": "Potwierdź domenę Key Connector" }, "atRiskLoginsSecured": { - "message": "Great job securing your at-risk logins!" + "message": "Świetna robota z zabezpieczeniem Twoich zagrożonych danych logowania!" }, "upgradeNow": { - "message": "Upgrade now" + "message": "Zaktualizuj teraz" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "Wbudowany uwierzytelniacz" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "Bezpieczne przechowywanie plików" }, "emergencyAccess": { - "message": "Emergency access" + "message": "Dostęp awaryjny" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "Monitorowanie naruszeń" }, "andMoreFeatures": { - "message": "And more!" + "message": "I jeszcze więcej!" }, "planDescPremium": { - "message": "Complete online security" + "message": "Pełne bezpieczeństwo w Internecie" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Ulepsz do Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { - "message": "Explore Premium" + "message": "Poznaj Premium" }, "loadingVault": { - "message": "Loading vault" + "message": "Ładowanie sejfu" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "Sejf załadowany" }, "settingDisabledByPolicy": { - "message": "This setting is disabled by your organization's policy.", + "message": "To ustawienie jest wyłączone zgodnie z zasadami polityki Twojej organizacji.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { @@ -5838,6 +5880,6 @@ "message": "Numer karty" }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Akcja przekroczenia limitu czasu" } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 9e5d2331744..c7a5300a873 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -10,11 +10,11 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Em qual lugar for, o Bitwarden protege suas senhas, chaves de acesso, e informações confidenciais", + "message": "Onde quer que você esteja, o Bitwarden protege suas senhas, chaves de acesso e informações sensíveis", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { - "message": "Inicie a sessão ou crie uma nova conta para acessar seu cofre seguro." + "message": "Conecte-se ou crie uma conta para acessar o seu cofre seguro." }, "inviteAccepted": { "message": "Convite aceito" @@ -26,7 +26,7 @@ "message": "Novo no Bitwarden?" }, "logInWithPasskey": { - "message": "Entrar com chave de acesso" + "message": "Conectar-se com chave de acesso" }, "useSingleSignOn": { "message": "Usar autenticação única" @@ -38,10 +38,10 @@ "message": "Boas-vindas de volta" }, "setAStrongPassword": { - "message": "Defina uma senha forte" + "message": "Configure uma senha forte" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Termine de criar a sua conta definindo uma senha" + "message": "Termine de criar a sua conta configurando uma senha" }, "enterpriseSingleSignOn": { "message": "Autenticação única empresarial" @@ -65,7 +65,7 @@ "message": "A senha principal é a senha que você usa para acessar o seu cofre. É muito importante que você não esqueça sua senha principal. Não há maneira de recuperar a senha caso você se esqueça." }, "masterPassHintDesc": { - "message": "Uma dica de senha principal pode ajudá-lo(a) a lembrá-lo(a) caso você esqueça." + "message": "Uma dica de senha principal pode ajudá-lo(a) a lembrá-la caso você esqueça." }, "masterPassHintText": { "message": "Se você esquecer sua senha, a dica de senha pode ser enviada ao seu e-mail. $CURRENT$/$MAXIMUM$ caracteres máximos.", @@ -108,7 +108,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Termine de juntar-se à organização definindo uma senha principal." + "message": "Termine de juntar-se à organização configurando uma senha principal." }, "tab": { "message": "Aba" @@ -117,7 +117,7 @@ "message": "Cofre" }, "myVault": { - "message": "Meu Cofre" + "message": "Meu cofre" }, "allVaults": { "message": "Todos os cofres" @@ -129,10 +129,10 @@ "message": "Configurações" }, "currentTab": { - "message": "Aba Atual" + "message": "Aba atual" }, "copyPassword": { - "message": "Copiar Senha" + "message": "Copiar senha" }, "copyPassphrase": { "message": "Copiar frase secreta" @@ -144,7 +144,7 @@ "message": "Copiar URI" }, "copyUsername": { - "message": "Copiar Nome de Usuário" + "message": "Copiar nome de usuário" }, "copyNumber": { "message": "Copiar número" @@ -200,10 +200,10 @@ "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": "Autopreencher" + "message": "Preenchimento automático" }, "autoFillLogin": { - "message": "Preencher login automaticamente" + "message": "Preencher credencial automaticamente" }, "autoFillCard": { "message": "Preencher cartão automaticamente" @@ -212,7 +212,7 @@ "message": "Preencher identidade automaticamente" }, "fillVerificationCode": { - "message": "Preencher o código de verificação" + "message": "Preencher código de verificação" }, "fillVerificationCodeAria": { "message": "Preencher código de verificação", @@ -228,7 +228,7 @@ "message": "Nenhuma credencial correspondente" }, "noCards": { - "message": "Sem cartões" + "message": "Nenhum cartão" }, "noIdentities": { "message": "Nenhuma identidade" @@ -246,7 +246,7 @@ "message": "Desbloqueie seu cofre" }, "loginToVaultMenu": { - "message": "Acesse o seu cofre" + "message": "Conecte-se ao seu cofre" }, "autoFillInfo": { "message": "Não há credenciais disponíveis para preencher automaticamente na aba atual do navegador." @@ -264,13 +264,13 @@ "message": "Solicitar dica" }, "requestPasswordHint": { - "message": "Dica da senha principal" + "message": "Solicitar dica da senha" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Digite o endereço de e-mail da sua conta e dica da sua senha será enviada para você" }, "getMasterPasswordHint": { - "message": "Obter dica da senha principal" + "message": "Receber dica da senha principal" }, "continue": { "message": "Continuar" @@ -319,14 +319,14 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "yourAccountsFingerprint": { - "message": "A sua frase biométrica", + "message": "A frase biométrica da sua conta", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "twoStepLogin": { - "message": "Login em Duas Etapas" + "message": "Autenticação em duas etapas" }, "logOut": { - "message": "Sair" + "message": "Desconectar" }, "aboutBitwarden": { "message": "Sobre o Bitwarden" @@ -338,7 +338,7 @@ "message": "Mais do Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continuar para bitwarden.com?" + "message": "Continuar em bitwarden.com?" }, "bitwardenForBusiness": { "message": "Bitwarden para Negócios" @@ -362,10 +362,10 @@ "message": "Crie experiências de autenticação seguras, simples, livres de senhas tradicionais, com o Passwordless.dev. Saiba mais no site bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Bitwarden Families grátis" + "message": "Bitwarden Famílias grátis" }, "freeBitwardenFamiliesPageDesc": { - "message": "Você é elegível para o plano Bitwarden Families grátis. Resgate esta oferta hoje no aplicativo web." + "message": "Você é elegível para o plano Bitwarden Famílias grátis. Resgate esta oferta hoje no aplicativo web." }, "version": { "message": "Versão" @@ -377,13 +377,13 @@ "message": "Mover" }, "addFolder": { - "message": "Adicionar Pasta" + "message": "Adicionar pasta" }, "name": { "message": "Nome" }, "editFolder": { - "message": "Editar Pasta" + "message": "Editar pasta" }, "editFolderWithName": { "message": "Editar pasta: $FOLDERNAME$", @@ -413,25 +413,25 @@ "message": "Tem certeza que quer apagar esta pasta para sempre?" }, "deleteFolder": { - "message": "Excluir Pasta" + "message": "Apagar pasta" }, "folders": { "message": "Pastas" }, "noFolders": { - "message": "Não existem pastas para listar." + "message": "Não há pastas para listar." }, "helpFeedback": { - "message": "Ajuda & Feedback" + "message": "Ajuda e retorno" }, "helpCenter": { "message": "Central de ajuda do Bitwarden" }, "communityForums": { - "message": "Explore os fóruns da comunidade do Bitwarden" + "message": "Explorar os fóruns da comunidade do Bitwarden" }, "contactSupport": { - "message": "Contate o suporte do Bitwarden" + "message": "Entrar em contato com o suporte do Bitwarden" }, "sync": { "message": "Sincronizar" @@ -440,7 +440,7 @@ "message": "Sincronizar cofre agora" }, "lastSync": { - "message": "Última Sincronização:" + "message": "Última sincronização:" }, "passGen": { "message": "Gerador de senhas" @@ -462,7 +462,7 @@ "message": "Selecionar" }, "generatePassword": { - "message": "Gerar Senha" + "message": "Gerar senha" }, "generatePassphrase": { "message": "Gerar frase secreta" @@ -480,7 +480,7 @@ "message": "E-mail gerado" }, "regeneratePassword": { - "message": "Gerar nova senha" + "message": "Regerar senha" }, "options": { "message": "Opções" @@ -527,7 +527,7 @@ "message": "Separador de palavras" }, "capitalize": { - "message": "Iniciais em Maiúsculas", + "message": "Iniciais maiúsculas", "description": "Make the first letter of a work uppercase." }, "includeNumber": { @@ -548,10 +548,10 @@ "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { - "message": "Pesquisar no Cofre" + "message": "Buscar no cofre" }, "resetSearch": { - "message": "Redefinir pesquisa" + "message": "Apagar busca" }, "archiveNoun": { "message": "Arquivo", @@ -571,7 +571,7 @@ "message": "Nenhum item no arquivo" }, "noItemsInArchiveDesc": { - "message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados de pesquisa gerais e das sugestões de preenchimento automático." + "message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados gerais de busca e das sugestões de preenchimento automático." }, "itemWasSentToArchive": { "message": "O item foi enviado para o arquivo" @@ -583,7 +583,10 @@ "message": "Arquivar item" }, "archiveItemConfirmDesc": { - "message": "Itens arquivados são excluídos dos resultados da pesquisa geral e das sugestões de preenchimento automático. Tem certeza de que deseja arquivar este item?" + "message": "Itens arquivados são excluídos dos resultados gerais de busca e das sugestões de preenchimento automático. Tem certeza de que deseja arquivar este item?" + }, + "upgradeToUseArchive": { + "message": "Um plano Premium é necessário para usar o arquivamento." }, "edit": { "message": "Editar" @@ -594,6 +597,9 @@ "viewAll": { "message": "Ver tudo" }, + "showAll": { + "message": "Mostrar tudo" + }, "viewLess": { "message": "Ver menos" }, @@ -607,7 +613,7 @@ "message": "Informações do item" }, "username": { - "message": "Nome de Usuário" + "message": "Nome de usuário" }, "password": { "message": "Senha" @@ -616,10 +622,10 @@ "message": "Segredo do autenticador" }, "passphrase": { - "message": "Frase Secreta" + "message": "Frase secreta" }, "favorite": { - "message": "Favorito" + "message": "Favoritar" }, "unfavorite": { "message": "Desfavoritar" @@ -631,13 +637,13 @@ "message": "Item removido dos favoritos" }, "notes": { - "message": "Notas" + "message": "Anotações" }, "privateNote": { "message": "Anotação privada" }, "note": { - "message": "Nota" + "message": "Anotação" }, "editItem": { "message": "Editar item" @@ -697,10 +703,10 @@ "message": "Outras opções" }, "rateExtension": { - "message": "Avaliar a Extensão" + "message": "Avalie a extensão" }, "browserNotSupportClipboard": { - "message": "O seu navegador web não suporta cópia para a área de transferência. Em alternativa, copie manualmente." + "message": "O seu navegador web não suporta copiar para a área de transferência. Em vez disso, copie manualmente." }, "verifyYourIdentity": { "message": "Verifique a sua identidade" @@ -712,7 +718,7 @@ "message": "Continuar acessando" }, "yourVaultIsLocked": { - "message": "Seu cofre está trancado. Verifique sua identidade para continuar." + "message": "Seu cofre está bloqueado. Verifique sua identidade para continuar." }, "yourVaultIsLockedV2": { "message": "Seu cofre está bloqueado" @@ -727,7 +733,7 @@ "message": "Desbloquear" }, "loggedInAsOn": { - "message": "Entrou como $EMAIL$ em $HOSTNAME$.", + "message": "Conectado como $EMAIL$ em $HOSTNAME$.", "placeholders": { "email": { "content": "$1", @@ -758,7 +764,7 @@ "message": "Tempo limite" }, "lockNow": { - "message": "Bloquear Agora" + "message": "Bloquear agora" }, "lockAll": { "message": "Bloquear tudo" @@ -797,16 +803,16 @@ "message": "4 horas" }, "onLocked": { - "message": "Ao bloquear o sistema" + "message": "No bloqueio do sistema" }, "onIdle": { - "message": "Quando o sistema ficar inativo" + "message": "Na inatividade do sistema" }, "onSleep": { - "message": "Quando o sistema hibernar" + "message": "Na hibernação do sistema" }, "onRestart": { - "message": "Ao reiniciar o navegador" + "message": "No reinício do navegador" }, "never": { "message": "Nunca" @@ -839,7 +845,7 @@ "message": "A senha principal é necessária." }, "confirmMasterPasswordRequired": { - "message": "É necessário digitar a senha principal novamente." + "message": "É necessário redigitar a senha principal." }, "masterPasswordMinlength": { "message": "A senha principal deve ter pelo menos $VALUE$ caracteres.", @@ -855,7 +861,7 @@ "message": "A confirmação da senha principal não corresponde." }, "newAccountCreated": { - "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." + "message": "A sua conta nova foi criada! Agora você pode se conectar." }, "newAccountCreated2": { "message": "Sua nova conta foi criada!" @@ -864,7 +870,7 @@ "message": "Você foi conectado!" }, "youSuccessfullyLoggedIn": { - "message": "Você entrou na sua conta com sucesso" + "message": "Você se conectou com sucesso" }, "youMayCloseThisWindow": { "message": "Você pode fechar esta janela" @@ -876,13 +882,13 @@ "message": "O código de verificação é necessário." }, "webauthnCancelOrTimeout": { - "message": "A autenticação foi cancelada ou demorou muito. Por favor tente novamente." + "message": "A autenticação foi cancelada ou demorou muito. Tente novamente." }, "invalidVerificationCode": { "message": "Código de verificação inválido" }, "valueCopied": { - "message": " copiado", + "message": "$VALUE$ copiado(a)", "description": "Value has been copied to the clipboard.", "placeholders": { "value": { @@ -919,7 +925,7 @@ "message": "Copiar chave do autenticador (TOTP)" }, "loggedOut": { - "message": "Sessão encerrada" + "message": "Desconectado" }, "loggedOutDesc": { "message": "Você foi desconectado de sua conta." @@ -928,13 +934,13 @@ "message": "A sua sessão expirou." }, "logIn": { - "message": "Entrar" + "message": "Conectar-se" }, "logInToBitwarden": { - "message": "Entre no Bitwarden" + "message": "Conecte-se ao Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Digite o código enviado por e-mail" + "message": "Digite o código enviado ao seu e-mail" }, "enterTheCodeFromYourAuthenticatorApp": { "message": "Digite o código do seu autenticador" @@ -943,19 +949,19 @@ "message": "Pressione sua YubiKey para autenticar-se" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "A autenticação de dois fatores do Duo é necessária para sua conta. Siga os passos abaixo para conseguir entrar." + "message": "A autenticação de duas etapas do Duo é necessária para sua conta. Siga os passos abaixo para conseguir se conectar." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Siga os passos abaixo para finalizar a autenticação." + "message": "Siga os passos abaixo para terminar de se conectar." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Siga os passos abaixo para finalizar a autenticação com a sua chave de segurança." + "message": "Siga os passos abaixo para finalizar de se conectar com a sua chave de segurança." }, "restartRegistration": { "message": "Reiniciar cadastro" }, "expiredLink": { - "message": "Link expirado" + "message": "Link vencido" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Reinicie o cadastro ou tente conectar-se." @@ -964,7 +970,7 @@ "message": "Você pode já ter uma conta" }, "logOutConfirmation": { - "message": "Você tem certeza que deseja sair?" + "message": "Tem certeza que quer se desconectar?" }, "yes": { "message": "Sim" @@ -991,13 +997,13 @@ "message": "Torne sua conta mais segura configurando a autenticação em duas etapas no aplicativo web do Bitwarden." }, "twoStepLoginConfirmationTitle": { - "message": "Continuar para o aplicativo web?" + "message": "Continuar no aplicativo web?" }, "editedFolder": { "message": "Pasta salva" }, "deleteFolderConfirmation": { - "message": "Você tem certeza que deseja excluir esta pasta?" + "message": "Tem certeza que deseja apagar esta pasta?" }, "deletedFolder": { "message": "Pasta apagada" @@ -1009,16 +1015,16 @@ "message": "Assista o nosso tutorial de introdução e saiba como tirar o máximo de proveito da extensão de navegador." }, "syncingComplete": { - "message": "Sincronização completa" + "message": "Sincronização concluída" }, "syncingFailed": { - "message": "A Sincronização falhou" + "message": "A sincronização falhou" }, "passwordCopied": { "message": "Senha copiada" }, "uri": { - "message": "URL" + "message": "URI" }, "uriPosition": { "message": "URI $POSITION$", @@ -1056,7 +1062,7 @@ } }, "deleteItemConfirmation": { - "message": "Você tem certeza que deseja enviar este item para a lixeira?" + "message": "Tem certeza que quer enviar este item para a lixeira?" }, "deletedItem": { "message": "Item enviado para a lixeira" @@ -1068,19 +1074,19 @@ "message": "Você tem certeza que deseja substituir a senha atual?" }, "overwriteUsername": { - "message": "Sobrescrever nome de usuário" + "message": "Substituir nome de usuário" }, "overwriteUsernameConfirmation": { "message": "Tem certeza que deseja substituir o nome de usuário atual?" }, "searchFolder": { - "message": "Pesquisar pasta" + "message": "Buscar na pasta" }, "searchCollection": { - "message": "Pesquisar coleção" + "message": "Buscar no conjunto" }, "searchType": { - "message": "Pesquisar tipo" + "message": "Buscar tipo" }, "noneFolder": { "message": "Sem pasta", @@ -1114,7 +1120,7 @@ "message": "Mostrar identidades na página da aba" }, "showIdentitiesCurrentTabDesc": { - "message": "Liste as identidades na página da aba para facilitar o preenchimento automático." + "message": "Listar as identidades na página da aba para facilitar o preenchimento automático." }, "clickToAutofillOnVault": { "message": "Clique em itens na tela do Cofre para preencher automaticamente" @@ -1248,10 +1254,10 @@ "description": "Detailed error message shown when saving login details fails." }, "changePasswordWarning": { - "message": "Ao alterar sua senha, você precisará entrar com a sua senha nova. Sessões ativas em outros dispositivos serão desconectados dentro de uma hora." + "message": "Ao alterar sua senha, você precisará se conectar com a sua senha nova. Sessões ativas em outros dispositivos serão desconectadas dentro de uma hora." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Mude a sua senha principal para completar a recuperação de conta." + "message": "Altere a sua senha principal para concluir a recuperação da conta." }, "enableChangedPasswordNotification": { "message": "Pedir para atualizar credencial existente" @@ -1266,7 +1272,7 @@ "message": "Pedir para salvar e usar chaves de acesso" }, "usePasskeysDesc": { - "message": "Pedir para salvar novas chaves de acesso ou entrar com as mesmas armazenadas no seu cofre. Aplica-se a todas as contas conectadas." + "message": "Pedir para salvar novas chaves de acesso ou se conectar com as mesmas armazenadas no seu cofre. Aplica-se a todas as contas conectadas." }, "notificationChangeDesc": { "message": "Você quer atualizar esta senha no Bitwarden?" @@ -1293,7 +1299,7 @@ "message": "Use um clique secundário para acessar a geração de senha e as credenciais correspondentes para o site. Aplica-se a todas as contas conectadas." }, "defaultUriMatchDetection": { - "message": "Detecção de correspondência de URI padrão", + "message": "Detecção de correspondência padrão de URI", "description": "Default URI match detection for autofill." }, "defaultUriMatchDetectionDesc": { @@ -1320,7 +1326,7 @@ "message": "Exportar de" }, "exportVault": { - "message": "Exportar Cofre" + "message": "Exportar cofre" }, "fileFormat": { "message": "Formato do arquivo" @@ -1338,7 +1344,7 @@ "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha principal da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." }, "passwordProtectedOptionDescription": { - "message": "Defina uma senha para criptografar a exportação e importá-la para qualquer conta do Bitwarden usando a senha para descriptografar." + "message": "Configure uma senha de arquivo para criptografar a exportação e importá-la para qualquer conta do Bitwarden usando a senha para descriptografá-la." }, "exportTypeHeading": { "message": "Tipo da exportação" @@ -1347,21 +1353,21 @@ "message": "Restrita à conta" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "\"Senha do arquivo\" e \"Confirmação de senha\" não correspondem." + "message": "\"Senha do arquivo\" e \"Confirmar senha do arquivo\" não correspondem." }, "warning": { - "message": "AVISO", + "message": "ALERTA", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Aviso", + "message": "Atenção", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { "message": "Confirmar exportação do cofre" }, "exportWarningDesc": { - "message": "Esta exportação contém os dados do seu cofre em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Exclua o arquivo imediatamente após terminar de usá-lo." + "message": "Esta exportação contém os dados do seu cofre em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague o arquivo imediatamente após terminar de usá-lo." }, "encExportKeyWarningDesc": { "message": "Esta exportação criptografa seus dados usando a chave de criptografia da sua conta. Se você rotacionar a chave de criptografia da sua conta, você deve exportar novamente, já que você não será capaz de descriptografar este arquivo de exportação." @@ -1370,7 +1376,7 @@ "message": "As chaves de criptografia são únicas para cada conta de usuário do Bitwarden, então você não pode importar um arquivo de exportação criptografado para uma conta diferente." }, "exportMasterPassword": { - "message": "Insira a sua senha principal para exportar os dados do seu cofre." + "message": "Digite a sua senha principal para exportar os dados do seu cofre." }, "shared": { "message": "Compartilhado" @@ -1398,7 +1404,28 @@ "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." }, "learnMore": { - "message": "Saber mais" + "message": "Saiba mais" + }, + "migrationsFailed": { + "message": "Ocorreu um erro ao atualizar as configurações de criptografia." + }, + "updateEncryptionSettingsTitle": { + "message": "Atualize suas configurações de criptografia" + }, + "updateEncryptionSettingsDesc": { + "message": "As novas configurações de criptografia recomendadas melhorarão a segurança da sua conta. Digite sua senha principal para atualizar agora." + }, + "confirmIdentityToContinue": { + "message": "Confirme sua identidade para continuar" + }, + "enterYourMasterPassword": { + "message": "Digite sua senha principal" + }, + "updateSettings": { + "message": "Atualizar configurações" + }, + "later": { + "message": "Depois" }, "authenticatorKeyTotp": { "message": "Chave do autenticador (TOTP)" @@ -1413,10 +1440,10 @@ "message": "Anexos" }, "deleteAttachment": { - "message": "Excluir anexo" + "message": "Apagar anexo" }, "deleteAttachmentConfirmation": { - "message": "Tem a certeza de que deseja excluir este anexo?" + "message": "Tem a certeza de que deseja apagar este anexo?" }, "deletedAttachment": { "message": "Anexo apagado" @@ -1425,7 +1452,7 @@ "message": "Adicionar novo anexo" }, "noAttachments": { - "message": "Sem anexos." + "message": "Nenhum anexo." }, "attachmentSaved": { "message": "Anexo salvo" @@ -1437,7 +1464,7 @@ "message": "Arquivo para compartilhar" }, "selectFile": { - "message": "Selecione um arquivo." + "message": "Selecione um arquivo" }, "maxFileSize": { "message": "O tamanho máximo do arquivo é de 500 MB." @@ -1455,19 +1482,28 @@ "message": "Gerenciar plano" }, "premiumManageAlert": { - "message": "Você pode gerenciar a sua assinatura premium no cofre web em bitwarden.com. Você deseja visitar o site agora?" + "message": "Você pode gerenciar o seu plano no cofre web do bitwarden.com. Você deseja visitar o site agora?" }, "premiumRefresh": { - "message": "Recarregar assinatura" + "message": "Recarregar plano" }, "premiumNotCurrentMember": { "message": "Você não é um membro Premium atualmente." }, "premiumSignUpAndGet": { - "message": "Inscreva-se para uma assinatura Premium e receba:" + "message": "Inscreva-se para um plano Premium e receba:" }, "ppremiumSignUpStorage": { - "message": "1 GB de armazenamento de arquivos encriptados." + "message": "1 GB de armazenamento criptografado para anexo de arquivos." + }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ de armazenamento criptografado para anexos de arquivo.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } }, "premiumSignUpEmergency": { "message": "Acesso de emergência." @@ -1476,7 +1512,7 @@ "message": "Opções de autenticação em duas etapas proprietárias como YubiKey e Duo." }, "ppremiumSignUpReports": { - "message": "Higiene de senha, saúde da conta, e relatórios sobre violação de dados para manter o seu cofre seguro." + "message": "Relatórios de higiene de senha, saúde da conta, e vazamentos de dados para manter o seu cofre seguro." }, "ppremiumSignUpTotp": { "message": "Gerador de códigos de verificação TOTP (2FA) para credenciais no seu cofre." @@ -1485,7 +1521,7 @@ "message": "Prioridade no suporte ao cliente." }, "ppremiumSignUpFuture": { - "message": "Todas as recursos do Premium no futuro. Mais em breve!" + "message": "Todos as recursos do Premium no futuro. Mais em breve!" }, "premiumPurchase": { "message": "Comprar Premium" @@ -1503,7 +1539,7 @@ "message": "Faça upgrade para o Premium e receba:" }, "premiumPrice": { - "message": "Tudo por apenas %price% /ano!", + "message": "Tudo por apenas $PRICE$ por ano!", "placeholders": { "price": { "content": "$1", @@ -1521,7 +1557,7 @@ } }, "refreshComplete": { - "message": "Atualização completa" + "message": "Recarregamento concluído" }, "enableAutoTotpCopy": { "message": "Copiar TOTP automaticamente" @@ -1533,7 +1569,7 @@ "message": "Pedir biometria ao abrir" }, "authenticationTimeout": { - "message": "Tempo de autenticação esgotado" + "message": "Tempo limite da autenticação atingido" }, "authenticationSessionTimedOut": { "message": "A sessão de autenticação expirou. Reinicie o processo de autenticação." @@ -1558,7 +1594,7 @@ "message": "Usar seu código de recuperação" }, "insertU2f": { - "message": "Insira a sua chave de segurança na porta USB do seu computador. Se ele tiver um botão, toque nele." + "message": "Insira a sua chave de segurança na porta USB do seu computador. Se ela tiver um botão, toque nele." }, "openInNewTab": { "message": "Abrir em uma nova aba" @@ -1576,7 +1612,7 @@ "message": "Falha na autenticação da chave de acesso" }, "useADifferentLogInMethod": { - "message": "Usar um método de entrada diferente" + "message": "Usar um método de autenticação diferente" }, "awaitingSecurityKeyInteraction": { "message": "Aguardando interação com a chave de segurança..." @@ -1585,10 +1621,10 @@ "message": "Autenticação indisponível" }, "noTwoStepProviders": { - "message": "Esta conta tem a verificação de duas etapas ativada, no entanto, nenhum dos provedores de verificação de duas etapas configurados são suportados por este navegador web." + "message": "Esta conta tem a autenticação de duas etapas ativada, no entanto, nenhum dos provedores configurados são suportados por este navegador web." }, "noTwoStepProviders2": { - "message": "Por favor utilize um navegador web suportado (tal como o Chrome) e/ou inclua provedores adicionais que são melhor suportados entre navegadores web (tal como uma aplicativo de autenticação)." + "message": "Use um navegador web suportado (tal como o Chrome) e/ou inclua provedores adicionais que são melhor suportados entre navegadores web (tal como um aplicativo autenticator)." }, "twoStepOptions": { "message": "Opções de autenticação em duas etapas" @@ -1610,14 +1646,14 @@ "message": "Chave de segurança Yubico OTP" }, "yubiKeyDesc": { - "message": "Utilize uma YubiKey para acessar a sua conta. Funciona com YubiKey 4, 4 Nano, 4C, e dispositivos NEO." + "message": "Utilize uma YubiKey para acessar a sua conta. Funciona com os dispositivos YubiKey 4, 4 Nano, 4C, e NEO." }, "duoDescV2": { "message": "Digite um código gerado pelo Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "Verifique com o Duo Security utilizando o aplicativo Duo Mobile, SMS, chamada telefônica, ou chave de segurança U2F.", + "message": "Verifique com o Duo Security para a sua organização usando 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." }, "webAuthnTitle": { @@ -1636,13 +1672,13 @@ "message": "Ambiente auto-hospedado" }, "selfHostedBaseUrlHint": { - "message": "Especifique o0 URL de base da sua instalação local do Bitwarden. Exemplo: https://bitwarden.company.com" + "message": "Especifique o URL de base da sua instalação local do Bitwarden. Exemplo: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { "message": "Para configuração avançada, você pode especificar a URL de base de cada serviço independentemente." }, "selfHostedEnvFormInvalid": { - "message": "Você deve adicionar um URL do servidor de base ou pelo menos um ambiente personalizado." + "message": "Você deve adicionar um URL de base de um servidor ou pelo menos um ambiente personalizado." }, "selfHostedEnvMustUseHttps": { "message": "URLs devem usar HTTPS." @@ -1651,7 +1687,7 @@ "message": "Ambiente personalizado" }, "baseUrl": { - "message": "URL do Servidor" + "message": "URL do servidor" }, "selfHostBaseUrl": { "message": "URL do servidor auto-hospedado", @@ -1737,7 +1773,7 @@ "message": "Aplica-se a todas as contas conectadas." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Desative o gerenciador de senhas integrado do seu navegador para evitar conflitos." + "message": "Desative o gerenciador de senhas embutido no seu navegador para evitar conflitos." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "Edite as configurações do navegador." @@ -1773,7 +1809,7 @@ "message": "Saiba mais sobre preenchimento automático" }, "defaultAutoFillOnPageLoad": { - "message": "Configuração de autopreenchimento padrão para itens de credenciais" + "message": "Configuração padrão de preenchimento automático para credenciais" }, "defaultAutoFillOnPageLoadDesc": { "message": "Você pode desativar o preenchimento automático no carregamento da página para credenciais individuais na tela de Editar do item." @@ -1794,7 +1830,7 @@ "message": "Abrir cofre na barra lateral" }, "commandAutofillLoginDesc": { - "message": "Preencher automaticamente o último login utilizado para o site atual" + "message": "Preencher automaticamente a última credencial utilizada para o site atual" }, "commandAutofillCardDesc": { "message": "Preencher automaticamente o último cartão utilizado para o site atual" @@ -1803,7 +1839,7 @@ "message": "Preencher automaticamente a última identidade usada para o site atual" }, "commandGeneratePasswordDesc": { - "message": "Gerar e copiar uma nova senha aleatória para a área de transferência." + "message": "Gere e copie uma nova senha aleatória para a área de transferência" }, "commandLockVaultDesc": { "message": "Bloquear o cofre" @@ -1821,7 +1857,7 @@ "message": "Novo campo personalizado" }, "dragToSort": { - "message": "Arrastar para ordenar" + "message": "Arraste para ordenar" }, "dragToReorder": { "message": "Arraste para reorganizar" @@ -1830,7 +1866,7 @@ "message": "Texto" }, "cfTypeHidden": { - "message": "Ocultado" + "message": "Oculto" }, "cfTypeBoolean": { "message": "Booleano" @@ -1847,7 +1883,7 @@ "description": "This describes a value that is 'linked' (tied) to another value." }, "popup2faCloseMessage": { - "message": "Ao clicar fora da janela de pop-up para verificar seu e-mail para o seu código de verificação fará com que este pop-up feche. Você deseja abrir este pop-up em uma nova janela para que ele não seja fechado?" + "message": "Ao clicar fora da janela de pop-up para conferir seu e-mail pelo seu código de verificação, este pop-up fechará. Você deseja abrir este pop-up em uma nova janela para que ele não seja fechado?" }, "showIconsChangePasswordUrls": { "message": "Mostrar ícones de sites e obter URLs de alteração de senha" @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Ano de vencimento" }, + "monthly": { + "message": "mês" + }, "expiration": { "message": "Vencimento" }, @@ -1913,7 +1952,7 @@ "message": "número do cartão" }, "ex": { - "message": "ex." + "message": "p. ex." }, "title": { "message": "Título" @@ -1979,10 +2018,10 @@ "message": "Endereço 3" }, "cityTown": { - "message": "Cidade / Localidade" + "message": "Cidade ou localidade" }, "stateProvince": { - "message": "Estado / Província" + "message": "Estado ou província" }, "zipPostalCode": { "message": "CEP / Código postal" @@ -2071,23 +2110,23 @@ "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "Visualizar credencial", + "message": "Ver credencial", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "Visualizar cartão", + "message": "Ver cartão", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { - "message": "Visualizar identidade", + "message": "Ver identidade", "description": "Header for view identity item type" }, "viewItemHeaderNote": { - "message": "Visualizar anotação", + "message": "Ver anotação", "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "Visualizar chave SSH", + "message": "Ver chave SSH", "description": "Header for view SSH key item type" }, "passwordHistory": { @@ -2106,10 +2145,10 @@ "message": "Voltar" }, "collections": { - "message": "Coleções" + "message": "Conjuntos" }, "nCollections": { - "message": "$COUNT$ coleções", + "message": "$COUNT$ conjuntos", "placeholders": { "count": { "content": "$1", @@ -2124,7 +2163,7 @@ "message": "Abrir em uma nova janela" }, "refresh": { - "message": "Atualizar" + "message": "Recarregar" }, "cards": { "message": "Cartões" @@ -2146,10 +2185,10 @@ "description": "To clear something out. example: To clear browser history." }, "checkPassword": { - "message": "Verifique se a senha foi exposta." + "message": "Confira se a senha foi exposta." }, "passwordExposed": { - "message": "Esta senha foi exposta $VALUE$ vez(es) em violações de dados. Você deve alterá-la.", + "message": "Esta senha foi exposta $VALUE$ vez(es) em vazamentos de dados. Você deve alterá-la.", "placeholders": { "value": { "content": "$1", @@ -2158,7 +2197,7 @@ } }, "passwordSafe": { - "message": "Esta senha não foi encontrada em violações de dados conhecidas. Deve ser seguro de usar." + "message": "Esta senha não foi encontrada em vazamentos de dados conhecidos. Deve ser segura de usar." }, "baseDomain": { "message": "Domínio de base", @@ -2216,7 +2255,7 @@ "message": "Todos os itens" }, "noPasswordsInList": { - "message": "Não existem senhas para listar." + "message": "Não há senhas para listar." }, "clearHistory": { "message": "Limpar histórico" @@ -2246,19 +2285,19 @@ "description": "ex. Date this password was updated" }, "neverLockWarning": { - "message": "Você tem certeza que deseja usar a opção \"Nunca\"? Definir suas opções de bloqueio para \"Nunca\" armazena a chave de criptografia do seu cofre no seu dispositivo. Se você usar esta opção, você deve garantir que irá manter o seu dispositivo devidamente protegido." + "message": "Você tem certeza que deseja usar a opção \"Nunca\"? Configurar suas opções de bloqueio para \"Nunca\" armazena a chave de criptografia do seu cofre no seu dispositivo. Se você usar esta opção, você deve garantir que irá manter o seu dispositivo devidamente protegido." }, "noOrganizationsList": { - "message": "Você pertence a nenhuma organização. As organizações permitem que você compartilhe itens em segurança com outros usuários." + "message": "Você não pertence a nenhuma organização. As organizações permitem que você compartilhe itens em segurança com outros usuários." }, "noCollectionsInList": { - "message": "Não há coleções para listar." + "message": "Não há conjuntos para listar." }, "ownership": { "message": "Propriedade" }, "whoOwnsThisItem": { - "message": "Quem possui este item?" + "message": "Quem é o proprietário deste item?" }, "strong": { "message": "Forte", @@ -2273,17 +2312,17 @@ "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Senha principal Fraca" + "message": "Senha principal fraca" }, "weakMasterPasswordDesc": { - "message": "A senha principal que você selecionou está fraca. Você deve usar uma senha principal forte (ou uma frase-passe) para proteger a sua conta Bitwarden adequadamente. Tem certeza que deseja usar esta senha principal?" + "message": "A senha principal que você selecionou está fraca. Você deve usar uma senha principal forte (ou uma frase secreta) para proteger a sua conta Bitwarden adequadamente. Tem certeza que deseja usar esta senha principal?" }, "pin": { "message": "PIN", "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { - "message": "Desbloquear com o PIN" + "message": "Desbloquear com PIN" }, "setYourPinTitle": { "message": "Configurar PIN" @@ -2292,10 +2331,10 @@ "message": "Configurar PIN" }, "setYourPinCode": { - "message": "Defina o seu código PIN para desbloquear o Bitwarden. Suas configurações de PIN serão redefinidas se alguma vez você encerrar completamente toda a sessão do aplicativo." + "message": "Configure o seu código PIN para desbloquear o Bitwarden. Suas configurações de PIN serão reconfiguradas se você desconectar a sua conta do aplicativo." }, "setPinCode": { - "message": "Você pode usar este PIN para desbloquear o Bitwarden. O seu PIN será redefinido se você sair da sua conta no aplicativo." + "message": "Você pode usar este PIN para desbloquear o Bitwarden. O seu PIN será reconfigurado se você desconectar a sua conta do aplicativo." }, "pinRequired": { "message": "O código PIN é necessário." @@ -2307,13 +2346,13 @@ "message": "Muitas tentativas de entrada de PIN inválidas. Desconectando." }, "unlockWithBiometrics": { - "message": "Desbloquear com a biometria" + "message": "Desbloquear com biometria" }, "unlockWithMasterPassword": { "message": "Desbloquear com senha principal" }, "awaitDesktop": { - "message": "Aguardando confirmação do desktop" + "message": "Aguardando confirmação do computador" }, "awaitDesktopDesc": { "message": "Confirme o uso de biometria no aplicativo do Bitwarden Desktop para ativar a biometria para o navegador." @@ -2325,7 +2364,7 @@ "message": "Exigir senha principal ao reiniciar o navegador" }, "selectOneCollection": { - "message": "Você deve selecionar pelo menos uma coleção." + "message": "Você deve selecionar pelo menos um conjunto." }, "cloneItem": { "message": "Clonar item" @@ -2343,16 +2382,16 @@ "message": "Usar este e-mail" }, "useThisPassword": { - "message": "Use esta senha" + "message": "Usar esta senha" }, "useThisPassphrase": { - "message": "Use esta frase secreta" + "message": "Usar esta frase secreta" }, "useThisUsername": { - "message": "Use este nome de usuário" + "message": "Usar este nome de usuário" }, "securePasswordGenerated": { - "message": "Senha segura gerada! Não se esqueça de atualizar também sua senha no site." + "message": "Senha segura gerada! Não se esqueça de atualizar sua senha no site também." }, "useGeneratorHelpTextPartOne": { "message": "Use o gerador", @@ -2380,13 +2419,13 @@ "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { - "message": "Pesquisar na lixeira" + "message": "Buscar na lixeira" }, "permanentlyDeleteItem": { "message": "Apagar item para sempre" }, "permanentlyDeleteItemConfirmation": { - "message": "Você tem certeza que deseja excluir permanentemente esse item?" + "message": "Tem certeza que deseja apagar esse item para sempre?" }, "permanentlyDeletedItem": { "message": "Item apagado para sempre" @@ -2401,7 +2440,7 @@ "message": "Já tem uma conta?" }, "vaultTimeoutLogOutConfirmation": { - "message": "Sair irá remover todo o acesso ao seu cofre e requer autenticação online após o período de tempo limite. Tem certeza de que deseja usar esta configuração?" + "message": "Desconectar-se irá remover todo o acesso ao seu cofre e requirirá autenticação online após o período de tempo limite. Tem certeza de que deseja usar esta configuração?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Confirmação da ação do tempo limite" @@ -2437,10 +2476,10 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "Esta página está interferindo com a experiência do Bitwarden. O menu inline do Bitwarden foi temporariamente desativado como uma medida de segurança." }, "setMasterPassword": { - "message": "Definir senha principal" + "message": "Configurar senha principal" }, "currentMasterPass": { "message": "Senha principal atual" @@ -2473,16 +2512,16 @@ } }, "policyInEffectUppercase": { - "message": "Contém um ou mais caracteres em maiúsculo" + "message": "Conter um ou mais caracteres em maiúsculo" }, "policyInEffectLowercase": { - "message": "Contém um ou mais caracteres em minúsculo" + "message": "Conter um ou mais caracteres em minúsculo" }, "policyInEffectNumbers": { - "message": "Contém um ou mais números" + "message": "Conter um ou mais números" }, "policyInEffectSpecial": { - "message": "Contém um ou mais dos seguintes caracteres especiais $CHARS$", + "message": "Conter um ou mais dos seguintes caracteres especiais $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -2524,7 +2563,7 @@ "message": "Sua senha nova não pode ser a mesma que a sua atual." }, "hintEqualsPassword": { - "message": "Sua dica de senha não pode ser o mesmo que sua senha." + "message": "A dica da sua senha não pode ser a mesma coisa que sua senha." }, "ok": { "message": "Ok" @@ -2539,7 +2578,7 @@ "message": "Verificação de sincronização do Desktop" }, "desktopIntegrationVerificationText": { - "message": "Por favor, verifique se o aplicativo desktop mostra esta impressão digital: " + "message": "Verifique se o aplicativo de computador mostra esta frase biométrica: " }, "desktopIntegrationDisabledTitle": { "message": "A integração com o navegador não foi configurada" @@ -2548,40 +2587,40 @@ "message": "A integração com o navegador não foi configurada no aplicativo do Bitwarden Desktop. Configure ela nas configurações do aplicativo de computador." }, "startDesktopTitle": { - "message": "Iniciar o aplicativo Bitwarden Desktop" + "message": "Abrir o aplicativo Bitwarden Desktop" }, "startDesktopDesc": { - "message": "O aplicativo do Bitwarden para desktop precisa ser iniciado antes que o desbloqueio com biometria possa ser usado." + "message": "O aplicativo Bitwarden para computador precisa ser aberto antes que o desbloqueio com biometria possa ser usado." }, "errorEnableBiometricTitle": { "message": "Não é possível ativar a biometria" }, "errorEnableBiometricDesc": { - "message": "A ação foi cancelada pelo aplicativo desktop" + "message": "A ação foi cancelada pelo aplicativo de computador" }, "nativeMessagingInvalidEncryptionDesc": { - "message": "O aplicativo desktop invalidou o canal de comunicação seguro. Por favor, tente esta operação novamente" + "message": "O aplicativo de computador invalidou o canal de comunicação seguro. Tente esta operação novamente" }, "nativeMessagingInvalidEncryptionTitle": { - "message": "Comunicação com o desktop interrompida" + "message": "Comunicação com o computador interrompida" }, "nativeMessagingWrongUserDesc": { - "message": "O aplicativo desktop está conectado em uma conta diferente. Por favor, certifique-se de que ambos os aplicativos estejam conectados na mesma conta." + "message": "O aplicativo de computador está conectado em uma conta diferente. Certifique-se de que ambos os aplicativos estejam conectados na mesma conta." }, "nativeMessagingWrongUserTitle": { - "message": "A conta não confere" + "message": "Não correspondência da conta" }, "nativeMessagingWrongUserKeyTitle": { "message": "Não correspondência da chave biométrica" }, "nativeMessagingWrongUserKeyDesc": { - "message": "O desbloqueio biométrico falhou. A chave secreta da biometria falhou ao desbloquear o cofre. Tente configurar a biometrica biométricos novamente." + "message": "O desbloqueio biométrico falhou. A chave secreta da biometria falhou ao desbloquear o cofre. Tente configurar a biometria novamente." }, "biometricsNotEnabledTitle": { "message": "Biometria não configurada" }, "biometricsNotEnabledDesc": { - "message": "A biometria com o navegador requer que a biometria de desktop seja ativada nas configurações primeiro." + "message": "A biometria com o navegador requer que a biometria do desktop seja configurada nas configurações primeiro." }, "biometricsNotSupportedTitle": { "message": "Biometria não suportada" @@ -2611,16 +2650,16 @@ "message": "Permissão não fornecida" }, "nativeMessaginPermissionErrorDesc": { - "message": "Sem a permissão para se comunicar com o Aplicativo Bitwarden Desktop, não podemos fornecer dados biométricos na extensão do navegador. Por favor, tente novamente." + "message": "Sem a permissão para se comunicar com o aplicativo do Bitwarden Desktop, não podemos fornecer a biometria na extensão do navegador. Tente novamente." }, "nativeMessaginPermissionSidebarTitle": { "message": "Erro ao solicitar permissão" }, "nativeMessaginPermissionSidebarDesc": { - "message": "Esta ação não pode ser feita na barra lateral. Por favor, tente novamente no pop-up ou popout." + "message": "Esta ação não pode ser feita na barra lateral. Tente novamente no pop-up." }, "personalOwnershipSubmitError": { - "message": "Devido a uma política empresarial, você não pode salvar itens no seu cofre pessoal. Altere a opção de propriedade para uma organização e escolha entre as coleções disponíveis." + "message": "Devido a uma política empresarial, você não pode salvar itens no seu cofre pessoal. Altere a opção de propriedade para uma organização e escolha entre os conjuntos disponíveis." }, "personalOwnershipPolicyInEffect": { "message": "Uma política de organização está afetando suas opções de propriedade." @@ -2629,7 +2668,7 @@ "message": "Uma política da organização bloqueou a importação de itens em seu cofre pessoal." }, "restrictCardTypeImport": { - "message": "Não é possível importar tipos de item de cartão" + "message": "Não é possível importar itens do tipo de cartão" }, "restrictCardTypeImportDesc": { "message": "Uma política definida por 1 ou mais organizações impedem que você importe cartões em seus cofres." @@ -2651,10 +2690,10 @@ "message": "O Bitwarden não irá pedir para salvar os detalhes de credencial para estes domínios. Você deve atualizar a página para que as alterações entrem em vigor." }, "excludedDomainsDescAlt": { - "message": "O Bitwarden não irá pedir para salvar os detalhes de credencial para estes domínios, em todas as contas. Você deve atualizar a página para que as alterações entrem em vigor." + "message": "O Bitwarden não irá pedir para salvar os detalhes de credencial para estes domínios, em todas as contas. Você deve recarregar a página para que as alterações entrem em vigor." }, "blockedDomainsDesc": { - "message": "O preenchimento automático e outros recursos não serão oferecidos para estes sites. Atualize a página para que as mudanças surtam efeito." + "message": "O preenchimento automático e outros recursos relacionados não serão oferecidos para estes sites. Recarregue a página para que as mudanças surtam efeito." }, "autofillBlockedNoticeV2": { "message": "O preenchimento automático está bloqueado para este site." @@ -2685,7 +2724,7 @@ "message": "Senhas em risco" }, "atRiskPasswordDescSingleOrg": { - "message": "$ORGANIZATION$ solicita que altere uma senha, pois ela está vulnerável.", + "message": "$ORGANIZATION$ está solicitando que altere uma senha, pois ela está em risco.", "placeholders": { "organization": { "content": "$1", @@ -2694,7 +2733,7 @@ } }, "atRiskPasswordsDescSingleOrgPlural": { - "message": "$ORGANIZATION$ solicita que altere $COUNT$ senhas, pois elas estão vulneráveis.", + "message": "$ORGANIZATION$ está solicitando que altere $COUNT$ senhas, pois elas estão em risco.", "placeholders": { "organization": { "content": "$1", @@ -2707,7 +2746,7 @@ } }, "atRiskPasswordsDescMultiOrgPlural": { - "message": "Suas organizações estão solicitando que altere $COUNT$ senhas porque elas estão vulneráveis.", + "message": "Suas organizações estão solicitando que altere $COUNT$ senhas porque elas estão em risco.", "placeholders": { "count": { "content": "$1", @@ -2716,7 +2755,7 @@ } }, "atRiskChangePrompt": { - "message": "Sua senha para este site está em risco. $ORGANIZATION$ solicitou alterá-la.", + "message": "Sua senha para este site está em risco. $ORGANIZATION$ solicitou que você altere ela.", "placeholders": { "organization": { "content": "$1", @@ -2787,10 +2826,10 @@ "message": "Ativar preenchimento automático" }, "turnedOnAutofill": { - "message": "Desativar preenchimento automático" + "message": "Preenchimento automático ativado" }, "dismiss": { - "message": "Dispensar" + "message": "Descartar" }, "websiteItemLabel": { "message": "Site $number$ (URI)", @@ -2814,7 +2853,7 @@ "message": "Alterações de domínios bloqueados salvas" }, "excludedDomainsSavedSuccess": { - "message": "Mudanças de domínios excluídos salvas" + "message": "Alterações de domínios excluídos salvas" }, "limitSendViews": { "message": "Limitar visualizações" @@ -2862,7 +2901,7 @@ "message": "Ocultar texto por padrão" }, "expired": { - "message": "Expirado" + "message": "Vencido" }, "passwordProtected": { "message": "Protegido por senha" @@ -2875,10 +2914,10 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { - "message": "Remover Senha" + "message": "Remover senha" }, "delete": { - "message": "Excluir" + "message": "Apagar" }, "removedPassword": { "message": "Senha removida" @@ -2898,11 +2937,11 @@ "message": "Você tem certeza que deseja remover a senha?" }, "deleteSend": { - "message": "Excluir Send", + "message": "Apagar 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?", + "message": "Você tem certeza que deseja apagar este Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { @@ -2954,7 +2993,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Devido a uma política corporativa, você só pode excluir um Send existente.", + "message": "Devido a uma política corporativa, você só pode apagar um Send existente.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { @@ -2966,7 +3005,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { - "message": "O Send estará disponível para qualquer pessoa com o link pela próxima hora.", + "message": "O Send estará disponível para qualquer pessoa com o link pela próxima 1 hora.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHours": { @@ -2980,7 +3019,7 @@ } }, "sendExpiresInDaysSingle": { - "message": "O Send estará disponível para qualquer pessoa com o link pelo próximo dia.", + "message": "O Send estará disponível para qualquer pessoa com o link pelo próximo 1 dia.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInDays": { @@ -3002,7 +3041,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogText": { - "message": "Mostrar extensão?", + "message": "Criar janela da extensão?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { @@ -3019,7 +3058,7 @@ "message": "Para escolher um arquivo usando o Safari, abra uma nova janela clicando neste banner." }, "popOut": { - "message": "Separar da janela" + "message": "Mover para janela" }, "sendFileCalloutHeader": { "message": "Antes de começar" @@ -3028,28 +3067,28 @@ "message": "A data de validade fornecida não é válida." }, "deletionDateIsInvalid": { - "message": "A data de exclusão fornecida não é válida." + "message": "A data de apagamento fornecida não é válida." }, "expirationDateAndTimeRequired": { - "message": "Uma data e hora de expiração são obrigatórias." + "message": "Uma data e hora de validade são obrigatórias." }, "deletionDateAndTimeRequired": { - "message": "Uma data e hora de exclusão são obrigatórias." + "message": "Uma data e hora de apagamento são obrigatórias." }, "dateParsingError": { - "message": "Ocorreu um erro ao salvar as suas datas de exclusão e validade." + "message": "Ocorreu um erro ao salvar as suas datas de apagamento e validade." }, "hideYourEmail": { "message": "Oculte seu endereço de e-mail dos visualizadores." }, "passwordPrompt": { - "message": "Solicitação nova de senha principal" + "message": "Resolicitar senha principal" }, "passwordConfirmation": { "message": "Confirmação de senha principal" }, "passwordConfirmationDesc": { - "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha principal para verificar sua identidade." + "message": "Esta ação está protegida. Para continuar, digite a sua senha principal novamente para verificar sua identidade." }, "emailVerificationRequired": { "message": "Verificação de e-mail necessária" @@ -3061,7 +3100,7 @@ "message": "Você precisa verificar o seu e-mail para usar este recurso. Você pode verificar seu e-mail no cofre web." }, "masterPasswordSuccessfullySet": { - "message": "Senha principal definida com sucesso" + "message": "Senha principal configurada com sucesso" }, "updatedMasterPassword": { "message": "Senha principal atualizada" @@ -3070,19 +3109,19 @@ "message": "Atualizar senha principal" }, "updateMasterPasswordWarning": { - "message": "Sua senha principal foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizá-la agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "Sua senha principal foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizá-la agora. O processo desconectará você da sessão atual, exigindo que você se conecte novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "updateWeakMasterPasswordWarning": { - "message": "A sua senha principal não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha principal agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "A sua senha principal não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha principal agora. O processo desconectará você da sessão atual, exigindo que você se conecte novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "tdeDisabledMasterPasswordRequired": { - "message": "Sua organização desativou a criptografia confiável do dispositivo. Defina uma senha principal para acessar o seu cofre." + "message": "Sua organização desativou a criptografia de dispositivo confiado. Configure uma senha principal para acessar o seu cofre." }, "resetPasswordPolicyAutoEnroll": { "message": "Inscrição automática" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha principal." + "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na reconfiguração de senha. A inscrição permitirá que os administradores da organização alterem sua senha principal." }, "selectFolder": { "message": "Selecionar pasta..." @@ -3092,11 +3131,11 @@ "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha principal.", + "message": "As permissões da sua organização foram atualizadas, exigindo que você configure uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Sua organização requer que você defina uma senha principal.", + "message": "Sua organização requer que você configure uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { @@ -3119,10 +3158,10 @@ "message": "Minutos" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Os requisitos de política empresarial foram aplicados às opções de tempo limite" + "message": "Os requisitos de política empresarial foram aplicados às suas opções de tempo limite" }, "vaultTimeoutPolicyInEffect": { - "message": "As políticas da sua organização definiram o tempo máximo de limite para o cofre como $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "message": "As políticas da sua organização configuraram o seu máximo permitido do tempo limite do cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", "placeholders": { "hours": { "content": "$1", @@ -3161,7 +3200,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "As políticas da sua organização estão afetando seu cofre tempo limite. Tempo limite máximo permitido para cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de tempo limite do seu cofre é definida como $ACTION$.", + "message": "As políticas da sua organização estão afetando o tempo limite do seu cofre. \nO tempo limite máximo permitido para o cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de tempo limite do seu cofre está configurada como $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -3178,7 +3217,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "As políticas da sua organização definiram a ação tempo limite do seu cofre para $ACTION$.", + "message": "As políticas da sua organização configuraram a ação do tempo limite do seu cofre para $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -3187,7 +3226,7 @@ } }, "vaultTimeoutTooLarge": { - "message": "O tempo limite do seu cofre excede as restrições estabelecidas por sua organização." + "message": "O tempo limite do seu cofre excede as restrições estabelecidas pela sua organização." }, "vaultExportDisabled": { "message": "Exportação de cofre indisponível" @@ -3196,10 +3235,10 @@ "message": "Uma ou mais políticas da organização impedem que você exporte seu cofre individual." }, "copyCustomFieldNameInvalidElement": { - "message": "Não foi possível identificar um elemento de formulário válido. Em vez disso, tente inspecionar o HTML." + "message": "Não é possível identificar um elemento de formulário válido. Em vez disso, tente inspecionar o HTML." }, "copyCustomFieldNameNotUnique": { - "message": "Nenhum identificador exclusivo encontrado." + "message": "Nenhum identificador único encontrado." }, "removeMasterPasswordForOrganizationUserKeyConnector": { "message": "Uma senha principal não é mais necessária para os membros da seguinte organização. Confirme o domínio abaixo com o administrador da sua organização." @@ -3208,7 +3247,7 @@ "message": "Nome da organização" }, "keyConnectorDomain": { - "message": "Domínio do Conector de Chave" + "message": "Domínio do Key Connector" }, "leaveOrganization": { "message": "Sair da organização" @@ -3229,7 +3268,7 @@ "message": "Habilitar contagem de caracteres" }, "sessionTimeout": { - "message": "Sua sessão expirou. Volte e tente entrar novamente." + "message": "Sua sessão expirou. Volte e tente se conectar novamente." }, "exportingPersonalVaultTitle": { "message": "Exportando cofre individual" @@ -3274,7 +3313,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Os itens da minhas coleções não serão incluídos.", + "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Os itens dos meus conjuntos não serão incluídos.", "placeholders": { "organization": { "content": "$1", @@ -3286,7 +3325,7 @@ "message": "Erro" }, "decryptionError": { - "message": "Erro ao descriptografar" + "message": "Erro de descriptografia" }, "errorGettingAutoFillData": { "message": "Erro ao obter dados de preenchimento automático" @@ -3295,11 +3334,11 @@ "message": "O Bitwarden não conseguiu descriptografar o(s) item(ns) do cofre listado abaixo." }, "contactCSToAvoidDataLossPart1": { - "message": "Contate o sucesso do cliente", + "message": "Contate o costumer success", "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 perca adicional dos dados.", + "message": "para evitar a perca de dados adicionais.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { @@ -3353,7 +3392,7 @@ "message": "E-mail pega-tudo" }, "catchallEmailDesc": { - "message": "Use o pega-tudo configurado na caixa de entrada do seu domínio." + "message": "Use a caixa de entrada pega-tudo configurada no seu domínio." }, "random": { "message": "Aleatório" @@ -3368,10 +3407,10 @@ "message": "Serviço" }, "forwardedEmail": { - "message": "Alias do e-mail encaminhado" + "message": "Alias de encaminhamento de e-mail" }, "forwardedEmailDesc": { - "message": "Gere um alias de e-mail com um serviço de encaminhamento externo." + "message": "Gere um alias de e-mail com um serviço externo de encaminhamento." }, "forwarderDomainName": { "message": "Domínio de e-mail", @@ -3382,7 +3421,7 @@ "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "Erro $SERVICENAME$: $ERRORMESSAGE$", + "message": "Erro do $SERVICENAME$: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -3410,7 +3449,7 @@ } }, "forwaderInvalidToken": { - "message": "Token de API $SERVICENAME$ inválido", + "message": "Token de API do $SERVICENAME$ inválido", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -3420,7 +3459,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Token de API $SERVICENAME$ inválido: $ERRORMESSAGE$", + "message": "Token de API da $SERVICENAME$ inválido: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -3468,7 +3507,7 @@ } }, "forwarderNoDomain": { - "message": "Domínio $SERVICENAME$ inválido.", + "message": "Domínio inválido do $SERVICENAME$.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -3478,7 +3517,7 @@ } }, "forwarderNoUrl": { - "message": "URL $SERVICENAME$ inválida.", + "message": "URL inválido do $SERVICENAME$.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -3488,7 +3527,7 @@ } }, "forwarderUnknownError": { - "message": "Ocorreu um erro $SERVICENAME$ desconhecido.", + "message": "Ocorreu um erro desconhecido do $SERVICENAME$.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -3518,10 +3557,10 @@ "message": "Chave da API" }, "ssoKeyConnectorError": { - "message": "Erro do conector de chave: certifique-se de que o conector de chave está disponível e funcionando corretamente." + "message": "Erro de Key Connector: certifique-se de que a Key Connector está disponível e funcionando corretamente." }, "premiumSubcriptionRequired": { - "message": "Assinatura Premium necessária" + "message": "Plano Premium necessário" }, "organizationIsDisabled": { "message": "Organização suspensa." @@ -3530,7 +3569,7 @@ "message": "Itens em organizações suspensas não podem ser acessados. Entre em contato com o proprietário da sua Organização para obter assistência." }, "loggingInTo": { - "message": "Entrando em $DOMAIN$", + "message": "Conectando-se a $DOMAIN$", "placeholders": { "domain": { "content": "$1", @@ -3548,7 +3587,7 @@ "message": "Terceiros" }, "thirdPartyServerMessage": { - "message": "Conectado à implementação de servidores terceiros, $SERVERNAME$. Verifique bugs usando o servidor oficial ou reporte-os ao servidor de terceiros.", + "message": "Conectado à uma implementação de terceiros do servidor, $SERVERNAME$. Verifique bugs usando o servidor oficial ou reporte-os ao servidor de terceiros.", "placeholders": { "servername": { "content": "$1", @@ -3566,7 +3605,7 @@ } }, "loginWithMasterPassword": { - "message": "Entrar com a senha principal" + "message": "Conectar-se com senha principal" }, "newAroundHere": { "message": "Novo por aqui?" @@ -3575,7 +3614,7 @@ "message": "Lembrar e-mail" }, "loginWithDevice": { - "message": "Entrar com dispositivo" + "message": "Conectar-se com dispositivo" }, "fingerprintPhraseHeader": { "message": "Frase biométrica" @@ -3630,7 +3669,7 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "Você negou uma tentativa de autenticação de outro dispositivo. Se era você, tente entrar com o dispositivo novamente." + "message": "Você negou uma tentativa de autenticação de outro dispositivo. Se era você, tente se conectar com o dispositivo novamente." }, "device": { "message": "Dispositivo" @@ -3705,7 +3744,7 @@ "message": "Atalho de teclado para preenchimento automático" }, "autofillLoginShortcutNotSet": { - "message": "O atalho do preenchimento automático não está definido. Altere isso nas configurações do navegador." + "message": "O atalho do preenchimento automático não está configurado. Altere isso nas configurações do navegador." }, "autofillLoginShortcutText": { "message": "O atalho de preenchimento automático é $COMMAND$. Gerencie todos os atalhos nas configurações do navegador.", @@ -3844,10 +3883,10 @@ "message": "Não é possível concluir a autenticação" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { - "message": "Você precisa entrar com um dispositivo confiável ou solicitar ao administrador que lhe atribua uma senha." + "message": "Você precisa se conectar com um dispositivo confiado ou solicitar ao administrador que lhe atribua uma senha." }, "ssoIdentifierRequired": { - "message": "Identificador de SSO da organização é necessário." + "message": "O identificador de SSO da organização é necessário." }, "creatingAccountOn": { "message": "Criando conta em" @@ -3865,7 +3904,7 @@ "message": "Nenhum e-mail?" }, "goBack": { - "message": "Voltar" + "message": "Volte" }, "toEditYourEmailAddress": { "message": "para editar o seu endereço de e-mail." @@ -3902,7 +3941,7 @@ "message": "E-mail do usuário ausente" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "E-mail de usuário ativo não encontrado. Desconectando." + "message": "E-mail do usuário ativo não encontrado. Desconectando você." }, "deviceTrusted": { "message": "Dispositivo confiado" @@ -3920,13 +3959,13 @@ "message": "Organização não foi confiada" }, "emergencyAccessTrustWarning": { - "message": "Para a segurança da sua conta, apenas confirme se você permitiu o acesso de emergência a esse usuário e se a frase biométrica dele coincide com a que é exibida em sua conta" + "message": "Para a segurança da sua conta, apenas confirme que você permitiu o acesso de emergência a esse usuário e se a frase biométrica dele coincide com a que é exibida na conta deles" }, "orgTrustWarning": { - "message": "Para a segurança da sua conta, prossiga apenas se você for um membro dessa organização, tem uma recuperação de conta ativa e a frase biométrica exibida abaixo corresponde com a da organização." + "message": "Para a segurança da sua conta, prossiga apenas se você for um membro dessa organização, tem a recuperação de conta ativa, e a frase biométrica exibida abaixo corresponde com a da organização." }, "orgTrustWarning1": { - "message": "Esta organização tem uma política empresarial que lhe inscreverá na recuperação de conta. A inscrição permitirá que os administradores da organização alterem sua senha. Prossiga somente se você reconhecer esta organização e se a frase biométrica exibida abaixo corresponde com a impressão digital da organização." + "message": "Esta organização tem uma política empresarial que lhe inscreverá na recuperação de conta. A inscrição permitirá que os administradores da organização alterem sua senha. Prossiga somente se você reconhecer esta organização e se a frase biométrica exibida abaixo corresponde com a da organização." }, "trustUser": { "message": "Confiar no usuário" @@ -3946,7 +3985,7 @@ "message": "obrigatório" }, "search": { - "message": "Pesquisar" + "message": "Buscar" }, "inputMinLength": { "message": "A entrada deve ter pelo menos $COUNT$ caracteres.", @@ -3994,7 +4033,7 @@ } }, "multipleInputEmails": { - "message": "Um ou mais e-mails são inválidos" + "message": "1 ou mais e-mails são inválidos" }, "inputTrimValidator": { "message": "A entrada não pode conter somente espaços em branco.", @@ -4013,7 +4052,7 @@ } }, "singleFieldNeedsAttention": { - "message": "Um campo precisa da sua atenção." + "message": "1 campo precisa de sua atenção." }, "multipleFieldsNeedAttention": { "message": "$COUNT$ campos precisam da sua atenção.", @@ -4056,7 +4095,7 @@ "description": "Toggling an expand/collapse state." }, "aliasDomain": { - "message": "Alias do domínio" + "message": "Domínio de alias" }, "autofillOnPageLoadSetToDefault": { "message": "O preenchimento automático ao carregar a página está usando a configuração padrão.", @@ -4102,7 +4141,7 @@ "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Desbloqueie sua conta, abre em uma nova janela", + "message": "Desbloquear sua conta, abre em uma nova janela", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { @@ -4130,7 +4169,7 @@ "description": "Button text to display in overlay when there are no matching items" }, "addNewVaultItem": { - "message": "Adicionar novo item do cofre", + "message": "Adicionar novo item no cofre", "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { @@ -4146,7 +4185,7 @@ "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Adicione um novo item de cartão no cofre, abre em uma nova janela", + "message": "Adicionar um novo item de cartão no cofre, abre em uma nova janela", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { @@ -4199,10 +4238,10 @@ "message": "Tentar novamente" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verificação necessária para esta ação. Defina um PIN para continuar." + "message": "Verificação necessária para esta ação. Configure um PIN para continuar." }, "setPin": { - "message": "Definir PIN" + "message": "Configurar PIN" }, "verifyWithBiometrics": { "message": "Verificar com biometria" @@ -4211,13 +4250,13 @@ "message": "Aguardando confirmação" }, "couldNotCompleteBiometrics": { - "message": "Não foi possível completar a biometria." + "message": "Não foi possível concluir a biometria." }, "needADifferentMethod": { "message": "Precisa de um método diferente?" }, "useMasterPassword": { - "message": "Usar a senha principal" + "message": "Usar senha principal" }, "usePin": { "message": "Usar PIN" @@ -4250,10 +4289,10 @@ "message": "A autenticação em duas etapas do Duo é necessária para sua conta." }, "popoutExtension": { - "message": "Mostrar extensão" + "message": "Criar janela da extensão" }, "launchDuo": { - "message": "Abrir o Duo" + "message": "Abrir Duo" }, "importFormatError": { "message": "Os dados não estão formatados corretamente. Verifique o seu arquivo de importação e tente novamente." @@ -4277,13 +4316,13 @@ "message": "Selecione uma pasta" }, "selectImportCollection": { - "message": "Selecione uma coleção" + "message": "Selecione um conjunto" }, "importTargetHintCollection": { - "message": "Selecione esta opção caso queira importar os conteúdos de arquivos para uma coleção" + "message": "Selecione esta opção caso queira importar os conteúdos do arquivo para um conjunto" }, "importTargetHintFolder": { - "message": "Selecione esta opção caso queira importar os conteúdos de arquivos para uma pasta" + "message": "Selecione esta opção caso queira importar os conteúdos do arquivo para uma pasta" }, "importUnassignedItemsError": { "message": "Arquivo contém itens não atribuídos." @@ -4295,7 +4334,7 @@ "message": "Selecione o arquivo de importação" }, "chooseFile": { - "message": "Escolher arquivo" + "message": "Selecionar arquivo" }, "noFileChosen": { "message": "Nenhum arquivo escolhido" @@ -4341,7 +4380,7 @@ "message": "A chave de acesso não será copiada para o item clonado. Deseja continuar clonando este item?" }, "logInWithPasskeyQuestion": { - "message": "Entrar com chave de acesso?" + "message": "Conectar-se com chave de acesso?" }, "passkeyAlreadyExists": { "message": "Uma chave de acesso já existe para este aplicativo." @@ -4356,7 +4395,7 @@ "message": "Sem credenciais correspondentes para este site" }, "searchSavePasskeyNewLogin": { - "message": "Salvar ou pesquisar chave de acesso como uma nova credencial" + "message": "Buscar ou salvar chave de acesso como nova credencial" }, "confirm": { "message": "Confirmar" @@ -4365,19 +4404,19 @@ "message": "Salvar chave de acesso" }, "savePasskeyNewLogin": { - "message": "Salvar chave de acesso como uma nova credencial" + "message": "Salvar chave de acesso como nova credencial" }, "chooseCipherForPasskeySave": { "message": "Escolha uma credencial para salvar essa chave de acesso" }, "chooseCipherForPasskeyAuth": { - "message": "Escolha uma chave para conectar-se" + "message": "Escolha uma chave de acesso para conectar-se" }, "passkeyItem": { "message": "Item de chave de acesso" }, "overwritePasskey": { - "message": "Sobrescrever chave de acesso?" + "message": "Substituir chave de acesso?" }, "overwritePasskeyAlert": { "message": "Este item já contém uma chave de acesso. Tem certeza que deseja substituir a atual?" @@ -4419,7 +4458,7 @@ "message": "Importando sua conta..." }, "lastPassMFARequired": { - "message": "Requer autenticação multifatorial do LastPass" + "message": "Autenticação multifatorial do LastPass é necessária" }, "lastPassMFADesc": { "message": "Digite sua senha única do app autenticador" @@ -4431,7 +4470,7 @@ "message": "Código" }, "lastPassMasterPassword": { - "message": "Senha principal do LastPass" + "message": "Senha mestra do LastPass" }, "lastPassAuthRequired": { "message": "Autenticação do LastPass necessária" @@ -4440,7 +4479,7 @@ "message": "Aguardando autenticação do SSO" }, "awaitingSSODesc": { - "message": "Continue autenticando-se usando as credenciais da sua empresa." + "message": "Continue conectando-se usando as credenciais da sua empresa." }, "seeDetailedInstructions": { "message": "Veja instruções detalhadas no nosso site de ajuda em", @@ -4456,7 +4495,7 @@ "message": "Tente novamente ou procure um e-mail do LastPass para verificar que é você." }, "collection": { - "message": "Coleção" + "message": "Conjunto" }, "lastPassYubikeyDesc": { "message": "Insira a YubiKey associada com a sua conta do LastPass na porta USB do seu computador, e depois toque no botão dela." @@ -4465,10 +4504,10 @@ "message": "Trocar de conta" }, "switchAccounts": { - "message": "Alternar conta" + "message": "Trocar de conta" }, "switchToAccount": { - "message": "Mudar para conta" + "message": "Trocar para a conta" }, "activeAccount": { "message": "Conta ativa" @@ -4480,7 +4519,7 @@ "message": "Contas disponíveis" }, "accountLimitReached": { - "message": "Limite de contas atingido. Saia de uma conta para adicionar outra." + "message": "Limite de contas atingido. Desconecte uma conta para adicionar outra." }, "active": { "message": "ativo" @@ -4498,7 +4537,7 @@ "message": "hospedado em" }, "useDeviceOrHardwareKey": { - "message": "Use o seu dispositivo ou chave de hardware" + "message": "Use a sua chave de dispositivo ou hardware" }, "justOnce": { "message": "Somente uma vez" @@ -4532,7 +4571,7 @@ "description": "Content for dialog which warns a user when selecting 'starts with' matching strategy as a cipher match strategy" }, "uriMatchWarningDialogLink": { - "message": "Mais sobre detecção de correspondências", + "message": "Mais sobre a detecção de correspondência", "description": "Link to match detection docs on warning dialog for advance match strategy" }, "uriAdvancedOption": { @@ -4544,7 +4583,7 @@ "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continuar para o Centro de Ajuda?", + "message": "Continuar no Centro de Ajuda?", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { @@ -4552,7 +4591,7 @@ "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": "Você pode ver e definir atalhos de extensão nas configurações do seu navegador.", + "message": "Você pode ver e configurar atalhos de extensão nas configurações do seu navegador.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { @@ -4560,7 +4599,7 @@ "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "Você pode ver e definir atalhos de extensão nas configurações do seu navegador.", + "message": "Você pode ver e configurar atalhos de extensão nas configurações do seu navegador.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { @@ -4576,11 +4615,11 @@ "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Não é possível definir o Bitwarden como o gerenciador de senhas padrão", + "message": "Não é possível configurar o Bitwarden como o gerenciador de senhas padrão", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "Você deve conceder permissões de privacidade do navegador ao Bitwarden para defini-lo como o gerenciador de senhas padrão.", + "message": "Você deve conceder permissões de privacidade do navegador ao Bitwarden para configurá-lo como o gerenciador de senhas padrão.", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { @@ -4623,16 +4662,16 @@ "message": "Itens sugeridos" }, "autofillSuggestionsTip": { - "message": "Salvar um item de credencial para este site para o preenchimento automático" + "message": "Salve um item de credencial para este site para preencher automaticamente" }, "yourVaultIsEmpty": { "message": "Seu cofre está vazio" }, "noItemsMatchSearch": { - "message": "Nenhum item corresponde à sua pesquisa" + "message": "Nenhum item corresponde à sua busca" }, "clearFiltersOrTryAnother": { - "message": "Limpe os filtros ou tente outro termo de pesquisa" + "message": "Limpe os filtros ou tente outro termo de busca" }, "copyInfoTitle": { "message": "Copiar informações - $ITEMNAME$", @@ -4675,7 +4714,7 @@ } }, "viewItemTitle": { - "message": "Visualizar item - $ITEMNAME$", + "message": "Ver item - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4685,7 +4724,7 @@ } }, "viewItemTitleWithField": { - "message": "Visualizar item - $ITEMNAME$ - $FIELD$", + "message": "Ver item - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4740,7 +4779,7 @@ "message": "Não há valores para copiar" }, "assignToCollections": { - "message": "Atribuir à coleções" + "message": "Atribuir a conjuntos" }, "copyEmail": { "message": "Copiar e-mail" @@ -4764,13 +4803,13 @@ "message": "Aparência" }, "errorAssigningTargetCollection": { - "message": "Erro ao atribuir coleção de destino." + "message": "Erro ao atribuir conjunto de destino." }, "errorAssigningTargetFolder": { "message": "Erro ao atribuir pasta de destino." }, "viewItemsIn": { - "message": "Visualizar itens em $NAME$", + "message": "Ver itens em $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4873,10 +4912,10 @@ "message": "Baixar o Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Baixe o Bitwarden em todos os dispositivos" + "message": "Baixar o Bitwarden em tudo" }, "getTheMobileApp": { - "message": "Baixar o aplicativo para dispositivos móveis" + "message": "Baixe o aplicativo para dispositivos móveis" }, "getTheMobileAppDesc": { "message": "Acesse as suas senhas em qualquer lugar com o aplicativo móvel do Bitwarden." @@ -4885,7 +4924,7 @@ "message": "Baixe o aplicativo para computador" }, "getTheDesktopAppDesc": { - "message": "Acesse o seu cofre sem um navegador e, em seguida, configure o desbloqueio com biometria para facilitar o desbloqueio tanto no aplicativo de computador quanto na extensão do navegador." + "message": "Acesse o seu cofre sem um navegador, configure o desbloqueio com biometria para facilitar o desbloqueio tanto no aplicativo de computador quanto na extensão do navegador." }, "downloadFromBitwardenNow": { "message": "Baixar em bitwarden.com agora" @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Desbloqueie relatórios, acesso de emergência, e mais recursos de segurança com o Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Organizações gratuitas não podem usar anexos" }, @@ -5056,17 +5098,17 @@ "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Entrar com chave de acesso", + "message": "Conectar-se com chave de acesso", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { "message": "Atribuir" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Apenas membros da organização com acesso a essas coleções poderão ver o item." + "message": "Apenas membros da organização com acesso a estes conjuntos poderão ver o item." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Apenas membros da organização com acesso à essas coleções poderão ver os itens." + "message": "Apenas membros da organização com acesso a estes conjuntos poderão ver os itens." }, "bulkCollectionAssignmentWarning": { "message": "Você selecionou $TOTAL_COUNT$ itens. Você não pode atualizar $READONLY_COUNT$ destes itens porque você não tem permissão de edição.", @@ -5093,7 +5135,7 @@ "message": "Rótulo do campo" }, "textHelpText": { - "message": "Utilize campos de texto para dados como questões de segurança" + "message": "Use campos de texto para dados como questões de segurança" }, "hiddenHelpText": { "message": "Use campos ocultos para dados confidenciais como uma senha" @@ -5138,7 +5180,7 @@ } }, "reorderToggleButton": { - "message": "Reordene $LABEL$. Use a tecla de seta para mover o item para cima ou para baixo.", + "message": "Reordenar $LABEL$. Use a tecla de seta para mover o item para cima ou para baixo.", "placeholders": { "label": { "content": "$1", @@ -5147,7 +5189,7 @@ } }, "reorderWebsiteUriButton": { - "message": "Reorganize a URI do site. Use as setas para mover o item para cima ou para baixo." + "message": "Reordenar a URI do site. Use as setas para mover o item para cima ou para baixo." }, "reorderFieldUp": { "message": "$LABEL$ movido para cima, posição $INDEX$ de $LENGTH$", @@ -5167,13 +5209,13 @@ } }, "selectCollectionsToAssign": { - "message": "Selecione as coleções para atribuir" + "message": "Selecione conjuntos a atribuir" }, "personalItemTransferWarningSingular": { - "message": "1 item será transferido permanentemente para a organização selecionada. Você não irá mais ser dono deste item." + "message": "1 item será transferido permanentemente para a organização selecionada. Você não irá mais ser o proprietário deste item." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ itens serão transferidos permanentemente para a organização selecionada. Você não irá mais ser dono desses itens.", + "message": "$PERSONAL_ITEMS_COUNT$ itens serão transferidos permanentemente para a organização selecionada. Você não irá mais ser o proprietário desses itens.", "placeholders": { "personal_items_count": { "content": "$1", @@ -5182,7 +5224,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item será transferido permanentemente para $ORG$. Você não irá mais ser dono deste item.", + "message": "1 item será transferido permanentemente para $ORG$. Você não irá mais ser o proprietário deste item.", "placeholders": { "org": { "content": "$1", @@ -5191,7 +5233,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ itens serão transferidos permanentemente para $ORG$. Você não irá mais ser dono desses itens.", + "message": "$PERSONAL_ITEMS_COUNT$ itens serão transferidos permanentemente para $ORG$. Você não irá mais ser o proprietário desses itens.", "placeholders": { "personal_items_count": { "content": "$1", @@ -5204,7 +5246,7 @@ } }, "successfullyAssignedCollections": { - "message": "Coleções atribuídas com sucesso" + "message": "Conjuntos atribuídos com sucesso" }, "nothingSelected": { "message": "Você não selecionou nada." @@ -5257,7 +5299,7 @@ "message": "Ações da conta" }, "showNumberOfAutofillSuggestions": { - "message": "Mostrar o número de sugestões de preenchimento automático de login no ícone da extensão" + "message": "Mostrar número de sugestões de preenchimento automático de credenciais no ícone da extensão" }, "accountAccessRequested": { "message": "Acesso à conta solicitado" @@ -5272,7 +5314,7 @@ } }, "showQuickCopyActions": { - "message": "Mostrar a opção de cópia rápida no Cofre" + "message": "Mostrar opções de cópia rápida no Cofre" }, "systemDefault": { "message": "Padrão do sistema" @@ -5347,10 +5389,10 @@ "message": "O desbloqueio por biometria está indisponível no momento." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "O desbloqueio por biometria está indisponível devido a algum erro na configuração dos arquivos de sistema." + "message": "O desbloqueio por biometria está indisponível devido a algum erro na configuração dos arquivos do sistema." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "O desbloqueio por biometria está indisponível devido a algum erro na configuração dos arquivos de sistema." + "message": "O desbloqueio por biometria está indisponível devido a algum erro na configuração dos arquivos do sistema." }, "biometricsStatusHelptextDesktopDisconnected": { "message": "O desbloqueio por biometria está indisponível porque o aplicativo de computador não está aberto." @@ -5371,23 +5413,23 @@ "message": "Desbloqueie seu cofre em segundos" }, "unlockVaultDesc": { - "message": "Você pode personalizar suas configurações de desbloqueio e tempo limite para acessar mais rapidamente seu cofre." + "message": "Você pode personalizar suas configurações de desbloqueio e tempo limite para acessar seu cofre mais rapidamente." }, "unlockPinSet": { - "message": "Desbloqueio com PIN definido" + "message": "PIN de desbloqueio configurado" }, "unlockWithBiometricSet": { - "message": "Desbloqueio com biometria definido" + "message": "Desbloqueio com biometria configurado" }, "authenticating": { "message": "Autenticando" }, "fillGeneratedPassword": { - "message": "Preencher a senha gerada", + "message": "Preencher senha gerada", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Senha regenerada", + "message": "Senha regerada", "description": "Notification message for when a password has been regenerated" }, "saveToBitwarden": { @@ -5431,11 +5473,11 @@ "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "E Comercial", + "message": "E", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterísco", + "message": "Asterisco", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { @@ -5563,7 +5605,7 @@ "message": "Digite a senha da chave SSH." }, "enterSshKeyPassword": { - "message": "Digite a senha" + "message": "Digitar senha" }, "invalidSshKey": { "message": "A chave SSH é inválida" @@ -5578,7 +5620,7 @@ "message": "Chave SSH importada com sucesso" }, "cannotRemoveViewOnlyCollections": { - "message": "Você não pode remover coleções com permissões de Somente leitura: $COLLECTIONS$", + "message": "Você não pode remover conjuntos com permissões de Apenas ver: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -5590,7 +5632,7 @@ "message": "Atualize seu aplicativo de computador" }, "updateDesktopAppOrDisableFingerprintDialogMessage": { - "message": "Para usar o desbloqueio biométrica, atualize seu aplicativo de computador ou desative o desbloqueio biométrico nas configurações do desktop." + "message": "Para usar o desbloqueio biométrico, atualize seu aplicativo de computador ou desative o desbloqueio biométrico nas configurações do desktop." }, "changeAtRiskPassword": { "message": "Alterar senhas em risco" @@ -5620,7 +5662,7 @@ "message": "Autenticação rápida e fácil" }, "quickLoginBody": { - "message": "Configure o desbloqueio por biometria e o preenchimento automático para acessar suas contas sem digitar uma única letra." + "message": "Configure o desbloqueio biométrico e o preenchimento automático para acessar suas contas sem digitar uma única letra." }, "secureUser": { "message": "Dê um up nas suas credenciais" @@ -5666,7 +5708,7 @@ "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name follows this." }, "phishingPageExplanation2": { - "message": ", uma lista de código aberto de sites de phishing conhecidos usados para roubar informações pessoais e sensíveis.", + "message": ", uma lista de código aberto de sites de phishing conhecidos usados por roubar informações pessoais e sensíveis.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this." }, "phishingPageLearnMore": { @@ -5682,16 +5724,16 @@ } }, "hasItemsVaultNudgeBodyOne": { - "message": "Preencher itens automaticamente para a página atual" + "message": "Preenche automaticamente itens para a página atual" }, "hasItemsVaultNudgeBodyTwo": { "message": "Favorite itens para acesso rápido" }, "hasItemsVaultNudgeBodyThree": { - "message": "Pesquise seu cofre por outra coisa" + "message": "Busque no cofre por outra coisa" }, "newLoginNudgeTitle": { - "message": "Seja mais rápido com o preenchimento automático" + "message": "Economize tempo com o preenchimento automático" }, "newLoginNudgeBodyOne": { "message": "Inclua um", @@ -5760,10 +5802,10 @@ "message": "Sobre esta configuração" }, "permitCipherDetailsDescription": { - "message": "O Bitwarden usará URIs de credenciais salvas para identificar qual ícone ou URL de mudança de senha deverá ser usado para melhorar sua experiência. Nenhuma informação é coletada ou salva quando você utiliza este serviço." + "message": "O Bitwarden usará URIs de credenciais salvas para identificar qual ícone ou URL de alteração de senha deverá ser usado para melhorar sua experiência. Nenhuma informação é coletada ou salva quando você utiliza este serviço." }, "noPermissionsViewPage": { - "message": "Você não tem permissão para visualizar esta página. Tente entrar com uma conta diferente." + "message": "Você não tem permissão para ver esta página. Tente se conectar com uma conta diferente." }, "wasmNotSupported": { "message": "O WebAssembly não é suportado no seu navegador ou não está ativado. O WebAssembly é necessário para utilizar o aplicativo do Bitwarden.", @@ -5783,7 +5825,7 @@ "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." }, "confirmKeyConnectorDomain": { - "message": "Confirmar domínio do Conector de Chave" + "message": "Confirmar domínio do Key Connector" }, "atRiskLoginsSecured": { "message": "Ótimo trabalho protegendo suas credenciais em risco!" @@ -5801,7 +5843,7 @@ "message": "Acesso de emergência" }, "breachMonitoring": { - "message": "Monitoramento de brechas" + "message": "Monitoramento de vazamentos" }, "andMoreFeatures": { "message": "E mais!" @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Faça upgrade para o Premium" }, - "upgradeCompleteSecurity": { - "message": "Faça upgrade para segurança completa" + "unlockAdvancedSecurity": { + "message": "Desbloqueie recursos avançados de segurança" }, - "premiumGivesMoreTools": { - "message": "O Premium te oferece mais ferramentas para se permanecer seguro, trabalhar eficientemente, e manter o controle." + "unlockAdvancedSecurityDesc": { + "message": "Um plano Premium te dá mais ferramentas para se manter seguro e em controle" }, "explorePremium": { "message": "Explorar o Premium" diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 4fd291e5c89..c03f3038f98 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Os itens arquivados são excluídos dos resultados gerais da pesquisa e das sugestões de preenchimento automático. Tem a certeza de que pretende arquivar este item?" }, + "upgradeToUseArchive": { + "message": "É necessária uma subscrição Premium para utilizar o Arquivo." + }, "edit": { "message": "Editar" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Ver tudo" }, + "showAll": { + "message": "Mostrar tudo" + }, "viewLess": { "message": "Ver menos" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Saber mais" }, + "migrationsFailed": { + "message": "Ocorreu um erro ao atualizar as definições de encriptação." + }, + "updateEncryptionSettingsTitle": { + "message": "Atualize as suas definições de encriptação" + }, + "updateEncryptionSettingsDesc": { + "message": "As novas definições de encriptação recomendadas irão melhorar a segurança da sua conta. Introduza a sua palavra-passe mestra para atualizar agora." + }, + "confirmIdentityToContinue": { + "message": "Confirme a sua identidade para continuar" + }, + "enterYourMasterPassword": { + "message": "Introduza a sua palavra-passe mestra" + }, + "updateSettings": { + "message": "Atualizar definições" + }, + "later": { + "message": "Mais tarde" + }, "authenticatorKeyTotp": { "message": "Chave de autenticação (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB de armazenamento encriptado para anexos de ficheiros." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ de armazenamento encriptado para anexos de ficheiros.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Acesso de emergência." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Ano de validade" }, + "monthly": { + "message": "mês" + }, "expiration": { "message": "Prazo de validade" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Desbloqueie relatórios, acesso de emergência e outras funcionalidades de segurança com o Premium." + }, "freeOrgsCannotUseAttachments": { "message": "As organizações gratuitas não podem utilizar anexos" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Atualizar para o Premium" }, - "upgradeCompleteSecurity": { - "message": "Atualize para obter segurança total" + "unlockAdvancedSecurity": { + "message": "Desbloqueie funcionalidades de segurança avançadas" }, - "premiumGivesMoreTools": { - "message": "O Premium oferece mais ferramentas para manter a segurança, trabalhar com eficiência e manter o controlo." + "unlockAdvancedSecurityDesc": { + "message": "Uma subscrição Premium dá-lhe ferramentas adicionais para reforçar a sua segurança e manter o controlo" }, "explorePremium": { "message": "Explorar o Premium" diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 66a5b9d796b..85db8d8a4f3 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Editare" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Aflați mai multe" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Cheie de autentificare (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB spațiu de stocare criptat pentru atașamente de fișiere." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Anul expirării" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expirare" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 1f3d7c7234f..58f2b372459 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Архивированные элементы исключены из общих результатов поиска и предложений автозаполнения. Вы уверены, что хотите архивировать этот элемент?" }, + "upgradeToUseArchive": { + "message": "Для использования архива требуется премиум-статус." + }, "edit": { "message": "Изменить" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Посмотреть все" }, + "showAll": { + "message": "Показать все" + }, "viewLess": { "message": "Свернуть" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Подробнее" }, + "migrationsFailed": { + "message": "Произошла ошибка при обновлении настроек шифрования." + }, + "updateEncryptionSettingsTitle": { + "message": "Обновите настройки шифрования" + }, + "updateEncryptionSettingsDesc": { + "message": "Новые рекомендуемые настройки шифрования повысят безопасность вашего аккаунта. Введите мастер-пароль, чтобы обновить сейчас." + }, + "confirmIdentityToContinue": { + "message": "Подтвердите вашу личность, чтобы продолжить" + }, + "enterYourMasterPassword": { + "message": "Введите ваш мастер-пароль" + }, + "updateSettings": { + "message": "Обновить настройки" + }, + "later": { + "message": "Позже" + }, "authenticatorKeyTotp": { "message": "Ключ аутентификатора (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 ГБ зашифрованного хранилища для вложенных файлов." }, + "premiumSignUpStorageV2": { + "message": "Зашифрованного хранилища для вложенных файлов: $SIZE$", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Экстренный доступ" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Год" }, + "monthly": { + "message": "месяц" + }, "expiration": { "message": "Срок действия" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Премиум" }, + "unlockFeaturesWithPremium": { + "message": "Разблокируйте отчеты, экстренный доступ и другие функции безопасности с помощью Премиум." + }, "freeOrgsCannotUseAttachments": { "message": "Бесплатные организации не могут использовать вложения" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Обновить до Премиум" }, - "upgradeCompleteSecurity": { - "message": "Перейти для полной защищенности" + "unlockAdvancedSecurity": { + "message": "Разблокировать дополнительные функции безопасности" }, - "premiumGivesMoreTools": { - "message": "Премиум предоставит вам больше инструментов для обеспечения безопасности, эффективной работы и контроля над ситуацией." + "unlockAdvancedSecurityDesc": { + "message": "Премиум-подписка дает вам больше возможностей для обеспечения безопасности и контроля" }, "explorePremium": { "message": "Познакомиться с Премиум" diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 61dc029754a..ead646d0ac0 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "සංස්කරණය" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "වැඩිදුර ඉගෙන ගන්න" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "සත්යාපන යතුර (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "ගොනු ඇමුණුම් සඳහා 1 GB සංකේතාත්මක ගබඩා." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "කල් ඉකුත්වන වසර" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "කල් ඉකුත්" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 865e832fda3..70620e6c5e5 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archivované položky sú vylúčené zo všeobecného vyhľadávania a z návrhov automatického vypĺňania. Naozaj chcete archivovať túto položku?" }, + "upgradeToUseArchive": { + "message": "Na použitie archívu je potrebné prémiové členstvo." + }, "edit": { "message": "Upraviť" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Zobraziť všetky" }, + "showAll": { + "message": "Zobraziť všetko" + }, "viewLess": { "message": "Zobraziť menej" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Zistiť viac" }, + "migrationsFailed": { + "message": "Pri aktualizácii nastavení šifrovania došlo k chybe." + }, + "updateEncryptionSettingsTitle": { + "message": "Aktualizujte nastavenie šifrovania" + }, + "updateEncryptionSettingsDesc": { + "message": "Nové odporúčané nastavenia šifrovania zlepšia bezpečnosť vášho účtu. Ak ich chcete aktualizovať teraz, zadajte hlavné heslo." + }, + "confirmIdentityToContinue": { + "message": "Ak chcete pokračovať, potvrďte svoju identitu" + }, + "enterYourMasterPassword": { + "message": "Zadajte hlavné heslo" + }, + "updateSettings": { + "message": "Aktualizovať nastavenia" + }, + "later": { + "message": "Neskôr" + }, "authenticatorKeyTotp": { "message": "Overovací kľúč (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB šifrovaného úložiska na prílohy." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ šifrovaného úložiska na prílohy.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Núdzový prístup." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Rok exspirácie" }, + "monthly": { + "message": "mesačne" + }, "expiration": { "message": "Expirácia" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Prémium" }, + "unlockFeaturesWithPremium": { + "message": "Odomknite reportovanie, núdzový prístup a ďalšie bezpečnostné funkcie s predplatným Prémium." + }, "freeOrgsCannotUseAttachments": { "message": "Bezplatné organizácie nemôžu používať prílohy" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgradovať na Prémium" }, - "upgradeCompleteSecurity": { - "message": "Upgradovať pre úplné zabezpečenie" + "unlockAdvancedSecurity": { + "message": "Odomknutie pokročilých funkcií zabezpečenia" }, - "premiumGivesMoreTools": { - "message": "Predplatné Prémium vám poskytuje viac nástrojov na zabezpečenie, efektívnu prácu a kontrolu." + "unlockAdvancedSecurityDesc": { + "message": "Predplatné Prémium vám poskytne viac nástrojov na zabezpečenie a kontrolu" }, "explorePremium": { "message": "Preskúmať Prémium" diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index ebb245290f9..4867f4b89f2 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Uredi" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Več o tem" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Ključ avtentikatorja (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB šifriranega prostora za shrambo podatkov." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Leto poteka" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Veljavna do" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index d54a6ba928f..c04c113caca 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Архивиране ставке су искључене из општих резултата претраге и предлога за ауто попуњавање. Јесте ли сигурни да желите да архивирате ову ставку?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Уреди" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Прегледај све" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Сазнај више" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Једнократни код" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1ГБ шифровано складиште за прилоге." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Хитан приступ." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Година истека" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Истек" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Премијум" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Бесплатне организације не могу да користе прилоге" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Надоградите на Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 245692a27aa..a6cf12541fd 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Arkiverade objekt är exkluderade från allmänna sökresultat och förslag för autofyll. Är du säker på att du vill arkivera detta objekt?" }, + "upgradeToUseArchive": { + "message": "Ett premium-medlemskap krävs för att använda Arkiv." + }, "edit": { "message": "Redigera" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Visa alla" }, + "showAll": { + "message": "Visa alla" + }, "viewLess": { "message": "Visa mindre" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Läs mer" }, + "migrationsFailed": { + "message": "Ett fel inträffade när krypteringsinställningarna skulle uppdateras." + }, + "updateEncryptionSettingsTitle": { + "message": "Uppdatera dina krypteringsinställningar" + }, + "updateEncryptionSettingsDesc": { + "message": "De nya rekommenderade krypteringsinställningarna kommer att förbättra säkerheten för ditt konto. Ange ditt huvudlösenord för att uppdatera nu." + }, + "confirmIdentityToContinue": { + "message": "Bekräfta din identitet för att fortsätta" + }, + "enterYourMasterPassword": { + "message": "Ange ditt huvudlösenord" + }, + "updateSettings": { + "message": "Uppdatera inställningar" + }, + "later": { + "message": "Senare" + }, "authenticatorKeyTotp": { "message": "Autentiseringsnyckel (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB lagring av krypterade filer." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ krypterad lagring för filbilagor.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Nödåtkomst." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Utgångsår" }, + "monthly": { + "message": "månad" + }, "expiration": { "message": "Utgång" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Lås upp rapportering, nödåtkomst och fler säkerhetsfunktioner med Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Fria organisationer kan inte använda bilagor" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Uppgradera till Premium" }, - "upgradeCompleteSecurity": { - "message": "Uppgradera för fullständig säkerhet" + "unlockAdvancedSecurity": { + "message": "Lås upp avancerade säkerhetsfunktioner" }, - "premiumGivesMoreTools": { - "message": "Premium ger dig fler verktyg för att hålla dig säker, arbeta effektivt och ha kontroll." + "unlockAdvancedSecurityDesc": { + "message": "En Premium-prenumeration ger dig fler verktyg för att hålla dig säker och ha kontroll" }, "explorePremium": { "message": "Utforska Premium" diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index a6e2ad0ee31..934cb8e1a01 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "திருத்து" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "மேலும் அறிக" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "அங்கீகரிப்பு விசை (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "கோப்பு இணைப்புகளுக்கு 1 GB என்க்ரிப்ட் செய்யப்பட்ட ஸ்டோரேஜ்." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "அவசர அணுகல்." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "காலாவதி ஆண்டு" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "காலாவதி" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "பிரீமியம்" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "இலவச நிறுவனங்கள் இணைப்புகளைப் பயன்படுத்த முடியாது" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 7c4dbaf85dc..684a04d9175 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Expiration" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index ff0c05a470a..bade59ad99b 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "แก้ไข" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "เรียนรู้เพิ่มเติม" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Authenticator Key (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 GB of encrypted file storage." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Emergency access." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Expiration Year" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "วันหมดอายุ" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index b2bff83e8a9..0ba7e2ffec3 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Arşivlenmiş kayıtlar genel arama sonuçları ve otomatik doldurma önerilerinden hariç tutulur. Bu kaydı arşivlemek istediğine emin misin?" }, + "upgradeToUseArchive": { + "message": "Arşivi kullanmak için premium üyelik gereklidir." + }, "edit": { "message": "Düzenle" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Tümünü göster" }, + "showAll": { + "message": "Tümünü göster" + }, "viewLess": { "message": "Daha az göster" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Daha fazla bilgi al" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Kimlik doğrulama anahtarı (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "Dosya ekleri için 1 GB şifrelenmiş depolama." }, + "premiumSignUpStorageV2": { + "message": "Dosya ekleri için $SIZE$ şifrelenmiş depolama.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Acil durum erişimi." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Son kullanma yılı" }, + "monthly": { + "message": "ay" + }, "expiration": { "message": "Son kullanma tarihi" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Ücretsiz kuruluşlar dosya eklerini kullanamaz" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Premium'a yükselt" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Gelişmiş güvenlik özelliklerinin kilidini açın" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "Premium abonelik size daha fazla güvenlik ve kontrol olanağı sunan ek araçlara erişmenizi sağlar" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index b104f845fa7..147e63cce02 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Архівовані записи виключаються з результатів звичайного пошуку та пропозицій автозаповнення. Ви дійсно хочете архівувати цей запис?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Змінити" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "Переглянути все" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Докладніше" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Ключ автентифікації (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1 ГБ зашифрованого сховища для файлів." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Екстрений доступ." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Рік завершення" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "Термін дії" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Організації без передплати не можуть використовувати вкладення" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "Покращити до Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 242b779ca26..a94a38ffb63 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Các mục đã lưu trữ sẽ bị loại khỏi kết quả tìm kiếm chung và gợi ý tự động điền. Bạn có chắc chắn muốn lưu trữ mục này không?" }, + "upgradeToUseArchive": { + "message": "Cần là thành viên premium để sử dụng Archive." + }, "edit": { "message": "Sửa" }, @@ -594,8 +597,11 @@ "viewAll": { "message": "Xem tất cả" }, + "showAll": { + "message": "Hiện tất cả" + }, "viewLess": { - "message": "View less" + "message": "Ẩn bớt" }, "viewLogin": { "message": "Xem đăng nhập" @@ -800,10 +806,10 @@ "message": "Mỗi khi khóa máy" }, "onIdle": { - "message": "On system idle" + "message": "Khi hệ thống rãnh rỗi" }, "onSleep": { - "message": "On system sleep" + "message": "Khi hệ thống ngủ" }, "onRestart": { "message": "Mỗi khi khởi động lại trình duyệt" @@ -1400,6 +1406,27 @@ "learnMore": { "message": "Tìm hiểu thêm" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "Khóa xác thực (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "1GB bộ nhớ lưu trữ được mã hóa cho các tệp đính kèm." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ bộ nhớ lưu trữ được mã hóa cho các tệp đính kèm.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "Truy cập khẩn cấp." }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "Năm hết hạn" }, + "monthly": { + "message": "tháng" + }, "expiration": { "message": "Hết hạn" }, @@ -2437,7 +2476,7 @@ } }, "topLayerHijackWarning": { - "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + "message": "Trang này đang cản trở trải nghiệm Bitwarden. Menu nội tuyến Bitwarden đã tạm thời bị vô hiệu hóa như một biện pháp an toàn." }, "setMasterPassword": { "message": "Đặt mật khẩu chính" @@ -4902,6 +4941,9 @@ "premium": { "message": "Cao cấp" }, + "unlockFeaturesWithPremium": { + "message": "Mở khóa tính năng báo cáo, quyền truy cập khẩn cấp và nhiều tính năng bảo mật khác với Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Các tổ chức miễn phí không thể sử dụng tệp đính kèm" }, @@ -4987,7 +5029,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "Mặc định ( $VALUE$ )", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5812,20 +5854,20 @@ "upgradeToPremium": { "message": "Nâng cấp lên gói Cao cấp" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Mở khóa các tính năng bảo mật nâng cao" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "Đăng ký Premium cung cấp cho bạn nhiều công cụ hơn để luôn an toàn và kiểm soát" }, "explorePremium": { - "message": "Explore Premium" + "message": "Khám phá Premium" }, "loadingVault": { - "message": "Loading vault" + "message": "Đang tải kho" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "Đã tải kho" }, "settingDisabledByPolicy": { "message": "Cài đặt này bị vô hiệu hóa bởi chính sách tổ chức của bạn.", @@ -5838,6 +5880,6 @@ "message": "Số thẻ" }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Hành động sau khi đóng kho" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index cf1664b6a6f..fd4b6380b1b 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -309,7 +309,7 @@ "message": "前往浏览器扩展商店吗?" }, "continueToBrowserExtensionStoreDesc": { - "message": "帮助别人了解 Bitwarden 是否适合他们。立即访问浏览器的扩展程序商店并留下评分。" + "message": "帮助别人了解 Bitwarden 是否适合他们。立即访问浏览器扩展商店并留下评分。" }, "changeMasterPasswordOnWebConfirmation": { "message": "您可以在 Bitwarden 网页 App 上更改您的主密码。" @@ -362,10 +362,10 @@ "message": "使用 Passwordless.dev 摆脱传统密码束缚,打造流畅且安全的登录体验。访问 bitwarden.com 网站了解更多信息。" }, "freeBitwardenFamilies": { - "message": "免费 Bitwarden 家庭" + "message": "免费的 Bitwarden 家庭版" }, "freeBitwardenFamiliesPageDesc": { - "message": "您有资格获得免费的 Bitwarden 家庭。立即在网页 App 中兑换此优惠。" + "message": "您有资格获得免费的 Bitwarden 家庭版。立即在网页 App 中兑换此优惠。" }, "version": { "message": "版本" @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "已归档的项目将被排除在一般搜索结果和自动填充建议之外。确定要归档此项目吗?" }, + "upgradeToUseArchive": { + "message": "需要高级会员才能使用归档。" + }, "edit": { "message": "编辑" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "查看全部" }, + "showAll": { + "message": "显示全部" + }, "viewLess": { "message": "查看更少" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "进一步了解" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "验证器密钥 (TOTP)" }, @@ -1467,7 +1494,16 @@ "message": "注册高级会员将获得:" }, "ppremiumSignUpStorage": { - "message": "1 GB 文件附件加密存储。" + "message": "1 GB 文件附件加密存储空间。" + }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ 文件附件加密存储空间。", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } }, "premiumSignUpEmergency": { "message": "紧急访问。" @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "过期年份" }, + "monthly": { + "message": "月" + }, "expiration": { "message": "有效期" }, @@ -3002,7 +3041,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogText": { - "message": "弹出扩展?", + "message": "弹出扩展吗?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { @@ -4885,7 +4924,7 @@ "message": "获取桌面 App" }, "getTheDesktopAppDesc": { - "message": "无需使用浏览器访问您的密码库,然后在桌面 App 和浏览器扩展中同时设置生物识别解锁,即可实现快速解锁。" + "message": "无需使用浏览器访问您的密码库。在桌面 App 和浏览器扩展中同时设置生物识别解锁,即可实现快速解锁。" }, "downloadFromBitwardenNow": { "message": "立即从 bitwarden.com 下载" @@ -4902,6 +4941,9 @@ "premium": { "message": "高级版" }, + "unlockFeaturesWithPremium": { + "message": "使用高级版解锁报告、紧急访问以及更多安全功能。" + }, "freeOrgsCannotUseAttachments": { "message": "免费组织无法使用附件" }, @@ -5760,7 +5802,7 @@ "message": "关于此设置" }, "permitCipherDetailsDescription": { - "message": "Bitwarden 将使用已保存的登录 URI 来识别应使用哪个图标或更改密码的 URL 来改善您的体验。当您使用此服务时,不会收集或保存任何信息。" + "message": "Bitwarden 将使用已保存的登录 URI 来确定应使用的图标或更改密码的 URL,以提升您的使用体验。使用此服务时不会收集或保存任何信息。" }, "noPermissionsViewPage": { "message": "您没有查看此页面的权限。请尝试使用其他账户登录。" @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "升级为高级版" }, - "upgradeCompleteSecurity": { - "message": "升级以获得全面的安全防护" + "unlockAdvancedSecurity": { + "message": "解锁高级安全功能" }, - "premiumGivesMoreTools": { - "message": "高级版为您提供更多工具,助您保障安全、高效工作并掌控一切。" + "unlockAdvancedSecurityDesc": { + "message": "高级版订阅为您提供更多工具,助您保持安全并掌控一切" }, "explorePremium": { "message": "探索高级版" diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 0ef20edce81..de78e415fa6 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "封存的項目將不會出現在一般搜尋結果或自動填入建議中。確定要封存此項目嗎?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "編輯" }, @@ -594,6 +597,9 @@ "viewAll": { "message": "檢視全部" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "顯示較少" }, @@ -1400,6 +1406,27 @@ "learnMore": { "message": "深入了解" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, + "later": { + "message": "Later" + }, "authenticatorKeyTotp": { "message": "驗證器金鑰 (TOTP)" }, @@ -1469,6 +1496,15 @@ "ppremiumSignUpStorage": { "message": "用於檔案附件的 1 GB 加密儲存空間。" }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpEmergency": { "message": "緊急存取" }, @@ -1867,6 +1903,9 @@ "expirationYear": { "message": "逾期年份" }, + "monthly": { + "message": "month" + }, "expiration": { "message": "逾期" }, @@ -4902,6 +4941,9 @@ "premium": { "message": "進階版" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "免費組織無法使用附檔" }, @@ -5812,11 +5854,11 @@ "upgradeToPremium": { "message": "升級到 Premium" }, - "upgradeCompleteSecurity": { - "message": "升級以獲得完整的安全防護" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "進階版提供更多工具,協助您維持安全、高效工作並保持掌控。" + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "探索進階版" 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 2e2440f6258..7ab55f36753 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 @@ -2,7 +2,7 @@
`; - const shadowRoot = document.getElementById("shadow-root").attachShadow({ mode: "open" }); + const shadowRoot = document.getElementById("shadow-root")!.attachShadow({ mode: "open" }); shadowRoot.innerHTML = ` `; @@ -1668,7 +1668,7 @@ describe("AutofillOverlayContentService", () => { pageDetailsMock, ); await flushPromises(); - buttonElement.dispatchEvent(new KeyboardEvent("keyup", { code: "Enter" })); + buttonElement?.dispatchEvent(new KeyboardEvent("keyup", { code: "Enter" })); expect(sendExtensionMessageSpy).toHaveBeenCalledWith( "formFieldSubmitted", @@ -1716,6 +1716,85 @@ describe("AutofillOverlayContentService", () => { }); }); + describe("refreshMenuLayerPosition", () => { + it("calls refreshTopLayerPosition on the inline menu content service", () => { + autofillOverlayContentService.refreshMenuLayerPosition(); + + expect(inlineMenuContentService.refreshTopLayerPosition).toHaveBeenCalled(); + }); + + it("does not throw if inline menu content service is not available", () => { + const serviceWithoutInlineMenu = new AutofillOverlayContentService( + domQueryService, + domElementVisibilityService, + inlineMenuFieldQualificationService, + ); + + expect(() => serviceWithoutInlineMenu.refreshMenuLayerPosition()).not.toThrow(); + }); + }); + + describe("getOwnedInlineMenuTagNames", () => { + it("returns tag names from the inline menu content service", () => { + inlineMenuContentService.getOwnedTagNames.mockReturnValue(["div", "span"]); + + const result = autofillOverlayContentService.getOwnedInlineMenuTagNames(); + + expect(result).toEqual(["div", "span"]); + }); + + it("returns an empty array if inline menu content service is not available", () => { + const serviceWithoutInlineMenu = new AutofillOverlayContentService( + domQueryService, + domElementVisibilityService, + inlineMenuFieldQualificationService, + ); + + const result = serviceWithoutInlineMenu.getOwnedInlineMenuTagNames(); + + expect(result).toEqual([]); + }); + }); + + describe("getUnownedTopLayerItems", () => { + it("returns unowned top layer items from the inline menu content service", () => { + const mockElements = document.querySelectorAll("div"); + inlineMenuContentService.getUnownedTopLayerItems.mockReturnValue(mockElements); + + const result = autofillOverlayContentService.getUnownedTopLayerItems(true); + + expect(result).toEqual(mockElements); + expect(inlineMenuContentService.getUnownedTopLayerItems).toHaveBeenCalledWith(true); + }); + + it("returns undefined if inline menu content service is not available", () => { + const serviceWithoutInlineMenu = new AutofillOverlayContentService( + domQueryService, + domElementVisibilityService, + inlineMenuFieldQualificationService, + ); + + const result = serviceWithoutInlineMenu.getUnownedTopLayerItems(); + + expect(result).toBeUndefined(); + }); + }); + + describe("clearUserFilledFields", () => { + it("deletes all user filled fields", () => { + const mockElement1 = document.createElement("input") as FillableFormFieldElement; + const mockElement2 = document.createElement("input") as FillableFormFieldElement; + autofillOverlayContentService["userFilledFields"] = { + username: mockElement1, + password: mockElement2, + }; + + autofillOverlayContentService.clearUserFilledFields(); + + expect(autofillOverlayContentService["userFilledFields"]).toEqual({}); + }); + }); + describe("handleOverlayRepositionEvent", () => { const repositionEvents = [EVENTS.SCROLL, EVENTS.RESIZE]; repositionEvents.forEach((repositionEvent) => { @@ -2049,7 +2128,7 @@ describe("AutofillOverlayContentService", () => { }); it("skips focusing an element if no recently focused field exists", async () => { - autofillOverlayContentService["mostRecentlyFocusedField"] = undefined; + (autofillOverlayContentService as any)["mostRecentlyFocusedField"] = null; sendMockExtensionMessage({ command: "redirectAutofillInlineMenuFocusOut", @@ -2149,7 +2228,6 @@ describe("AutofillOverlayContentService", () => { }); it("returns null if the sub frame URL cannot be parsed correctly", async () => { - delete globalThis.location; globalThis.location = { href: "invalid-base" } as Location; sendMockExtensionMessage( { 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 6f2c00a4dd4..367599f7ad0 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.ts @@ -1400,7 +1400,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ this.intersectionObserver = new IntersectionObserver(this.handleFormElementIntersection, { root: null, rootMargin: "0px", - threshold: 1.0, + threshold: 0.9999, // Safari doesn't seem to function properly with a threshold of 1, }); } 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 f7c46a9fa77..65f9eee1ecb 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 @@ -16,9 +16,7 @@ import { } from "./autofill-constants"; import AutofillService from "./autofill.service"; -export class InlineMenuFieldQualificationService - implements InlineMenuFieldQualificationServiceInterface -{ +export class InlineMenuFieldQualificationService implements InlineMenuFieldQualificationServiceInterface { private searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames); private excludedAutofillFieldTypesSet = new Set(AutoFillConstants.ExcludedAutofillLoginTypes); private usernameFieldTypes = new Set(["text", "email", "number", "tel"]); @@ -945,7 +943,8 @@ export class InlineMenuFieldQualificationService !fieldType || !this.usernameFieldTypes.has(fieldType) || this.isExcludedFieldType(field, this.excludedAutofillFieldTypesSet) || - this.fieldHasDisqualifyingAttributeValue(field) + this.fieldHasDisqualifyingAttributeValue(field) || + this.isTotpField(field) ) { return false; } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 78b5e323798..2540571abb0 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -297,6 +297,7 @@ import { SafariApp } from "../browser/safariApp"; import { PhishingDataService } from "../dirt/phishing-detection/services/phishing-data.service"; import { PhishingDetectionService } from "../dirt/phishing-detection/services/phishing-detection.service"; import { BackgroundBrowserBiometricsService } from "../key-management/biometrics/background-browser-biometrics.service"; +import { BrowserSessionTimeoutTypeService } from "../key-management/session-timeout/services/browser-session-timeout-type.service"; import VaultTimeoutService from "../key-management/vault-timeout/vault-timeout.service"; import { BrowserActionsService } from "../platform/actions/browser-actions.service"; import { DefaultBadgeBrowserApi } from "../platform/badge/badge-browser-api"; @@ -732,7 +733,15 @@ export default class MainBackground { this.singleUserStateProvider, ); this.organizationService = new DefaultOrganizationService(this.stateProvider); - this.policyService = new DefaultPolicyService(this.stateProvider, this.organizationService); + this.policyService = new DefaultPolicyService( + this.stateProvider, + this.organizationService, + this.accountService, + ); + + const sessionTimeoutTypeService = new BrowserSessionTimeoutTypeService( + this.platformUtilsService, + ); this.vaultTimeoutSettingsService = new DefaultVaultTimeoutSettingsService( this.accountService, @@ -745,6 +754,7 @@ export default class MainBackground { this.stateProvider, this.logService, VaultTimeoutStringType.OnRestart, // default vault timeout + sessionTimeoutTypeService, ); this.apiService = new ApiService( @@ -831,10 +841,7 @@ export default class MainBackground { ); this.pinService = new PinService( - this.accountService, this.encryptService, - this.kdfConfigService, - this.keyGenerationService, this.logService, this.keyService, this.sdkService, @@ -1102,7 +1109,7 @@ export default class MainBackground { this.collectionService, this.keyService, this.encryptService, - this.pinService, + this.keyGenerationService, this.accountService, this.restrictedItemTypesService, ); @@ -1110,7 +1117,7 @@ export default class MainBackground { this.individualVaultExportService = new IndividualVaultExportService( this.folderService, this.cipherService, - this.pinService, + this.keyGenerationService, this.keyService, this.encryptService, this.cryptoFunctionService, @@ -1124,7 +1131,7 @@ export default class MainBackground { this.organizationVaultExportService = new OrganizationVaultExportService( this.cipherService, this.exportApiService, - this.pinService, + this.keyGenerationService, this.keyService, this.encryptService, this.cryptoFunctionService, @@ -1196,6 +1203,7 @@ export default class MainBackground { this.webPushConnectionService, this.authRequestAnsweringService, this.configService, + this.policyService, ); this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.authService); diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 597babdc777..eba6b01fe90 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -294,19 +294,11 @@ export default class RuntimeBackground { await this.openPopup(); break; case VaultMessages.OpenAtRiskPasswords: { - if (await this.shouldRejectManyOriginMessage(msg)) { - return; - } - await this.main.openAtRisksPasswordsPage(); this.announcePopupOpen(); break; } case VaultMessages.OpenBrowserExtensionToUrl: { - if (await this.shouldRejectManyOriginMessage(msg)) { - return; - } - await this.main.openTheExtensionToPage(msg.url); this.announcePopupOpen(); break; diff --git a/apps/browser/src/billing/popup/settings/premium-v2.component.html b/apps/browser/src/billing/popup/settings/premium-v2.component.html index 47d72751af3..fea3e558057 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.html +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.html @@ -12,7 +12,7 @@
  • - {{ "ppremiumSignUpStorage" | i18n }} + {{ "premiumSignUpStorageV2" | i18n: `${storageProvidedGb} GB` }}
  • {{ "premiumSignUpTwoStepOptions" | 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 b858b74242d..0c246d734e5 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.ts +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.ts @@ -1,13 +1,14 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { CommonModule, CurrencyPipe, Location } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, OnInit } 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 { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; 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"; @@ -44,7 +45,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co SectionComponent, ], }) -export class PremiumV2Component extends BasePremiumComponent { +export class PremiumV2Component extends BasePremiumComponent implements OnInit { priceString: string; constructor( @@ -59,6 +60,7 @@ export class PremiumV2Component extends BasePremiumComponent { billingAccountProfileStateService: BillingAccountProfileStateService, toastService: ToastService, accountService: AccountService, + billingApiService: BillingApiServiceAbstraction, ) { super( i18nService, @@ -70,15 +72,18 @@ export class PremiumV2Component extends BasePremiumComponent { billingAccountProfileStateService, toastService, accountService, + billingApiService, ); - + } + async ngOnInit() { + await super.ngOnInit(); // Support old price string. Can be removed in future once all translations are properly updated. const thePrice = this.currencyPipe.transform(this.price, "$"); // Safari extension crashes due to $1 appearing in the price string ($10.00). Escape the $ to fix. const formattedPrice = this.platformUtilsService.isSafari() ? thePrice.replace("$", "$$$") : thePrice; - this.priceString = i18nService.t("premiumPriceV2", formattedPrice); + this.priceString = this.i18nService.t("premiumPriceV2", formattedPrice); if (this.priceString.indexOf("%price%") > -1) { this.priceString = this.priceString.replace("%price%", thePrice); } 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 b6e84fee31a..d803a457a81 100644 --- a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts +++ b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts @@ -48,7 +48,11 @@ export class ForegroundBrowserBiometricsService extends BiometricsService { result: BiometricsStatus; error: string; }>(BiometricsCommands.GetBiometricsStatusForUser, { userId: id }); - return response.result; + if (response != null) { + return response.result; + } else { + return BiometricsStatus.DesktopDisconnected; + } } async getShouldAutopromptNow(): Promise { diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.html b/apps/browser/src/key-management/key-connector/remove-password.component.html deleted file mode 100644 index 427065e83f3..00000000000 --- a/apps/browser/src/key-management/key-connector/remove-password.component.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - @if (loading) { -
    - - {{ "loading" | i18n }} -
    - } @else { -

    {{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}

    -

    {{ "organizationName" | i18n }}:

    -

    {{ organization.name }}

    -

    {{ "keyConnectorDomain" | i18n }}:

    -

    {{ organization.keyConnectorUrl }}

    - - - - } -
    diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.ts b/apps/browser/src/key-management/key-connector/remove-password.component.ts deleted file mode 100644 index c4077a1eca9..00000000000 --- a/apps/browser/src/key-management/key-connector/remove-password.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -// FIXME (PM-22628): angular imports are forbidden in background -// eslint-disable-next-line no-restricted-imports -import { Component } from "@angular/core"; - -import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitwarden/key-management-ui"; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "app-remove-password", - templateUrl: "remove-password.component.html", - standalone: false, -}) -export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-settings-component.service.spec.ts b/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-settings-component.service.spec.ts new file mode 100644 index 00000000000..cf5d556a553 --- /dev/null +++ b/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-settings-component.service.spec.ts @@ -0,0 +1,57 @@ +import { mock } from "jest-mock-extended"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout"; +import { + VaultTimeoutNumberType, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; + +import { BrowserSessionTimeoutSettingsComponentService } from "./browser-session-timeout-settings-component.service"; + +describe("BrowserSessionTimeoutSettingsComponentService", () => { + let service: BrowserSessionTimeoutSettingsComponentService; + let mockI18nService: jest.Mocked; + let mockSessionTimeoutTypeService: jest.Mocked; + let mockPolicyService: jest.Mocked; + let mockMessagingService: jest.Mocked; + + beforeEach(() => { + mockI18nService = mock(); + mockSessionTimeoutTypeService = mock(); + mockPolicyService = mock(); + mockMessagingService = mock(); + + service = new BrowserSessionTimeoutSettingsComponentService( + mockI18nService, + mockSessionTimeoutTypeService, + mockPolicyService, + mockMessagingService, + ); + }); + + describe("onTimeoutSave", () => { + it("should call messagingService.send with 'bgReseedStorage' when timeout is Never", () => { + service.onTimeoutSave(VaultTimeoutStringType.Never); + + expect(mockMessagingService.send).toHaveBeenCalledWith("bgReseedStorage"); + }); + + it.each([ + VaultTimeoutNumberType.Immediately, + VaultTimeoutNumberType.OnMinute, + VaultTimeoutNumberType.EightHours, + VaultTimeoutStringType.OnIdle, + VaultTimeoutStringType.OnSleep, + VaultTimeoutStringType.OnLocked, + VaultTimeoutStringType.OnRestart, + VaultTimeoutStringType.Custom, + ])("should not call messagingService.send when timeout is %s", (timeoutValue) => { + service.onTimeoutSave(timeoutValue); + + expect(mockMessagingService.send).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-settings-component.service.ts b/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-settings-component.service.ts index 297718687eb..24925e25e24 100644 --- a/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-settings-component.service.ts +++ b/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-settings-component.service.ts @@ -1,56 +1,24 @@ -import { defer, Observable, of } from "rxjs"; - +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout"; import { VaultTimeout, - VaultTimeoutOption, VaultTimeoutStringType, } from "@bitwarden/common/key-management/vault-timeout"; 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 { SessionTimeoutSettingsComponentService } from "@bitwarden/key-management-ui"; -export class BrowserSessionTimeoutSettingsComponentService - implements SessionTimeoutSettingsComponentService -{ - availableTimeoutOptions$: Observable = defer(() => { - const options: VaultTimeoutOption[] = [ - { name: this.i18nService.t("immediately"), value: 0 }, - { name: this.i18nService.t("oneMinute"), value: 1 }, - { name: this.i18nService.t("fiveMinutes"), value: 5 }, - { name: this.i18nService.t("fifteenMinutes"), value: 15 }, - { name: this.i18nService.t("thirtyMinutes"), value: 30 }, - { name: this.i18nService.t("oneHour"), value: 60 }, - { name: this.i18nService.t("fourHours"), value: 240 }, - ]; - - const showOnLocked = - !this.platformUtilsService.isFirefox() && - !this.platformUtilsService.isSafari() && - !(this.platformUtilsService.isOpera() && navigator.platform === "MacIntel"); - - if (showOnLocked) { - options.push({ - name: this.i18nService.t("onLocked"), - value: VaultTimeoutStringType.OnLocked, - }); - } - - options.push( - { name: this.i18nService.t("onRestart"), value: VaultTimeoutStringType.OnRestart }, - { name: this.i18nService.t("never"), value: VaultTimeoutStringType.Never }, - ); - - return of(options); - }); - +export class BrowserSessionTimeoutSettingsComponentService extends SessionTimeoutSettingsComponentService { constructor( - private readonly i18nService: I18nService, - private readonly platformUtilsService: PlatformUtilsService, + i18nService: I18nService, + sessionTimeoutTypeService: SessionTimeoutTypeService, + policyService: PolicyService, private readonly messagingService: MessagingService, - ) {} + ) { + super(i18nService, sessionTimeoutTypeService, policyService); + } - onTimeoutSave(timeout: VaultTimeout): void { + override onTimeoutSave(timeout: VaultTimeout): void { if (timeout === VaultTimeoutStringType.Never) { this.messagingService.send("bgReseedStorage"); } diff --git a/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-type.service.spec.ts b/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-type.service.spec.ts new file mode 100644 index 00000000000..83de5c51a4a --- /dev/null +++ b/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-type.service.spec.ts @@ -0,0 +1,139 @@ +import { mock } from "jest-mock-extended"; + +import { + VaultTimeoutNumberType, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { BrowserSessionTimeoutTypeService } from "./browser-session-timeout-type.service"; + +describe("BrowserSessionTimeoutTypeService", () => { + let service: BrowserSessionTimeoutTypeService; + let mockPlatformUtilsService: jest.Mocked; + + beforeEach(() => { + mockPlatformUtilsService = mock(); + service = new BrowserSessionTimeoutTypeService(mockPlatformUtilsService); + }); + + describe("isAvailable", () => { + it.each([ + VaultTimeoutNumberType.Immediately, + VaultTimeoutStringType.OnRestart, + VaultTimeoutStringType.Never, + VaultTimeoutStringType.Custom, + ])("should return true for always available type: %s", async (timeoutType) => { + const result = await service.isAvailable(timeoutType); + + expect(result).toBe(true); + }); + + it.each([VaultTimeoutNumberType.OnMinute, VaultTimeoutNumberType.EightHours])( + "should return true for numeric timeout type: %s", + async (timeoutType) => { + const result = await service.isAvailable(timeoutType); + + expect(result).toBe(true); + }, + ); + + describe("OnLocked availability", () => { + const mockNavigatorPlatform = (platform: string) => { + Object.defineProperty(navigator, "platform", { + value: platform, + writable: true, + configurable: true, + }); + }; + + beforeEach(() => { + mockNavigatorPlatform("Linux x86_64"); + mockPlatformUtilsService.isFirefox.mockReturnValue(false); + mockPlatformUtilsService.isSafari.mockReturnValue(false); + mockPlatformUtilsService.isOpera.mockReturnValue(false); + }); + + it("should return true when not Firefox, Safari, or Opera on Mac", async () => { + const result = await service.isAvailable(VaultTimeoutStringType.OnLocked); + + expect(result).toBe(true); + }); + + it("should return true when Opera on non-Mac platform", async () => { + mockNavigatorPlatform("Win32"); + mockPlatformUtilsService.isOpera.mockReturnValue(true); + + const result = await service.isAvailable(VaultTimeoutStringType.OnLocked); + + expect(result).toBe(true); + }); + + it("should return false when Opera on Mac", async () => { + mockNavigatorPlatform("MacIntel"); + mockPlatformUtilsService.isOpera.mockReturnValue(true); + + const result = await service.isAvailable(VaultTimeoutStringType.OnLocked); + + expect(result).toBe(false); + }); + + it("should return false when Firefox", async () => { + mockPlatformUtilsService.isFirefox.mockReturnValue(true); + + const result = await service.isAvailable(VaultTimeoutStringType.OnLocked); + + expect(result).toBe(false); + }); + + it("should return false when Safari", async () => { + mockPlatformUtilsService.isSafari.mockReturnValue(true); + + const result = await service.isAvailable(VaultTimeoutStringType.OnLocked); + + expect(result).toBe(false); + }); + }); + + it.each([VaultTimeoutStringType.OnIdle, VaultTimeoutStringType.OnSleep])( + "should return false for unavailable timeout type: %s", + async (timeoutType) => { + const result = await service.isAvailable(timeoutType); + + expect(result).toBe(false); + }, + ); + }); + + describe("getOrPromoteToAvailable", () => { + it.each([ + VaultTimeoutNumberType.Immediately, + VaultTimeoutNumberType.OnMinute, + VaultTimeoutStringType.Never, + VaultTimeoutStringType.OnRestart, + VaultTimeoutStringType.OnLocked, + VaultTimeoutStringType.Custom, + ])("should return the original type when it is available: %s", async (timeoutType) => { + jest.spyOn(service, "isAvailable").mockResolvedValue(true); + + const result = await service.getOrPromoteToAvailable(timeoutType); + + expect(result).toBe(timeoutType); + expect(service.isAvailable).toHaveBeenCalledWith(timeoutType); + }); + + it.each([ + VaultTimeoutStringType.OnIdle, + VaultTimeoutStringType.OnSleep, + VaultTimeoutStringType.OnLocked, + 5, + ])("should return OnRestart when type is not available: %s", async (timeoutType) => { + jest.spyOn(service, "isAvailable").mockResolvedValue(false); + + const result = await service.getOrPromoteToAvailable(timeoutType); + + expect(result).toBe(VaultTimeoutStringType.OnRestart); + expect(service.isAvailable).toHaveBeenCalledWith(timeoutType); + }); + }); +}); diff --git a/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-type.service.ts b/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-type.service.ts new file mode 100644 index 00000000000..33ac3e356d4 --- /dev/null +++ b/apps/browser/src/key-management/session-timeout/services/browser-session-timeout-type.service.ts @@ -0,0 +1,43 @@ +import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout"; +import { + isVaultTimeoutTypeNumeric, + VaultTimeout, + VaultTimeoutNumberType, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +export class BrowserSessionTimeoutTypeService implements SessionTimeoutTypeService { + constructor(private readonly platformUtilsService: PlatformUtilsService) {} + + async isAvailable(type: VaultTimeout): Promise { + switch (type) { + case VaultTimeoutNumberType.Immediately: + case VaultTimeoutStringType.OnRestart: + case VaultTimeoutStringType.Never: + case VaultTimeoutStringType.Custom: + return true; + case VaultTimeoutStringType.OnLocked: + return ( + !this.platformUtilsService.isFirefox() && + !this.platformUtilsService.isSafari() && + !(this.platformUtilsService.isOpera() && navigator.platform === "MacIntel") + ); + default: + if (isVaultTimeoutTypeNumeric(type)) { + return true; + } + break; + } + + return false; + } + + async getOrPromoteToAvailable(type: VaultTimeout): Promise { + const available = await this.isAvailable(type); + if (!available) { + return VaultTimeoutStringType.OnRestart; + } + return type; + } +} diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 3d8f648daca..1651f616e03 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2025.11.1", + "version": "2025.12.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 2b2aa0f117b..67399192b64 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2025.11.1", + "version": "2025.12.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/platform/popup/layout/popup-layout.mdx b/apps/browser/src/platform/popup/layout/popup-layout.mdx index a2725350a8f..0a1c80b13f6 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.mdx +++ b/apps/browser/src/platform/popup/layout/popup-layout.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Canvas } from "@storybook/addon-docs"; +import { Meta, Story, Canvas } from "@storybook/addon-docs/blocks"; import * as stories from "./popup-layout.stories"; diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts index 4abd9cd4803..26138d57954 100644 --- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts @@ -22,7 +22,7 @@ export type NavButton = { templateUrl: "popup-tab-navigation.component.html", imports: [CommonModule, LinkModule, RouterModule, JslibModule, IconModule], host: { - class: "tw-block tw-h-full tw-w-full tw-flex tw-flex-col", + class: "tw-block tw-size-full tw-flex tw-flex-col", }, }) export class PopupTabNavigationComponent { diff --git a/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts index abb7c6405c2..6e5218c9f27 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts @@ -120,8 +120,8 @@ export class PopupRouterCacheService { /** * Navigate back in history */ - async back() { - if (!BrowserPopupUtils.inPopup(window)) { + async back(updateCache = false) { + if (!updateCache && !BrowserPopupUtils.inPopup(window)) { this.location.back(); return; } diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts index 5a933d9715e..c3dd3adb8ce 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts @@ -329,6 +329,25 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return autofillCommand; } + async packageType(): Promise { + switch (this.getDevice()) { + case DeviceType.ChromeExtension: + return "Chrome Extension"; + case DeviceType.FirefoxExtension: + return "Firefox Extension"; + case DeviceType.OperaExtension: + return "Opera Extension"; + case DeviceType.EdgeExtension: + return "Edge Extension"; + case DeviceType.VivaldiExtension: + return "Vivaldi Extension"; + case DeviceType.SafariExtension: + return "Safari Extension"; + default: + return "Unknown Browser Extension"; + } + } + /** * Triggers the offscreen document API to copy the text to the clipboard. */ diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index a36396afa1a..eb64c076192 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -43,11 +43,16 @@ import { TwoFactorAuthGuard, } from "@bitwarden/auth/angular"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; -import { LockComponent, ConfirmKeyConnectorDomainComponent } from "@bitwarden/key-management-ui"; +import { + LockComponent, + ConfirmKeyConnectorDomainComponent, + RemovePasswordComponent, +} from "@bitwarden/key-management-ui"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; import { AuthExtensionRoute } from "../auth/popup/constants/auth-extension-route.constant"; import { fido2AuthGuard } from "../auth/popup/guards/fido2-auth.guard"; +import { platformPopoutGuard } from "../auth/popup/guards/platform-popout.guard"; import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; import { ExtensionDeviceManagementComponent } from "../auth/popup/settings/extension-device-management.component"; import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; @@ -58,7 +63,6 @@ import { NotificationsSettingsComponent } from "../autofill/popup/settings/notif import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component"; import { PhishingWarning } from "../dirt/phishing-detection/popup/phishing-warning.component"; import { ProtectedByComponent } from "../dirt/phishing-detection/popup/protected-by-component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import BrowserPopupUtils from "../platform/browser/browser-popup-utils"; import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; import { RouteCacheOptions } from "../platform/services/popup-view-cache-background.service"; @@ -187,9 +191,22 @@ const routes: Routes = [ }, { path: "remove-password", - component: RemovePasswordComponent, + component: ExtensionAnonLayoutWrapperComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, + children: [ + { + path: "", + component: RemovePasswordComponent, + data: { + pageTitle: { + key: "verifyYourOrganization", + }, + showBackButton: false, + pageIcon: LockIcon, + } satisfies ExtensionAnonLayoutWrapperData, + }, + ], }, { path: "view-cipher", @@ -414,7 +431,7 @@ const routes: Routes = [ }, { path: AuthRoute.LoginWithPasskey, - canActivate: [unauthGuardFn(unauthRouteOverrides)], + canActivate: [unauthGuardFn(unauthRouteOverrides), platformPopoutGuard(["linux"])], data: { pageIcon: TwoFactorAuthSecurityKeyIcon, pageTitle: { @@ -645,7 +662,7 @@ const routes: Routes = [ component: ConfirmKeyConnectorDomainComponent, data: { pageTitle: { - key: "confirmKeyConnectorDomain", + key: "verifyYourOrganization", }, showBackButton: true, pageIcon: DomainIcon, diff --git a/apps/browser/src/popup/app.component.html b/apps/browser/src/popup/app.component.html index 3d81354ca35..3a5c8021e17 100644 --- a/apps/browser/src/popup/app.component.html +++ b/apps/browser/src/popup/app.component.html @@ -13,8 +13,11 @@
} @else { -
- + +
+
+ +
+
- } diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 71846cc6444..d178cee2fc3 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -28,7 +28,6 @@ import { CurrentAccountComponent } from "../auth/popup/account-switching/current import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { PopOutComponent } from "../platform/popup/components/pop-out.component"; import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component"; @@ -85,13 +84,7 @@ import "../platform/popup/locales"; CalloutModule, LinkModule, ], - declarations: [ - AppComponent, - ColorPasswordPipe, - ColorPasswordCountPipe, - TabsV2Component, - RemovePasswordComponent, - ], + declarations: [AppComponent, ColorPasswordPipe, ColorPasswordCountPipe, TabsV2Component], exports: [CalloutModule], providers: [CurrencyPipe, DatePipe], bootstrap: [AppComponent], diff --git a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html index dcd0496ed30..7a1815b86ed 100644 --- a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html +++ b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html @@ -1,31 +1,21 @@ - -
- -
- - - - - -
- +
+ + @if (showAcctSwitcher && hasLoggedInAccount) { + + } +
diff --git a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index 7a30e15582c..57ef285bdf5 100644 --- a/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -238,6 +238,11 @@ export const DefaultContentExample: Story = { }, ], }), + parameters: { + chromatic: { + viewports: [380, 1280], + }, + }, }; // Dynamic Content Example diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss deleted file mode 100644 index 01b9d3f05d5..00000000000 --- a/apps/browser/src/popup/scss/base.scss +++ /dev/null @@ -1,453 +0,0 @@ -@import "variables.scss"; - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -html { - overflow: hidden; - min-height: 600px; - height: 100%; - - &.body-sm { - min-height: 500px; - } - - &.body-xs { - min-height: 400px; - } - - &.body-xxs { - min-height: 300px; - } - - &.body-3xs { - min-height: 240px; - } - - &.body-full { - min-height: unset; - width: 100%; - height: 100%; - - & body { - width: 100%; - } - } -} - -html, -body { - font-family: $font-family-sans-serif; - font-size: $font-size-base; - line-height: $line-height-base; - -webkit-font-smoothing: antialiased; -} - -body { - width: 380px; - height: 100%; - position: relative; - min-height: inherit; - overflow: hidden; - color: $text-color; - background-color: $background-color; - - @include themify($themes) { - color: themed("textColor"); - background-color: themed("backgroundColor"); - } -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: $font-family-sans-serif; - font-size: $font-size-base; - font-weight: normal; -} - -p { - margin-bottom: 10px; -} - -ul, -ol { - margin-bottom: 10px; -} - -img { - border: none; -} - -a:not(popup-page a, popup-tab-navigation a) { - text-decoration: none; - - @include themify($themes) { - color: themed("primaryColor"); - } - - &:hover, - &:focus { - @include themify($themes) { - color: darken(themed("primaryColor"), 6%); - } - } -} - -input:not(bit-form-field input, bit-search input, input[bitcheckbox]), -select:not(bit-form-field select), -textarea:not(bit-form-field textarea) { - @include themify($themes) { - color: themed("textColor"); - background-color: themed("inputBackgroundColor"); - } -} - -input:not(input[bitcheckbox]), -select, -textarea, -button:not(bit-chip-select button) { - font-size: $font-size-base; - font-family: $font-family-sans-serif; -} - -input[type*="date"] { - @include themify($themes) { - color-scheme: themed("dateInputColorScheme"); - } -} - -::-webkit-calendar-picker-indicator { - @include themify($themes) { - filter: themed("webkitCalendarPickerFilter"); - } -} - -::-webkit-calendar-picker-indicator:hover { - @include themify($themes) { - filter: themed("webkitCalendarPickerHoverFilter"); - } - cursor: pointer; -} - -select { - width: 100%; - padding: 0.35rem; -} - -button { - cursor: pointer; -} - -textarea { - resize: vertical; -} - -app-root > div { - height: 100%; - width: 100%; -} - -main::-webkit-scrollbar, -cdk-virtual-scroll-viewport::-webkit-scrollbar, -.vault-select::-webkit-scrollbar { - width: 10px; - height: 10px; -} - -main::-webkit-scrollbar-track, -.vault-select::-webkit-scrollbar-track { - background-color: transparent; -} - -cdk-virtual-scroll-viewport::-webkit-scrollbar-track { - @include themify($themes) { - background-color: themed("backgroundColor"); - } -} - -main::-webkit-scrollbar-thumb, -cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb, -.vault-select::-webkit-scrollbar-thumb { - border-radius: 10px; - margin-right: 1px; - - @include themify($themes) { - background-color: themed("scrollbarColor"); - } - - &:hover { - @include themify($themes) { - background-color: themed("scrollbarHoverColor"); - } - } -} - -header:not(bit-callout header, bit-dialog header, popup-page header) { - height: 44px; - display: flex; - - &:not(.no-theme) { - border-bottom: 1px solid #000000; - - @include themify($themes) { - color: themed("headerColor"); - background-color: themed("headerBackgroundColor"); - border-bottom-color: themed("headerBorderColor"); - } - } - - .header-content { - display: flex; - flex: 1 1 auto; - } - - .header-content > .right, - .header-content > .right > .right { - height: 100%; - } - - .left, - .right { - flex: 1; - display: flex; - min-width: -webkit-min-content; /* Workaround to Chrome bug */ - .header-icon { - margin-right: 5px; - } - } - - .right { - justify-content: flex-end; - align-items: center; - app-avatar { - max-height: 30px; - margin-right: 5px; - } - } - - .center { - display: flex; - align-items: center; - text-align: center; - min-width: 0; - } - - .login-center { - margin: auto; - } - - app-pop-out > button, - div > button:not(app-current-account button):not(.home-acc-switcher-btn), - div > a { - border: none; - padding: 0 10px; - text-decoration: none; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - height: 100%; - white-space: pre; - - &:not(.home-acc-switcher-btn):hover, - &:not(.home-acc-switcher-btn):focus { - @include themify($themes) { - background-color: themed("headerBackgroundHoverColor"); - color: themed("headerColor"); - } - } - - &[disabled] { - opacity: 0.65; - cursor: default !important; - background-color: inherit !important; - } - - i + span { - margin-left: 5px; - } - } - - app-pop-out { - display: flex; - padding-right: 0.5em; - } - - .title { - font-weight: bold; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .search { - padding: 7px 10px; - width: 100%; - text-align: left; - position: relative; - display: flex; - - .bwi { - position: absolute; - top: 15px; - left: 20px; - - @include themify($themes) { - color: themed("headerInputPlaceholderColor"); - } - } - - input:not(bit-form-field input) { - width: 100%; - margin: 0; - border: none; - padding: 5px 10px 5px 30px; - border-radius: $border-radius; - - @include themify($themes) { - background-color: themed("headerInputBackgroundColor"); - color: themed("headerInputColor"); - } - - &::selection { - @include themify($themes) { - // explicitly set text selection to invert foreground/background - background-color: themed("headerInputColor"); - color: themed("headerInputBackgroundColor"); - } - } - - &:focus { - border-radius: $border-radius; - outline: none; - - @include themify($themes) { - background-color: themed("headerInputBackgroundFocusColor"); - } - } - - &::-webkit-input-placeholder { - @include themify($themes) { - color: themed("headerInputPlaceholderColor"); - } - } - /** make the cancel button visible in both dark/light themes **/ - &[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: none; - appearance: none; - height: 15px; - width: 15px; - background-repeat: no-repeat; - mask-image: url("../images/close-button-white.svg"); - -webkit-mask-image: url("../images/close-button-white.svg"); - @include themify($themes) { - background-color: themed("headerInputColor"); - } - } - } - } - - .left + .search, - .left + .sr-only + .search { - padding-left: 0; - - .bwi { - left: 10px; - } - } - - .search + .right { - margin-left: -10px; - } -} - -.content { - padding: 15px 5px; -} - -app-root { - width: 100%; - height: 100vh; - display: flex; - - @include themify($themes) { - background-color: themed("backgroundColor"); - } -} - -main:not(popup-page main):not(auth-anon-layout main) { - position: absolute; - top: 44px; - bottom: 0; - left: 0; - right: 0; - overflow-y: auto; - overflow-x: hidden; - - @include themify($themes) { - background-color: themed("backgroundColor"); - } - - &.no-header { - top: 0; - } - - &.flex { - display: flex; - flex-flow: column; - height: calc(100% - 44px); - } -} - -.center-content, -.no-items, -.full-loading-spinner { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - flex-direction: column; - flex-grow: 1; -} - -.no-items, -.full-loading-spinner { - text-align: center; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - - .no-items-image { - @include themify($themes) { - content: url("../images/search-desktop" + themed("svgSuffix")); - } - } - - .bwi { - margin-bottom: 10px; - - @include themify($themes) { - color: themed("disabledIconColor"); - } - } -} - -// cdk-virtual-scroll -.cdk-virtual-scroll-viewport { - width: 100%; - height: 100%; - overflow-y: auto; - overflow-x: hidden; -} - -.cdk-virtual-scroll-content-wrapper { - width: 100%; -} diff --git a/apps/browser/src/popup/scss/box.scss b/apps/browser/src/popup/scss/box.scss deleted file mode 100644 index 763f73a15cb..00000000000 --- a/apps/browser/src/popup/scss/box.scss +++ /dev/null @@ -1,620 +0,0 @@ -@import "variables.scss"; - -.box { - position: relative; - width: 100%; - - &.first { - margin-top: 0; - } - - .box-header { - margin: 0 10px 5px 10px; - text-transform: uppercase; - display: flex; - - @include themify($themes) { - color: themed("headingColor"); - } - } - - .box-content { - @include themify($themes) { - background-color: themed("backgroundColor"); - border-color: themed("borderColor"); - } - - &.box-content-padded { - padding: 10px 15px; - } - - &.condensed .box-content-row, - .box-content-row.condensed { - padding-top: 5px; - padding-bottom: 5px; - } - - &.no-hover .box-content-row, - .box-content-row.no-hover { - &:hover, - &:focus { - @include themify($themes) { - background-color: themed("boxBackgroundColor") !important; - } - } - } - - &.single-line .box-content-row, - .box-content-row.single-line { - padding-top: 10px; - padding-bottom: 10px; - margin: 5px; - } - - &.row-top-padding { - padding-top: 10px; - } - } - - .box-footer { - margin: 0 5px 5px 5px; - padding: 0 10px 5px 10px; - font-size: $font-size-small; - - button.btn { - font-size: $font-size-small; - padding: 0; - } - - button.btn.primary { - font-size: $font-size-base; - padding: 7px 15px; - width: 100%; - - &:hover { - @include themify($themes) { - border-color: themed("borderHoverColor") !important; - } - } - } - - @include themify($themes) { - color: themed("mutedColor"); - } - } - - &.list { - margin: 10px 0 20px 0; - .box-content { - .virtual-scroll-item { - display: inline-block; - width: 100%; - } - - .box-content-row { - text-decoration: none; - border-radius: $border-radius; - // background-color: $background-color; - - @include themify($themes) { - color: themed("textColor"); - background-color: themed("boxBackgroundColor"); - } - - &.padded { - padding-top: 10px; - padding-bottom: 10px; - } - - &.no-hover { - &:hover { - @include themify($themes) { - background-color: themed("boxBackgroundColor") !important; - } - } - } - - &:hover, - &:focus, - &.active { - @include themify($themes) { - background-color: themed("listItemBackgroundHoverColor"); - } - } - - &:focus { - border-left: 5px solid #000000; - padding-left: 5px; - - @include themify($themes) { - border-left-color: themed("mutedColor"); - } - } - - .action-buttons { - .row-btn { - padding-left: 5px; - padding-right: 5px; - } - } - - .text:not(.no-ellipsis), - .detail { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .row-main { - display: flex; - min-width: 0; - align-items: normal; - - .row-main-content { - min-width: 0; - } - } - } - - &.single-line { - .box-content-row { - display: flex; - padding-top: 10px; - padding-bottom: 10px; - margin: 5px; - border-radius: $border-radius; - } - } - } - } -} - -.box-content-row { - display: block; - padding: 5px 10px; - position: relative; - z-index: 1; - border-radius: $border-radius; - margin: 3px 5px; - - @include themify($themes) { - background-color: themed("boxBackgroundColor"); - } - - &:last-child { - &:before { - border: none; - height: 0; - } - } - - &.override-last:last-child:before { - border-bottom: 1px solid #000000; - @include themify($themes) { - border-bottom-color: themed("boxBorderColor"); - } - } - - &.last:last-child:before { - border-bottom: 1px solid #000000; - @include themify($themes) { - border-bottom-color: themed("boxBorderColor"); - } - } - - &:after { - content: ""; - display: table; - clear: both; - } - - &:hover, - &:focus, - &.active { - @include themify($themes) { - background-color: themed("boxBackgroundHoverColor"); - } - } - - &.pre { - white-space: pre; - overflow-x: auto; - } - - &.pre-wrap { - white-space: pre-wrap; - overflow-x: auto; - } - - .row-label, - label { - font-size: $font-size-small; - display: block; - width: 100%; - margin-bottom: 5px; - - @include themify($themes) { - color: themed("mutedColor"); - } - - .sub-label { - margin-left: 10px; - } - } - - .flex-label { - font-size: $font-size-small; - display: flex; - flex-grow: 1; - margin-bottom: 5px; - - @include themify($themes) { - color: themed("mutedColor"); - } - - > a { - flex-grow: 0; - } - } - - .text, - .detail { - display: block; - text-align: left; - - @include themify($themes) { - color: themed("textColor"); - } - } - - .detail { - font-size: $font-size-small; - - @include themify($themes) { - color: themed("mutedColor"); - } - } - - .img-right, - .txt-right { - float: right; - margin-left: 10px; - } - - .row-main { - flex-grow: 1; - min-width: 0; - } - - &.box-content-row-flex, - .box-content-row-flex, - &.box-content-row-checkbox, - &.box-content-row-link, - &.box-content-row-input, - &.box-content-row-slider, - &.box-content-row-multi { - display: flex; - align-items: center; - word-break: break-all; - - &.box-content-row-word-break { - word-break: normal; - } - } - - &.box-content-row-multi { - input:not([type="checkbox"]) { - width: 100%; - } - - input + label.sr-only + select { - margin-top: 5px; - } - - > a, - > button { - padding: 8px 8px 8px 4px; - margin: 0; - - @include themify($themes) { - color: themed("dangerColor"); - } - } - } - - &.box-content-row-multi, - &.box-content-row-newmulti { - padding-left: 10px; - } - - &.box-content-row-newmulti { - @include themify($themes) { - color: themed("primaryColor"); - } - } - - &.box-content-row-checkbox, - &.box-content-row-link, - &.box-content-row-input, - &.box-content-row-slider { - padding-top: 10px; - padding-bottom: 10px; - margin: 5px; - - label, - .row-label { - font-size: $font-size-base; - display: block; - width: initial; - margin-bottom: 0; - - @include themify($themes) { - color: themed("textColor"); - } - } - - > span { - @include themify($themes) { - color: themed("mutedColor"); - } - } - - > input { - margin: 0 0 0 auto; - padding: 0; - } - - > * { - margin-right: 15px; - - &:last-child { - margin-right: 0; - } - } - } - - &.box-content-row-checkbox-left { - justify-content: flex-start; - - > input { - margin: 0 15px 0 0; - } - } - - &.box-content-row-input { - label { - white-space: nowrap; - } - - input { - text-align: right; - - &[type="number"] { - max-width: 50px; - } - } - } - - &.box-content-row-slider { - input[type="range"] { - height: 10px; - } - - input[type="number"] { - width: 45px; - } - - label { - white-space: nowrap; - } - } - - input:not([type="checkbox"]):not([type="radio"]), - textarea { - border: none; - width: 100%; - background-color: transparent !important; - - &::-webkit-input-placeholder { - @include themify($themes) { - color: themed("inputPlaceholderColor"); - } - } - - &:not([type="file"]):focus { - outline: none; - } - } - - select { - width: 100%; - border: 1px solid #000000; - border-radius: $border-radius; - padding: 7px 4px; - - @include themify($themes) { - border-color: themed("inputBorderColor"); - } - } - - .action-buttons { - display: flex; - margin-left: 5px; - - &.action-buttons-fixed { - align-self: start; - margin-top: 2px; - } - - .row-btn { - cursor: pointer; - padding: 10px 8px; - background: none; - border: none; - - @include themify($themes) { - color: themed("boxRowButtonColor"); - } - - &:hover, - &:focus { - @include themify($themes) { - color: themed("boxRowButtonHoverColor"); - } - } - - &.disabled, - &[disabled] { - @include themify($themes) { - color: themed("disabledIconColor"); - opacity: themed("disabledBoxOpacity"); - } - - &:hover { - @include themify($themes) { - color: themed("disabledIconColor"); - opacity: themed("disabledBoxOpacity"); - } - } - cursor: default !important; - } - } - - &.no-pad .row-btn { - padding-top: 0; - padding-bottom: 0; - } - } - - &:not(.box-draggable-row) { - .action-buttons .row-btn:last-child { - margin-right: -3px; - } - } - - &.box-draggable-row { - &.box-content-row-checkbox { - input[type="checkbox"] + .drag-handle { - margin-left: 10px; - } - } - } - - .drag-handle { - cursor: move; - padding: 10px 2px 10px 8px; - user-select: none; - - @include themify($themes) { - color: themed("mutedColor"); - } - } - - &.cdk-drag-preview { - position: relative; - display: flex; - align-items: center; - opacity: 0.8; - - @include themify($themes) { - background-color: themed("boxBackgroundColor"); - } - } - - select.field-type { - margin: 5px 0 0 25px; - width: calc(100% - 25px); - } - - .icon { - display: flex; - justify-content: center; - align-items: center; - min-width: 34px; - margin-left: -5px; - - @include themify($themes) { - color: themed("mutedColor"); - } - - &.icon-small { - min-width: 25px; - } - - img { - border-radius: $border-radius; - max-height: 20px; - max-width: 20px; - } - } - - .progress { - display: flex; - height: 5px; - overflow: hidden; - margin: 5px -15px -10px; - - .progress-bar { - display: flex; - flex-direction: column; - justify-content: center; - white-space: nowrap; - background-color: $brand-primary; - } - } - - .radio-group { - display: flex; - justify-content: flex-start; - align-items: center; - margin-bottom: 5px; - - input { - flex-grow: 0; - } - - label { - margin: 0 0 0 5px; - flex-grow: 1; - font-size: $font-size-base; - display: block; - width: 100%; - - @include themify($themes) { - color: themed("textColor"); - } - } - - &.align-start { - align-items: start; - margin-top: 10px; - - label { - margin-top: -4px; - } - } - } -} - -.truncate { - display: inline-block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -form { - .box { - .box-content { - .box-content-row { - &.no-hover { - &:hover { - @include themify($themes) { - background-color: themed("transparentColor") !important; - } - } - } - } - } - } -} diff --git a/apps/browser/src/popup/scss/buttons.scss b/apps/browser/src/popup/scss/buttons.scss deleted file mode 100644 index e9af536dc3d..00000000000 --- a/apps/browser/src/popup/scss/buttons.scss +++ /dev/null @@ -1,118 +0,0 @@ -@import "variables.scss"; - -.btn { - border-radius: $border-radius; - padding: 7px 15px; - border: 1px solid #000000; - font-size: $font-size-base; - text-align: center; - cursor: pointer; - - @include themify($themes) { - background-color: themed("buttonBackgroundColor"); - border-color: themed("buttonBorderColor"); - color: themed("buttonColor"); - } - - &.primary { - @include themify($themes) { - color: themed("buttonPrimaryColor"); - } - } - - &.danger { - @include themify($themes) { - color: themed("buttonDangerColor"); - } - } - - &.callout-half { - font-weight: bold; - max-width: 50%; - } - - &:hover:not([disabled]) { - cursor: pointer; - - @include themify($themes) { - background-color: darken(themed("buttonBackgroundColor"), 1.5%); - border-color: darken(themed("buttonBorderColor"), 17%); - color: darken(themed("buttonColor"), 10%); - } - - &.primary { - @include themify($themes) { - color: darken(themed("buttonPrimaryColor"), 6%); - } - } - - &.danger { - @include themify($themes) { - color: darken(themed("buttonDangerColor"), 6%); - } - } - } - - &:focus:not([disabled]) { - cursor: pointer; - outline: 0; - - @include themify($themes) { - background-color: darken(themed("buttonBackgroundColor"), 6%); - border-color: darken(themed("buttonBorderColor"), 25%); - } - } - - &[disabled] { - opacity: 0.65; - cursor: default !important; - } - - &.block { - display: block; - width: calc(100% - 10px); - margin: 0 auto; - } - - &.link, - &.neutral { - border: none !important; - background: none !important; - - &:focus { - text-decoration: underline; - } - } -} - -.action-buttons { - .btn { - &:focus { - outline: auto; - } - } -} - -button.box-content-row { - display: block; - width: calc(100% - 10px); - text-align: left; - border-color: none; - - @include themify($themes) { - background-color: themed("boxBackgroundColor"); - } -} - -button { - border: none; - background: transparent; - color: inherit; -} - -.login-buttons { - .btn.block { - width: 100%; - margin-bottom: 10px; - } -} diff --git a/apps/browser/src/popup/scss/environment.scss b/apps/browser/src/popup/scss/environment.scss deleted file mode 100644 index cd8f6379e2c..00000000000 --- a/apps/browser/src/popup/scss/environment.scss +++ /dev/null @@ -1,43 +0,0 @@ -@import "variables.scss"; - -html.browser_safari { - &.safari_height_fix { - body { - height: 360px !important; - - &.body-xs { - height: 300px !important; - } - - &.body-full { - height: 100% !important; - } - } - } - - header { - .search .bwi { - left: 20px; - } - - .left + .search .bwi { - left: 10px; - } - } - - .content { - &.login-page { - padding-top: 100px; - } - } - - app-root { - border-width: 1px; - border-style: solid; - border-color: #000000; - } - - &.theme_light app-root { - border-color: #777777; - } -} diff --git a/apps/browser/src/popup/scss/grid.scss b/apps/browser/src/popup/scss/grid.scss deleted file mode 100644 index 8cdb29bb52c..00000000000 --- a/apps/browser/src/popup/scss/grid.scss +++ /dev/null @@ -1,11 +0,0 @@ -.row { - display: flex; - margin: 0 -15px; - width: 100%; -} - -.col { - flex-basis: 0; - flex-grow: 1; - padding: 0 15px; -} diff --git a/apps/browser/src/popup/scss/misc.scss b/apps/browser/src/popup/scss/misc.scss deleted file mode 100644 index 006e1d35f6a..00000000000 --- a/apps/browser/src/popup/scss/misc.scss +++ /dev/null @@ -1,348 +0,0 @@ -@import "variables.scss"; - -small, -.small { - font-size: $font-size-small; -} - -.bg-primary { - @include themify($themes) { - background-color: themed("primaryColor") !important; - } -} - -.bg-success { - @include themify($themes) { - background-color: themed("successColor") !important; - } -} - -.bg-danger { - @include themify($themes) { - background-color: themed("dangerColor") !important; - } -} - -.bg-info { - @include themify($themes) { - background-color: themed("infoColor") !important; - } -} - -.bg-warning { - @include themify($themes) { - background-color: themed("warningColor") !important; - } -} - -.text-primary { - @include themify($themes) { - color: themed("primaryColor") !important; - } -} - -.text-success { - @include themify($themes) { - color: themed("successColor") !important; - } -} - -.text-muted { - @include themify($themes) { - color: themed("mutedColor") !important; - } -} - -.text-default { - @include themify($themes) { - color: themed("textColor") !important; - } -} - -.text-danger { - @include themify($themes) { - color: themed("dangerColor") !important; - } -} - -.text-info { - @include themify($themes) { - color: themed("infoColor") !important; - } -} - -.text-warning { - @include themify($themes) { - color: themed("warningColor") !important; - } -} - -.text-center { - text-align: center; -} - -.font-weight-semibold { - font-weight: 600; -} - -p.lead { - font-size: $font-size-large; - margin-bottom: 20px; - font-weight: normal; -} - -.flex-right { - margin-left: auto; -} - -.flex-bottom { - margin-top: auto; -} - -.no-margin { - margin: 0 !important; -} - -.display-block { - display: block !important; -} - -.monospaced { - font-family: $font-family-monospace; -} - -.show-whitespace { - white-space: pre-wrap; -} - -.img-responsive { - display: block; - max-width: 100%; - height: auto; -} - -.img-rounded { - border-radius: $border-radius; -} - -.select-index-top { - position: relative; - z-index: 100; -} - -.sr-only { - position: absolute !important; - width: 1px !important; - height: 1px !important; - padding: 0 !important; - margin: -1px !important; - overflow: hidden !important; - clip: rect(0, 0, 0, 0) !important; - border: 0 !important; -} - -:not(:focus) > .exists-only-on-parent-focus { - display: none; -} - -.password-wrapper { - overflow-wrap: break-word; - white-space: pre-wrap; - min-width: 0; -} - -.password-number { - @include themify($themes) { - color: themed("passwordNumberColor"); - } -} - -.password-special { - @include themify($themes) { - color: themed("passwordSpecialColor"); - } -} - -.password-character { - display: inline-flex; - flex-direction: column; - align-items: center; - width: 30px; - height: 36px; - font-weight: 600; - - &:nth-child(odd) { - @include themify($themes) { - background-color: themed("backgroundColor"); - } - } -} - -.password-count { - white-space: nowrap; - font-size: 8px; - - @include themify($themes) { - color: themed("passwordCountText") !important; - } -} - -#duo-frame { - background: url("../images/loading.svg") 0 0 no-repeat; - width: 100%; - height: 470px; - margin-bottom: -10px; - - iframe { - width: 100%; - height: 100%; - border: none; - } -} - -#web-authn-frame { - width: 100%; - height: 40px; - - iframe { - border: none; - height: 100%; - width: 100%; - } -} - -body.linux-webauthn { - width: 485px !important; - #web-authn-frame { - iframe { - width: 375px; - margin: 0 55px; - } - } -} - -app-root > #loading { - display: flex; - text-align: center; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - color: $text-muted; - - @include themify($themes) { - color: themed("mutedColor"); - } -} - -app-vault-icon, -.app-vault-icon { - display: flex; -} - -.logo-image { - margin: 0 auto; - width: 142px; - height: 21px; - background-size: 142px 21px; - background-repeat: no-repeat; - @include themify($themes) { - background-image: url("../images/logo-" + themed("logoSuffix") + "@2x.png"); - } - @media (min-width: 219px) { - width: 189px; - height: 28px; - background-size: 189px 28px; - } - @media (min-width: 314px) { - width: 284px; - height: 43px; - background-size: 284px 43px; - } -} - -[hidden] { - display: none !important; -} - -.draggable { - cursor: move; -} - -input[type="password"]::-ms-reveal { - display: none; -} - -.flex { - display: flex; - - &.flex-grow { - > * { - flex: 1; - } - } -} - -// Text selection styles -// Set explicit selection styles (assumes primary accent color has sufficient -// contrast against the background, so its inversion is also still readable) -// and suppress user selection for most elements (to make it more app-like) - -:not(bit-form-field input)::selection { - @include themify($themes) { - color: themed("backgroundColor"); - background-color: themed("primaryAccentColor"); - } -} - -h1, -h2, -h3, -label, -a, -button, -p, -img, -.box-header, -.box-footer, -.callout, -.row-label, -.modal-title, -.overlay-container { - user-select: none; - - &.user-select { - user-select: auto; - } -} - -/* tweak for inconsistent line heights in cipher view */ -.box-footer button, -.box-footer a { - line-height: 1; -} - -// Workaround for slow performance on external monitors on Chrome + MacOS -// See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64 -@keyframes redraw { - 0% { - opacity: 0.99; - } - 100% { - opacity: 1; - } -} -html.force_redraw { - animation: redraw 1s linear infinite; -} - -/* override for vault icon in browser (pre extension refresh) */ -app-vault-icon:not(app-vault-list-items-container app-vault-icon) > div { - display: flex; - justify-content: center; - align-items: center; - float: left; - height: 36px; - width: 34px; - margin-left: -5px; -} diff --git a/apps/browser/src/popup/scss/pages.scss b/apps/browser/src/popup/scss/pages.scss deleted file mode 100644 index 56c5f80c86c..00000000000 --- a/apps/browser/src/popup/scss/pages.scss +++ /dev/null @@ -1,144 +0,0 @@ -@import "variables.scss"; - -app-home { - position: fixed; - height: 100%; - width: 100%; - - .center-content { - margin-top: -50px; - height: calc(100% + 50px); - } - - img { - width: 284px; - margin: 0 auto; - } - - p.lead { - margin: 30px 0; - } - - .btn + .btn { - margin-top: 10px; - } - - button.settings-icon { - position: absolute; - top: 10px; - left: 10px; - - @include themify($themes) { - color: themed("mutedColor"); - } - - &:not(:hover):not(:focus) { - span { - clip: rect(0 0 0 0); - clip-path: inset(50%); - height: 1px; - overflow: hidden; - position: absolute; - white-space: nowrap; - width: 1px; - } - } - - &:hover, - &:focus { - text-decoration: none; - - @include themify($themes) { - color: themed("primaryColor"); - } - } - } -} - -body.body-sm, -body.body-xs { - app-home { - .center-content { - margin-top: 0; - height: 100%; - } - - p.lead { - margin: 15px 0; - } - } -} - -body.body-full { - app-home { - .center-content { - margin-top: -80px; - height: calc(100% + 80px); - } - } -} - -.createAccountLink { - padding: 30px 10px 0 10px; -} - -.remember-email-check { - padding-top: 18px; - padding-left: 10px; - padding-bottom: 18px; -} - -.login-buttons > button { - margin: 15px 0 15px 0; -} - -.useBrowserlink { - margin-left: 5px; - margin-top: 20px; - - span { - font-weight: 700; - font-size: $font-size-small; - } -} - -.fido2-browser-selector-dropdown { - @include themify($themes) { - background-color: themed("boxBackgroundColor"); - } - padding: 8px; - width: 100%; - box-shadow: - 0 2px 2px 0 rgba(0, 0, 0, 0.14), - 0 3px 1px -2px rgba(0, 0, 0, 0.12), - 0 1px 5px 0 rgba(0, 0, 0, 0.2); - border-radius: $border-radius; -} - -.fido2-browser-selector-dropdown-item { - @include themify($themes) { - color: themed("textColor") !important; - } - width: 100%; - text-align: left; - padding: 0px 15px 0px 5px; - margin-bottom: 5px; - border-radius: 3px; - border: 1px solid transparent; - transition: all 0.2s ease-in-out; - - &:hover { - @include themify($themes) { - background-color: themed("listItemBackgroundHoverColor") !important; - } - } - - &:last-child { - margin-bottom: 0; - } -} - -/** Temporary fix for avatar, will not be required once we migrate to tailwind preflight **/ -bit-avatar svg { - display: block; -} diff --git a/apps/browser/src/popup/scss/plugins.scss b/apps/browser/src/popup/scss/plugins.scss deleted file mode 100644 index 591e8a1bd0c..00000000000 --- a/apps/browser/src/popup/scss/plugins.scss +++ /dev/null @@ -1,23 +0,0 @@ -@import "variables.scss"; - -@each $mfaType in $mfaTypes { - .mfaType#{$mfaType} { - content: url("../images/two-factor/" + $mfaType + ".png"); - max-width: 100px; - } -} - -.mfaType1 { - @include themify($themes) { - content: url("../images/two-factor/1" + themed("mfaLogoSuffix")); - max-width: 100px; - max-height: 45px; - } -} - -.mfaType7 { - @include themify($themes) { - content: url("../images/two-factor/7" + themed("mfaLogoSuffix")); - max-width: 100px; - } -} diff --git a/apps/browser/src/popup/scss/popup.scss b/apps/browser/src/popup/scss/popup.scss index b150de2c75d..59b4d472f23 100644 --- a/apps/browser/src/popup/scss/popup.scss +++ b/apps/browser/src/popup/scss/popup.scss @@ -1,13 +1,50 @@ @import "../../../../../libs/angular/src/scss/bwicons/styles/style.scss"; @import "variables.scss"; @import "../../../../../libs/angular/src/scss/icons.scss"; -@import "base.scss"; -@import "grid.scss"; -@import "box.scss"; -@import "buttons.scss"; -@import "misc.scss"; -@import "environment.scss"; -@import "pages.scss"; -@import "plugins.scss"; @import "@angular/cdk/overlay-prebuilt.css"; @import "../../../../../libs/components/src/multi-select/scss/bw.theme"; + +.cdk-virtual-scroll-content-wrapper { + width: 100%; +} + +// MFA Types for logo styling with no dark theme alternative +$mfaTypes: 0, 2, 3, 4, 6; + +@each $mfaType in $mfaTypes { + .mfaType#{$mfaType} { + content: url("../images/two-factor/" + $mfaType + ".png"); + max-width: 100px; + } +} + +.mfaType0 { + content: url("../images/two-factor/0.png"); + max-width: 100px; + max-height: 45px; +} + +.mfaType1 { + max-width: 100px; + max-height: 45px; + + &:is(.theme_light *) { + content: url("../images/two-factor/1.png"); + } + + &:is(.theme_dark *) { + content: url("../images/two-factor/1-w.png"); + } +} + +.mfaType7 { + max-width: 100px; + + &:is(.theme_light *) { + content: url("../images/two-factor/7.png"); + } + + &:is(.theme_dark *) { + content: url("../images/two-factor/7-w.png"); + } +} diff --git a/apps/browser/src/popup/scss/tailwind.css b/apps/browser/src/popup/scss/tailwind.css index 54139990356..f58950cc86a 100644 --- a/apps/browser/src/popup/scss/tailwind.css +++ b/apps/browser/src/popup/scss/tailwind.css @@ -1,4 +1,104 @@ -@import "../../../../../libs/components/src/tw-theme.css"; +@import "../../../../../libs/components/src/tw-theme-preflight.css"; + +@layer base { + html { + overflow: hidden; + min-height: 600px; + height: 100%; + + &.body-sm { + min-height: 500px; + } + + &.body-xs { + min-height: 400px; + } + + &.body-xxs { + min-height: 300px; + } + + &.body-3xs { + min-height: 240px; + } + + &.body-full { + min-height: unset; + width: 100%; + height: 100%; + + & body { + width: 100%; + } + } + } + + html.browser_safari { + &.safari_height_fix { + body { + height: 360px !important; + + &.body-xs { + height: 300px !important; + } + + &.body-full { + height: 100% !important; + } + } + } + + app-root { + border-width: 1px; + border-style: solid; + border-color: #000000; + } + + &.theme_light app-root { + border-color: #777777; + } + } + + body { + width: 380px; + height: 100%; + position: relative; + min-height: inherit; + overflow: hidden; + @apply tw-bg-background-alt; + } + + /** + * Workaround for slow performance on external monitors on Chrome + MacOS + * See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64 + */ + @keyframes redraw { + 0% { + opacity: 0.99; + } + 100% { + opacity: 1; + } + } + html.force_redraw { + animation: redraw 1s linear infinite; + } + + /** + * Text selection style: + * suppress user selection for most elements (to make it more app-like) + */ + h1, + h2, + h3, + label, + a, + button, + p, + img { + user-select: none; + } +} @layer components { /** Safari Support */ @@ -19,4 +119,59 @@ html:not(.browser_safari) .tw-styled-scrollbar { scrollbar-color: rgb(var(--color-secondary-500)) rgb(var(--color-background-alt)); } + + #duo-frame { + background: url("../images/loading.svg") 0 0 no-repeat; + width: 100%; + height: 470px; + margin-bottom: -10px; + + iframe { + width: 100%; + height: 100%; + border: none; + } + } + + #web-authn-frame { + width: 100%; + height: 40px; + + iframe { + border: none; + height: 100%; + width: 100%; + } + } + + body.linux-webauthn { + width: 485px !important; + #web-authn-frame { + iframe { + width: 375px; + margin: 0 55px; + } + } + } + + app-root > #loading { + display: flex; + text-align: center; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + + @apply tw-text-muted; + } + + /** + * Text selection style: + * Set explicit selection styles (assumes primary accent color has sufficient + * contrast against the background, so its inversion is also still readable) + */ + :not(bit-form-field input)::selection { + @apply tw-text-contrast; + @apply tw-bg-primary-700; + } } diff --git a/apps/browser/src/popup/scss/variables.scss b/apps/browser/src/popup/scss/variables.scss index e57e98fd0cc..02a10521bca 100644 --- a/apps/browser/src/popup/scss/variables.scss +++ b/apps/browser/src/popup/scss/variables.scss @@ -1,178 +1,42 @@ -$dark-icon-themes: "theme_dark"; +/** + * DEPRECATED: DO NOT MODIFY OR USE! + */ + +$dark-icon-themes: "theme_dark"; $font-family-sans-serif: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; -$font-size-base: 16px; -$font-size-large: 18px; -$font-size-xlarge: 22px; -$font-size-xxlarge: 28px; -$font-size-small: 12px; $text-color: #000000; -$border-color: #f0f0f0; $border-color-dark: #ddd; -$list-item-hover: #fbfbfb; -$list-icon-color: #767679; -$disabled-box-opacity: 1; -$border-radius: 6px; -$line-height-base: 1.42857143; -$icon-hover-color: lighten($text-color, 50%); - -$mfaTypes: 0, 2, 3, 4, 6; - -$gray: #555; -$gray-light: #777; -$text-muted: $gray-light; - $brand-primary: #175ddc; -$brand-danger: #c83522; $brand-success: #017e45; -$brand-info: #555555; -$brand-warning: #8b6609; -$brand-primary-accent: #1252a3; - $background-color: #f0f0f0; - -$box-background-color: white; -$box-background-hover-color: $list-item-hover; -$box-border-color: $border-color; -$border-color-alt: #c3c5c7; - -$button-border-color: darken($border-color-dark, 12%); -$button-background-color: white; -$button-color: lighten($text-color, 40%); $button-color-primary: darken($brand-primary, 8%); -$button-color-danger: darken($brand-danger, 10%); - -$code-color: #c01176; -$code-color-dark: #f08dc7; $themes: ( light: ( textColor: $text-color, - hoverColorTransparent: rgba($text-color, 0.15), borderColor: $border-color-dark, backgroundColor: $background-color, - borderColorAlt: $border-color-alt, - backgroundColorAlt: #ffffff, - scrollbarColor: rgba(100, 100, 100, 0.2), - scrollbarHoverColor: rgba(100, 100, 100, 0.4), - boxBackgroundColor: $box-background-color, - boxBackgroundHoverColor: $box-background-hover-color, - boxBorderColor: $box-border-color, - tabBackgroundColor: #ffffff, - tabBackgroundHoverColor: $list-item-hover, - headerColor: #ffffff, - headerBackgroundColor: $brand-primary, - headerBackgroundHoverColor: rgba(255, 255, 255, 0.1), - headerBorderColor: $brand-primary, - headerInputBackgroundColor: darken($brand-primary, 8%), - headerInputBackgroundFocusColor: darken($brand-primary, 10%), - headerInputColor: #ffffff, - headerInputPlaceholderColor: lighten($brand-primary, 35%), - listItemBackgroundHoverColor: $list-item-hover, - disabledIconColor: $list-icon-color, - disabledBoxOpacity: $disabled-box-opacity, - headingColor: $gray-light, - labelColor: $gray-light, - mutedColor: $text-muted, - totpStrokeColor: $brand-primary, - boxRowButtonColor: $brand-primary, - boxRowButtonHoverColor: darken($brand-primary, 10%), inputBorderColor: darken($border-color-dark, 7%), inputBackgroundColor: #ffffff, - inputPlaceholderColor: lighten($gray-light, 35%), - buttonBackgroundColor: $button-background-color, - buttonBorderColor: $button-border-color, - buttonColor: $button-color, buttonPrimaryColor: $button-color-primary, - buttonDangerColor: $button-color-danger, primaryColor: $brand-primary, - primaryAccentColor: $brand-primary-accent, - dangerColor: $brand-danger, successColor: $brand-success, - infoColor: $brand-info, - warningColor: $brand-warning, - logoSuffix: "dark", - mfaLogoSuffix: ".png", passwordNumberColor: #007fde, passwordSpecialColor: #c40800, - passwordCountText: #212529, - calloutBorderColor: $border-color-dark, - calloutBackgroundColor: $box-background-color, - toastTextColor: #ffffff, - svgSuffix: "-light.svg", - transparentColor: rgba(0, 0, 0, 0), - dateInputColorScheme: light, - // https://stackoverflow.com/a/53336754 - webkitCalendarPickerFilter: invert(46%) sepia(69%) saturate(6397%) hue-rotate(211deg) - brightness(85%) contrast(103%), - // light has no hover so use same color - webkitCalendarPickerHoverFilter: invert(46%) sepia(69%) saturate(6397%) hue-rotate(211deg) - brightness(85%) contrast(103%), - codeColor: $code-color, ), dark: ( textColor: #ffffff, - hoverColorTransparent: rgba($text-color, 0.15), borderColor: #161c26, backgroundColor: #161c26, - borderColorAlt: #6e788a, - backgroundColorAlt: #2f343d, - scrollbarColor: #6e788a, - scrollbarHoverColor: #8d94a5, - boxBackgroundColor: #2f343d, - boxBackgroundHoverColor: #3c424e, - boxBorderColor: #4c525f, - tabBackgroundColor: #2f343d, - tabBackgroundHoverColor: #3c424e, - headerColor: #ffffff, - headerBackgroundColor: #2f343d, - headerBackgroundHoverColor: #3c424e, - headerBorderColor: #161c26, - headerInputBackgroundColor: #3c424e, - headerInputBackgroundFocusColor: #4c525f, - headerInputColor: #ffffff, - headerInputPlaceholderColor: #bac0ce, - listItemBackgroundHoverColor: #3c424e, - disabledIconColor: #bac0ce, - disabledBoxOpacity: 0.5, - headingColor: #bac0ce, - labelColor: #bac0ce, - mutedColor: #bac0ce, - totpStrokeColor: #4c525f, - boxRowButtonColor: #bac0ce, - boxRowButtonHoverColor: #ffffff, inputBorderColor: #4c525f, inputBackgroundColor: #2f343d, - inputPlaceholderColor: #bac0ce, - buttonBackgroundColor: #3c424e, - buttonBorderColor: #4c525f, - buttonColor: #bac0ce, buttonPrimaryColor: #6f9df1, - buttonDangerColor: #ff8d85, primaryColor: #6f9df1, - primaryAccentColor: #6f9df1, - dangerColor: #ff8d85, successColor: #52e07c, - infoColor: #a4b0c6, - warningColor: #ffeb66, - logoSuffix: "white", - mfaLogoSuffix: "-w.png", passwordNumberColor: #6f9df1, passwordSpecialColor: #ff8d85, - passwordCountText: #ffffff, - calloutBorderColor: #4c525f, - calloutBackgroundColor: #3c424e, - toastTextColor: #1f242e, - svgSuffix: "-dark.svg", - transparentColor: rgba(0, 0, 0, 0), - dateInputColorScheme: dark, - // https://stackoverflow.com/a/53336754 - must prepend brightness(0) saturate(100%) to dark themed date inputs - webkitCalendarPickerFilter: brightness(0) saturate(100%) invert(86%) sepia(19%) saturate(152%) - hue-rotate(184deg) brightness(87%) contrast(93%), - webkitCalendarPickerHoverFilter: brightness(0) saturate(100%) invert(100%) sepia(0%) - saturate(0%) hue-rotate(93deg) brightness(103%) contrast(103%), - codeColor: $code-color-dark, ), ); diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index 91b6f0ff105..f16d82d0810 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -1,5 +1,4 @@ -import { DOCUMENT } from "@angular/common"; -import { inject, Inject, Injectable } from "@angular/core"; +import { inject, Inject, Injectable, DOCUMENT } from "@angular/core"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index c462319dc2e..bb89eff1147 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -76,6 +76,7 @@ import { InternalMasterPasswordServiceAbstraction, MasterPasswordServiceAbstraction, } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout"; import { VaultTimeoutService, VaultTimeoutStringType, @@ -135,6 +136,7 @@ import { DialogService, ToastService, } from "@bitwarden/components"; +import { GeneratorServicesModule } from "@bitwarden/generator-components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { BiometricsService, @@ -170,6 +172,7 @@ import { InlineMenuFieldQualificationService } from "../../autofill/services/inl import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics"; import { ExtensionLockComponentService } from "../../key-management/lock/services/extension-lock-component.service"; import { BrowserSessionTimeoutSettingsComponentService } from "../../key-management/session-timeout/services/browser-session-timeout-settings-component.service"; +import { BrowserSessionTimeoutTypeService } from "../../key-management/session-timeout/services/browser-session-timeout-type.service"; import { ForegroundVaultTimeoutService } from "../../key-management/vault-timeout/foreground-vault-timeout.service"; import { BrowserActionsService } from "../../platform/actions/browser-actions.service"; import { BrowserApi } from "../../platform/browser/browser-api"; @@ -723,15 +726,25 @@ const safeProviders: SafeProvider[] = [ useClass: ExtensionNewDeviceVerificationComponentService, deps: [], }), + safeProvider({ + provide: SessionTimeoutTypeService, + useClass: BrowserSessionTimeoutTypeService, + deps: [PlatformUtilsService], + }), safeProvider({ provide: SessionTimeoutSettingsComponentService, useClass: BrowserSessionTimeoutSettingsComponentService, - deps: [I18nServiceAbstraction, PlatformUtilsService, MessagingServiceAbstraction], + deps: [ + I18nServiceAbstraction, + SessionTimeoutTypeService, + PolicyService, + MessagingServiceAbstraction, + ], }), ]; @NgModule({ - imports: [JslibServicesModule], + imports: [JslibServicesModule, GeneratorServicesModule], declarations: [], // Do not register your dependency here! Add it to the typesafeProviders array using the helper function providers: safeProviders, diff --git a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts index 3481ced35dc..8f30d00cc31 100644 --- a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts +++ b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts @@ -41,7 +41,12 @@ import { SendFilePopoutDialogContainerComponent } from "../send-file-popout-dial class QueryParams { constructor(params: Params) { this.sendId = params.sendId; - this.type = parseInt(params.type, 10); + const sendTypeValue = parseInt(params.type, 10); + if (sendTypeValue === SendType.Text || sendTypeValue === SendType.File) { + this.type = sendTypeValue; + } else { + throw new Error(`Invalid SendType: ${params.type}`); + } } /** diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts index 63ede7ba357..6d79f430a37 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts @@ -37,7 +37,7 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; -import { SendV2Component, SendState } from "./send-v2.component"; +import { SendState, SendV2Component } from "./send-v2.component"; describe("SendV2Component", () => { let component: SendV2Component; diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts index e3baba53c42..89769bdd1ce 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -38,12 +38,16 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; import { VaultFadeInOutSkeletonComponent } from "../../../vault/popup/components/vault-fade-in-out-skeleton/vault-fade-in-out-skeleton.component"; -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -export enum SendState { - Empty, - NoResults, -} +/** A state of the Send list UI. */ +export const SendState = Object.freeze({ + /** No sends exist for the current filter (file or text). */ + Empty: "Empty", + /** Sends exist, but none match the current filter/search. */ + NoResults: "NoResults", +} as const); + +/** A state of the Send list UI. */ +export type SendState = (typeof SendState)[keyof typeof SendState]; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @@ -114,6 +118,11 @@ export class SendV2Component implements OnDestroy { protected sendsDisabled = false; + private readonly sendTypeTitles: Record = { + [SendType.File]: "fileSends", + [SendType.Text]: "textSends", + }; + constructor( protected sendItemsService: SendItemsService, protected sendListFiltersService: SendListFiltersService, @@ -130,7 +139,7 @@ export class SendV2Component implements OnDestroy { .pipe(takeUntilDestroyed()) .subscribe(([emptyList, noFilteredResults, currentFilter]) => { if (currentFilter?.sendType !== null) { - this.title = `${this.sendType[currentFilter.sendType].toLowerCase()}Sends`; + this.title = this.sendTypeTitles[currentFilter.sendType] ?? "allSends"; } else { this.title = "allSends"; } 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 1bffcd9ad51..f2c9d470816 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 @@ -381,4 +381,88 @@ describe("AddEditV2Component", () => { expect(navigate).toHaveBeenCalledWith(["/tabs/vault"]); }); }); + + describe("reloadAddEditCipherData", () => { + beforeEach(fakeAsync(() => { + addEditCipherInfo$.next({ + cipher: { + name: "InitialName", + type: CipherType.Login, + login: { + password: "initialPassword", + username: "initialUsername", + uris: [{ uri: "https://initial.com" }], + }, + }, + } as AddEditCipherInfo); + queryParams$.next({}); + tick(); + + cipherServiceMock.setAddEditCipherInfo.mockClear(); + })); + + it("replaces all initialValues with new data, clearing stale fields", fakeAsync(() => { + const newCipherInfo = { + cipher: { + name: "UpdatedName", + type: CipherType.Login, + login: { + password: "updatedPassword", + uris: [{ uri: "https://updated.com" }], + }, + }, + } as AddEditCipherInfo; + + addEditCipherInfo$.next(newCipherInfo); + + const messageListener = component["messageListener"]; + messageListener({ command: "reloadAddEditCipherData" }); + tick(); + + expect(component.config.initialValues).toEqual({ + name: "UpdatedName", + password: "updatedPassword", + loginUri: "https://updated.com", + } as OptionalInitialValues); + + expect(cipherServiceMock.setAddEditCipherInfo).toHaveBeenCalledWith(null, "UserId"); + })); + + it("does not reload data if config is not set", fakeAsync(() => { + component.config = null; + + const messageListener = component["messageListener"]; + messageListener({ command: "reloadAddEditCipherData" }); + tick(); + + expect(cipherServiceMock.setAddEditCipherInfo).not.toHaveBeenCalled(); + })); + + it("does not reload data if latestCipherInfo is null", fakeAsync(() => { + addEditCipherInfo$.next(null); + + const messageListener = component["messageListener"]; + messageListener({ command: "reloadAddEditCipherData" }); + tick(); + + expect(component.config.initialValues).toEqual({ + name: "InitialName", + password: "initialPassword", + username: "initialUsername", + loginUri: "https://initial.com", + } as OptionalInitialValues); + + expect(cipherServiceMock.setAddEditCipherInfo).not.toHaveBeenCalled(); + })); + + it("ignores messages with different commands", fakeAsync(() => { + const initialValues = component.config.initialValues; + + const messageListener = component["messageListener"]; + messageListener({ command: "someOtherCommand" }); + tick(); + + expect(component.config.initialValues).toBe(initialValues); + })); + }); }); 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 60e44cefbdf..22aad854dd0 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { CommonModule } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; +import { Component, OnInit, OnDestroy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; import { ActivatedRoute, Params, Router } from "@angular/router"; @@ -158,7 +158,7 @@ export type AddEditQueryParams = Partial>; IconButtonModule, ], }) -export class AddEditV2Component implements OnInit { +export class AddEditV2Component implements OnInit, OnDestroy { headerText: string; config: CipherFormConfig; canDeleteCipher$: Observable; @@ -200,12 +200,58 @@ export class AddEditV2Component implements OnInit { this.subscribeToParams(); } + private messageListener: (message: any) => void; + async ngOnInit() { this.fido2PopoutSessionData = await firstValueFrom(this.fido2PopoutSessionData$); if (BrowserPopupUtils.inPopout(window)) { this.popupCloseWarningService.enable(); } + + // Listen for messages to reload cipher data when the pop up is already open + this.messageListener = async (message: any) => { + if (message?.command === "reloadAddEditCipherData") { + try { + await this.reloadCipherData(); + } catch (error) { + this.logService.error("Failed to reload cipher data", error); + } + } + }; + BrowserApi.addListener(chrome.runtime.onMessage, this.messageListener); + } + + ngOnDestroy() { + if (this.messageListener) { + BrowserApi.removeListener(chrome.runtime.onMessage, this.messageListener); + } + } + + /** + * Reloads the cipher data when the popup is already open and new form data is submitted. + * This completely replaces the initialValues to clear any stale data from the previous submission. + */ + private async reloadCipherData() { + if (!this.config) { + return; + } + + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const latestCipherInfo = await firstValueFrom( + this.cipherService.addEditCipherInfo$(activeUserId), + ); + + if (latestCipherInfo != null) { + this.config = { + ...this.config, + initialValues: mapAddEditCipherInfoToInitialValues(latestCipherInfo), + }; + + // Be sure to clear the "cached" cipher info, so it doesn't get used again + await this.cipherService.setAddEditCipherInfo(null, activeUserId); + } } /** diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts index 871163ac80b..1da2d352c14 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts @@ -1,4 +1,4 @@ -import { Component, Input } from "@angular/core"; +import { Component, input, ChangeDetectionStrategy } from "@angular/core"; import { ComponentFixture, TestBed, fakeAsync, tick } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { ActivatedRoute, Router } from "@angular/router"; @@ -25,31 +25,23 @@ import { PopupRouterCacheService } from "../../../../../platform/popup/view-cach import { AttachmentsV2Component } from "./attachments-v2.component"; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "popup-header", template: ``, + changeDetection: ChangeDetectionStrategy.OnPush, }) class MockPopupHeaderComponent { - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() pageTitle: string; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() backAction: () => void; + readonly pageTitle = input(); + readonly backAction = input<() => void>(); } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "popup-footer", template: ``, + changeDetection: ChangeDetectionStrategy.OnPush, }) class MockPopupFooterComponent { - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() pageTitle: string; + readonly pageTitle = input(); } describe("AttachmentsV2Component", () => { @@ -120,7 +112,7 @@ describe("AttachmentsV2Component", () => { const submitBtn = fixture.debugElement.queryAll(By.directive(ButtonComponent))[1] .componentInstance; - expect(cipherAttachment.submitBtn).toEqual(submitBtn); + expect(cipherAttachment.submitBtn()).toEqual(submitBtn); }); it("navigates the user to the edit view `onUploadSuccess`", fakeAsync(() => { diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts index 295496c701f..29282d293de 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts @@ -51,6 +51,6 @@ export class AttachmentsV2Component { /** Navigate the user back to the edit screen after uploading an attachment */ async navigateBack() { - await this.popupRouterCacheService.back(); + await this.popupRouterCacheService.back(true); } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.spec.ts new file mode 100644 index 00000000000..84ce58913a9 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.spec.ts @@ -0,0 +1,425 @@ +import { CommonModule } from "@angular/common"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { of } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + CipherViewLike, + CipherViewLikeUtils, +} from "@bitwarden/common/vault/utils/cipher-view-like-utils"; +import { IconButtonModule, ItemModule, MenuModule } from "@bitwarden/components"; +import { CipherListView, CopyableCipherFields } from "@bitwarden/sdk-internal"; + +import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service"; + +import { ItemCopyActionsComponent } from "./item-copy-actions.component"; + +describe("ItemCopyActionsComponent", () => { + let fixture: ComponentFixture; + let component: ItemCopyActionsComponent; + + let i18nService: jest.Mocked; + + beforeEach(async () => { + i18nService = { + t: jest.fn((key: string) => `translated-${key}`), + } as unknown as jest.Mocked; + + await TestBed.configureTestingModule({ + imports: [ + CommonModule, + JslibModule, + ItemModule, + IconButtonModule, + MenuModule, + ItemCopyActionsComponent, // standalone + ], + providers: [ + { provide: I18nService, useValue: i18nService }, + { + provide: VaultPopupCopyButtonsService, + useValue: { + showQuickCopyActions$: of(true), + } satisfies Partial, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ItemCopyActionsComponent); + component = fixture.componentInstance; + + // Default cipher so tests can override as needed + component.cipher = { + name: "My cipher", + viewPassword: true, + login: { username: null, password: null, totp: null }, + card: { code: null, number: null }, + identity: { + fullAddressForCopy: null, + email: null, + username: null, + phone: null, + }, + sshKey: { + privateKey: null, + publicKey: null, + keyFingerprint: null, + }, + notes: null, + copyableFields: [], + } as unknown as CipherViewLike; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe("findSingleCopyableItem", () => { + beforeEach(() => { + jest + .spyOn(CipherViewLikeUtils, "hasCopyableValue") + .mockImplementation( + (cipher: CipherViewLike & { __copyable?: Record }, field) => { + return Boolean(cipher.__copyable?.[field]); + }, + ); + }); + + it("returns the single item with value and translates its key", () => { + const items = [ + { key: "copyUsername", field: "username" as const }, + { key: "copyPassword", field: "password" as const }, + ]; + + (component.cipher as any).__copyable = { + username: true, + password: false, + }; + + const result = component.findSingleCopyableItem(items); + + expect(result).toEqual({ + key: "translated-copyUsername", + field: "username", + }); + expect(i18nService.t).toHaveBeenCalledWith("copyUsername"); + }); + + it("returns null when no items have a value", () => { + const items = [ + { key: "copyUsername", field: "username" as const }, + { key: "copyPassword", field: "password" as const }, + ]; + + (component.cipher as any).__copyable = { + username: false, + password: false, + }; + + const result = component.findSingleCopyableItem(items); + + expect(result).toBeNull(); + }); + + it("returns null when more than one item has a value", () => { + const items = [ + { key: "copyUsername", field: "username" as const }, + { key: "copyPassword", field: "password" as const }, + ]; + + (component.cipher as any).__copyable = { + username: true, + password: true, + }; + + const result = component.findSingleCopyableItem(items); + + expect(result).toBeNull(); + }); + }); + + describe("singleCopyableLogin", () => { + beforeEach(() => { + jest + .spyOn(CipherViewLikeUtils, "hasCopyableValue") + .mockImplementation( + (cipher: CipherViewLike & { __copyable?: Record }, field) => { + return Boolean(cipher.__copyable?.[field]); + }, + ); + }); + + it("returns username with special-case logic when password is hidden and both username/password exist and no totp", () => { + (component.cipher as CipherView).viewPassword = false; + + (component.cipher as any).__copyable = { + username: true, + password: true, + totp: false, + }; + + const result = component.singleCopyableLogin; + + expect(result).toEqual({ + key: "translated-copyUsername", + field: "username", + }); + expect(i18nService.t).toHaveBeenCalledWith("copyUsername"); + }); + + it("returns null when password is hidden but multiple fields exist, ensuring username and totp are shown in the menu UI ", () => { + (component.cipher as CipherView).viewPassword = false; + + (component.cipher as any).__copyable = { + username: true, + password: true, + totp: true, + }; + + const result = component.singleCopyableLogin; + + expect(result).toBeNull(); + }); + + it("falls back to findSingleCopyableItem when password is visible", () => { + const findSingleCopyableItemSpy = jest.spyOn(component, "findSingleCopyableItem"); + (component.cipher as CipherView).viewPassword = true; + + void component.singleCopyableLogin; + expect(findSingleCopyableItemSpy).toHaveBeenCalled(); + }); + }); + + describe("singleCopyableCard", () => { + beforeEach(() => { + jest + .spyOn(CipherViewLikeUtils, "hasCopyableValue") + .mockImplementation( + (cipher: CipherViewLike & { __copyable?: Record }, field) => { + return Boolean(cipher.__copyable?.[field]); + }, + ); + }); + + it("returns security code when it is the only available card value", () => { + (component.cipher as any).__copyable = { + securityCode: true, + cardNumber: false, + }; + + const result = component.singleCopyableCard; + + expect(result).toEqual({ + key: "translated-securityCode", + field: "securityCode", + }); + expect(i18nService.t).toHaveBeenCalledWith("securityCode"); + }); + + it("returns null when both card number and security code are available", () => { + (component.cipher as any).__copyable = { + securityCode: true, + cardNumber: true, + }; + + const result = component.singleCopyableCard; + + expect(result).toBeNull(); + }); + }); + + describe("singleCopyableIdentity", () => { + beforeEach(() => { + jest + .spyOn(CipherViewLikeUtils, "hasCopyableValue") + .mockImplementation( + (cipher: CipherViewLike & { __copyable?: Record }, field) => { + return Boolean(cipher.__copyable?.[field]); + }, + ); + }); + + it("returns the only copyable identity field", () => { + (component.cipher as any).__copyable = { + address: false, + email: true, + username: false, + phone: false, + }; + + const result = component.singleCopyableIdentity; + + expect(result).toEqual({ + key: "translated-email", + field: "email", + }); + expect(i18nService.t).toHaveBeenCalledWith("email"); + }); + + it("returns null when multiple identity fields are available", () => { + (component.cipher as any).__copyable = { + address: true, + email: true, + username: false, + phone: false, + }; + + const result = component.singleCopyableIdentity; + + expect(result).toBeNull(); + }); + }); + + describe("has*Values in non-list view", () => { + beforeEach(() => { + jest.spyOn(CipherViewLikeUtils, "isCipherListView").mockReturnValue(false); + }); + + it("computes hasLoginValues from login fields", () => { + (component.cipher as CipherView).login = { + username: "user", + password: null, + totp: null, + } as any; + + expect(component.hasLoginValues).toBe(true); + + (component.cipher as CipherView).login = { + username: null, + password: null, + totp: null, + } as any; + + expect(component.hasLoginValues).toBe(false); + }); + + it("computes hasCardValues from card fields", () => { + (component.cipher as CipherView).card = { code: "123", number: null } as any; + + expect(component.hasCardValues).toBe(true); + + (component.cipher as CipherView).card = { code: null, number: null } as any; + + expect(component.hasCardValues).toBe(false); + }); + + it("computes hasIdentityValues from identity fields", () => { + (component.cipher as CipherView).identity = { + fullAddressForCopy: null, + email: "test@example.com", + username: null, + phone: null, + } as any; + + expect(component.hasIdentityValues).toBe(true); + + (component.cipher as CipherView).identity = { + fullAddressForCopy: null, + email: null, + username: null, + phone: null, + } as any; + + expect(component.hasIdentityValues).toBe(false); + }); + + it("computes hasSecureNoteValue from notes", () => { + (component.cipher as CipherView).notes = "Some note" as any; + expect(component.hasSecureNoteValue).toBe(true); + + (component.cipher as CipherView).notes = null as any; + expect(component.hasSecureNoteValue).toBe(false); + }); + + it("computes hasSshKeyValues from sshKey fields", () => { + (component.cipher as CipherView).sshKey = { + privateKey: "priv", + publicKey: null, + keyFingerprint: null, + } as any; + + expect(component.hasSshKeyValues).toBe(true); + + (component.cipher as CipherView).sshKey = { + privateKey: null, + publicKey: null, + keyFingerprint: null, + } as any; + + expect(component.hasSshKeyValues).toBe(false); + }); + }); + + describe("has*Values in list view", () => { + beforeEach(() => { + jest.spyOn(CipherViewLikeUtils, "isCipherListView").mockReturnValue(true); + }); + + it("uses copyableFields for login values", () => { + (component.cipher as CipherListView).copyableFields = [ + "LoginUsername", + "CardNumber", + ] as CopyableCipherFields[]; + + expect(component.hasLoginValues).toBe(true); + + (component.cipher as CipherListView).copyableFields = [ + "CardNumber", + ] as CopyableCipherFields[]; + + expect(component.hasLoginValues).toBe(false); + }); + + it("uses copyableFields for card values", () => { + (component.cipher as CipherListView).copyableFields = [ + "CardSecurityCode", + ] as CopyableCipherFields[]; + + expect(component.hasCardValues).toBe(true); + + (component.cipher as CipherListView).copyableFields = [ + "LoginUsername", + ] as CopyableCipherFields[]; + + expect(component.hasCardValues).toBe(false); + }); + + it("uses copyableFields for identity values", () => { + (component.cipher as CipherListView).copyableFields = [ + "IdentityEmail", + ] as CopyableCipherFields[]; + + expect(component.hasIdentityValues).toBe(true); + + (component.cipher as CipherListView).copyableFields = [ + "LoginUsername", + ] as CopyableCipherFields[]; + + expect(component.hasIdentityValues).toBe(false); + }); + + it("uses copyableFields for secure note value", () => { + (component.cipher as CipherListView).copyableFields = [ + "SecureNotes", + ] as CopyableCipherFields[]; + expect(component.hasSecureNoteValue).toBe(true); + + (component.cipher as CipherListView).copyableFields = [ + "LoginUsername", + ] as CopyableCipherFields[]; + expect(component.hasSecureNoteValue).toBe(false); + }); + + it("uses copyableFields for ssh key values", () => { + (component.cipher as CipherListView).copyableFields = ["SshKey"] as CopyableCipherFields[]; + expect(component.hasSshKeyValues).toBe(true); + + (component.cipher as CipherListView).copyableFields = [ + "LoginUsername", + ] as CopyableCipherFields[]; + expect(component.hasSshKeyValues).toBe(false); + }); + }); +}); 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 e24db60a55a..e1398ac9f1f 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 @@ -54,17 +54,20 @@ export class ItemCopyActionsComponent { { key: "copyPassword", field: "password" }, { key: "copyVerificationCode", field: "totp" }, ]; - // If both the password and username are visible but the password is hidden, return the username + // If both the password and username are visible but the password is hidden and there's no + // totp code to copy return the username if ( !this.cipher.viewPassword && CipherViewLikeUtils.hasCopyableValue(this.cipher, "username") && - CipherViewLikeUtils.hasCopyableValue(this.cipher, "password") + CipherViewLikeUtils.hasCopyableValue(this.cipher, "password") && + !CipherViewLikeUtils.hasCopyableValue(this.cipher, "totp") ) { return { key: this.i18nService.t("copyUsername"), field: "username" as const, }; } + return this.findSingleCopyableItem(loginItems); } diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts index 577b7d96771..b9f48b7407b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts @@ -108,7 +108,7 @@ describe("ItemMoreOptionsComponent", () => { { provide: RestrictedItemTypesService, useValue: { restricted$: of([]) } }, { provide: CipherArchiveService, - useValue: { userCanArchive$: () => of(true), hasArchiveFlagEnabled$: () => of(true) }, + useValue: { userCanArchive$: () => of(true), hasArchiveFlagEnabled$: of(true) }, }, { provide: ToastService, useValue: { showToast: () => {} } }, { provide: Router, useValue: { navigate: () => Promise.resolve(true) } }, 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 4dfaf7bc66f..b65acc6ca8e 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 @@ -141,7 +141,7 @@ export class ItemMoreOptionsComponent { }), ); - protected showArchive$: Observable = this.cipherArchiveService.hasArchiveFlagEnabled$(); + protected showArchive$: Observable = this.cipherArchiveService.hasArchiveFlagEnabled$; protected canArchive$: Observable = this.accountService.activeAccount$.pipe( getUserId, 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 7dd0a5a3bc7..fa8683c12dc 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 @@ -1,5 +1,5 @@ - diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts index 5563cd3033b..4b992d9f1ee 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts @@ -180,7 +180,7 @@ describe("VaultV2Component", () => { const nudgesSvc = { showNudgeSpotlight$: jest.fn().mockImplementation((_type: NudgeType) => of(false)), dismissNudge: jest.fn().mockResolvedValue(undefined), - } as Partial; + }; const dialogSvc = {} as Partial; @@ -209,6 +209,10 @@ describe("VaultV2Component", () => { .mockResolvedValue(new Date(Date.now() - 8 * 24 * 60 * 60 * 1000)), // 8 days ago }; + const configSvc = { + getFeatureFlag$: jest.fn().mockImplementation((_flag: string) => of(false)), + }; + beforeEach(async () => { jest.clearAllMocks(); await TestBed.configureTestingModule({ @@ -256,9 +260,7 @@ describe("VaultV2Component", () => { { provide: StateProvider, useValue: mock() }, { provide: ConfigService, - useValue: { - getFeatureFlag$: (_: string) => of(false), - }, + useValue: configSvc, }, { provide: SearchService, @@ -453,7 +455,9 @@ describe("VaultV2Component", () => { hasPremiumFromAnySource$.next(false); - (nudgesSvc.showNudgeSpotlight$ as jest.Mock).mockImplementation((type: NudgeType) => + configSvc.getFeatureFlag$.mockImplementation((_flag: string) => of(true)); + + nudgesSvc.showNudgeSpotlight$.mockImplementation((type: NudgeType) => of(type === NudgeType.PremiumUpgrade), ); @@ -482,9 +486,11 @@ describe("VaultV2Component", () => { })); it("renders Empty-Vault spotlight when vaultState is Empty and nudge is on", fakeAsync(() => { + configSvc.getFeatureFlag$.mockImplementation((_flag: string) => of(false)); + itemsSvc.emptyVault$.next(true); - (nudgesSvc.showNudgeSpotlight$ as jest.Mock).mockImplementation((type: NudgeType) => { + nudgesSvc.showNudgeSpotlight$.mockImplementation((type: NudgeType) => { return of(type === NudgeType.EmptyVaultNudge); }); 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 9cee4f66b67..63d971081df 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 @@ -137,6 +137,10 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { FeatureFlag.VaultLoadingSkeletons, ); + protected premiumSpotlightFeatureFlag$ = this.configService.getFeatureFlag$( + FeatureFlag.BrowserPremiumSpotlight, + ); + private showPremiumNudgeSpotlight$ = this.activeUserId$.pipe( switchMap((userId) => this.nudgesService.showNudgeSpotlight$(NudgeType.PremiumUpgrade, userId)), ); @@ -164,6 +168,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { ); protected showPremiumSpotlight$ = combineLatest([ + this.premiumSpotlightFeatureFlag$, this.showPremiumNudgeSpotlight$, this.showHasItemsVaultSpotlight$, this.hasPremium$, @@ -171,8 +176,13 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { this.accountAgeInDays$, ]).pipe( map( - ([showPremiumNudge, showHasItemsNudge, hasPremium, count, age]) => - showPremiumNudge && !showHasItemsNudge && !hasPremium && count >= 5 && age >= 7, + ([featureFlagEnabled, showPremiumNudge, showHasItemsNudge, hasPremium, count, age]) => + featureFlagEnabled && + showPremiumNudge && + !showHasItemsNudge && + !hasPremium && + count >= 5 && + age >= 7, ), shareReplay({ bufferSize: 1, refCount: true }), ); diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts index c6db820c232..e085cb21c2d 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts @@ -49,7 +49,7 @@ export class VaultSettingsV2Component implements OnInit, OnDestroy { this.userId$.pipe(switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId))), ); - protected readonly showArchiveItem = toSignal(this.cipherArchiveService.hasArchiveFlagEnabled$()); + protected readonly showArchiveItem = toSignal(this.cipherArchiveService.hasArchiveFlagEnabled$); protected readonly userHasArchivedItems = toSignal( this.userId$.pipe( diff --git a/apps/browser/src/vault/popup/utils/vault-popout-window.spec.ts b/apps/browser/src/vault/popup/utils/vault-popout-window.spec.ts index 4597c004290..3389228dda4 100644 --- a/apps/browser/src/vault/popup/utils/vault-popout-window.spec.ts +++ b/apps/browser/src/vault/popup/utils/vault-popout-window.spec.ts @@ -2,6 +2,7 @@ import { mock } from "jest-mock-extended"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/browser/browser-popup-utils"; import { @@ -23,6 +24,19 @@ describe("VaultPopoutWindow", () => { .spyOn(BrowserPopupUtils, "closeSingleActionPopout") .mockImplementation(); + beforeEach(() => { + jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValue([]); + jest.spyOn(BrowserApi, "updateWindowProperties").mockResolvedValue(); + global.chrome = { + ...global.chrome, + runtime: { + ...global.chrome?.runtime, + sendMessage: jest.fn().mockResolvedValue(undefined), + getURL: jest.fn((path) => `chrome-extension://extension-id/${path}`), + }, + }; + }); + afterEach(() => { jest.clearAllMocks(); }); @@ -123,6 +137,32 @@ describe("VaultPopoutWindow", () => { }, ); }); + + it("sends a message to refresh data when the popup is already open", async () => { + const existingPopupTab = { + id: 123, + windowId: 456, + url: `chrome-extension://extension-id/popup/index.html#/edit-cipher?singleActionPopout=${VaultPopoutType.addEditVaultItem}_${CipherType.Login}`, + } as chrome.tabs.Tab; + + jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValue([existingPopupTab]); + const sendMessageSpy = jest.spyOn(chrome.runtime, "sendMessage"); + const updateWindowSpy = jest.spyOn(BrowserApi, "updateWindowProperties"); + + await openAddEditVaultItemPopout( + mock({ windowId: 1, url: "https://jest-testing-website.com" }), + { + cipherType: CipherType.Login, + }, + ); + + expect(openPopoutSpy).not.toHaveBeenCalled(); + expect(sendMessageSpy).toHaveBeenCalledWith({ + command: "reloadAddEditCipherData", + data: { cipherId: undefined, cipherType: CipherType.Login }, + }); + expect(updateWindowSpy).toHaveBeenCalledWith(456, { focused: true }); + }); }); describe("closeAddEditVaultItemPopout", () => { diff --git a/apps/browser/src/vault/popup/utils/vault-popout-window.ts b/apps/browser/src/vault/popup/utils/vault-popout-window.ts index 3dae96b6cc7..cccf005cd2e 100644 --- a/apps/browser/src/vault/popup/utils/vault-popout-window.ts +++ b/apps/browser/src/vault/popup/utils/vault-popout-window.ts @@ -115,10 +115,26 @@ async function openAddEditVaultItemPopout( addEditCipherUrl += formatQueryString("uri", url); } - await BrowserPopupUtils.openPopout(addEditCipherUrl, { - singleActionKey, - senderWindowId: windowId, - }); + const extensionUrl = chrome.runtime.getURL("popup/index.html"); + const existingPopupTabs = await BrowserApi.tabsQuery({ url: `${extensionUrl}*` }); + const existingPopup = existingPopupTabs.find((tab) => + tab.url?.includes(`singleActionPopout=${singleActionKey}`), + ); + // Check if the an existing popup is already open + try { + await chrome.runtime.sendMessage({ + command: "reloadAddEditCipherData", + data: { cipherId, cipherType }, + }); + await BrowserApi.updateWindowProperties(existingPopup.windowId, { + focused: true, + }); + } catch { + await BrowserPopupUtils.openPopout(addEditCipherUrl, { + singleActionKey, + senderWindowId: windowId, + }); + } } /** diff --git a/apps/browser/store/locales/pt_BR/copy.resx b/apps/browser/store/locales/pt_BR/copy.resx index 96ae2332f53..09855fd5798 100644 --- a/apps/browser/store/locales/pt_BR/copy.resx +++ b/apps/browser/store/locales/pt_BR/copy.resx @@ -121,63 +121,63 @@ Bitwarden Gerenciador de Senhas - Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, chaves de acesso e informações sensíveis. + Onde quer que você esteja, a Bitwarden protege facilmente todas as suas senhas, chaves de acesso e informações sensíveis. - Reconhecido como o melhor gerenciador de senhas por "PCMag", 'WIRED', 'The Verge', 'CNET', 'G2', entre outros! + Reconhecido como o melhor gerenciador de senhas por PCMag, WIRED, The Verge, CNET, G2, e outros! PROTEJA A SUA VIDA DIGITAL -Deixe a sua vida digital segura e se proteja contra violações de dados gerando e salvando senhas únicas e fortes para cada conta pessoal. Mantendo tudo isso em um cofre criptografado de ponta a ponta em que apenas você pode acessar. +Proteja a sua vida digital e proteja-se de vazamentos de dados gerando e salvando senhas únicas e seguras, para cada conta. Mantenha tudo em um cofre criptografado de ponta a ponta, que só você pode acessar. -ACESSE OS SEUS DADOS, EM QUALQUER LUGAR, HORA E DISPOSITIVO -Gerencie, armazene, proteja e compartilhe senhas facilmente entre dispositivos ilimitados e sem restrições. +ACESSE SEUS DADOS, DE QUALQUER LUGAR, QUALQUER HORA, EM QUALQUER DISPOSITIVO +Gerencie, armazene, proteja, e compartilhe senhas ilimitadas entre dispositivos ilimitados, sem restrições. Tudo de forma fácil. -TODOS DEVERIAM TEM FERRAMENTAS PARA SE PROTEGER ONLINE -Utilize Bitwarden de graça sem anúncios ou venda dos seus dados. A Bitwarden acredita que todos deveriam ter a opção de estar seguro online. Nossos planos Premium oferecem recursos mais avançados. +TODOS DEVERIAM TER AS FERRAMENTAS PARA SE PROTEGER ON-LINE +Utilize o Bitwarden de graça, sem anúncios, e sem venda de dados. O Bitwarden acredita que todos deveriam ter a possibilidade de se proteger on-line. O plano Premium oferece acesso a recursos avançados. -EMPODERE O SEUS TIMES COM BITWARDEN -Os planos para times e empresas vêm com recursos profissionais para negócios. Alguns exemplos incluem integração SSO, auto-hospedagem, integração de diretório e provisionamento SCIM, políticas globais, acesso API, registro de eventos e mais. +EMPODERE SUAS EQUIPES COM O BITWARDEN +Os planos para Equipes e o Empresarial vêm com recursos empresariais profisionais. Alguns exemplos incluem integração com SSO, auto-hospedagem, integração de diretório e provisionamento de SCIM, políticas globais, acesso à API, registros de eventos, e mais. -Utilize o Bitwarden para proteger os seus colaboradores e compartilhar informações sensíveis com seus colegas. +Use o Bitwarden para protejer a sua força de trabalho e compartilhe informações sensíveis com colegas. -Mais razões para escolher Bitwarden: +Mais motivos para escolher o Bitwarden: -Criptografia mundialmente reconhecida -Suas senhas são protegidas com avançada criptografia ponta a ponta (AES-256, salted hashing, e PBKDF2 SHA-256) para que os seus dados estejam seguros e privados. +Criptografia reconhecida mundialmente +As senhas são protegidas com criptografia avançada de ponta a ponta (AES de 256 bits, hashing com salting, e PBKDF2 SHA-256) para que seus dados continuem seguros e privados. -Auditoria de Terceiros -A Bitwarden regularmente conduz auditorias de terceiros com notáveis empresas de segurança. Essas auditorias anuais incluem qualificação do código fonte e testes de invasão contra os IPs da Bitwarden, servidores e aplicações web. +Auditoria por terceiros +O Bitwarden conduz auditorias abrangentes de terceiros periodicamente com firmas de segurança nótaveis. Essas auditorias anuais incluem análise do código-fonte e teste de penetração entre IPs, servidores, e aplicativos web do Bitwarden. -2FA Avançado -Proteja o seu login com um autenticador de dois fatores, códigos de email ou credenciais FIDO2 WebAuthn como chave de segurança por hardware ou chave de acesso. +Autenticação de duas etapas avançada +Proteja sua autenticação com um autenticador de terceiros, códigos enviados por e-mail, ou credenciais de WebAuthn FIDO2, como uma chave de segurança física ou uma chave de acesso. -Envio Bitwarden -Transmita dados diretamente para outras pessoas enquanto mantém a segurança da criptografia ponta a ponta para limitar sua exposição. +Bitwarden Send +Transmita dados diretamente para outros enquanto mantém a segurança da criptografia de ponta a ponta, limitando exposição. -Gerador integrado -Crie diferentes senhas fortes e complexas e nomes de usuário únicos para cada site que visitar. Integre com provedores de email para privacidade adicional. +Gerador embutido +Crie senhas longas, complexas, e distintas, e nomes de usuário únicos para cada site que visita. Integre com provedores de alias de email para privacidade adicional. -Tradutores globais -As traduções da Bitwarden estão disponíveis para mais de 60 línguas, traduzidas pela comunidade global através do Crowdin. +Traduções globais +O Bitwarden tem traduções para mais de 60 idiomas, traduzidos pela comunidade global, pelo Crowdin. -Aplicações Multi-Plataforma -Proteja e compartilhe conteúdo confidencial dentro do Vault da Bitwarden de qualquer navegador, dispositivo móvel, desktop, entre outros. +Aplicativos multiplataforma +Proteja e compartilhe dados sensíveis dentro do seu cofre do Bitwarden de qualquer navegador, dispositivo móvel, sistema operacional de computador, e mais. -Bitwarden protege mais do que apenas senhas -Com nossas soluções em gerenciamento de credenciais criptografadas ponta a ponta, a Bitwarden empodera organizações para proteger qualquer informação, incluindo senhas de desenvolvedor e chaves de acesso. Visite Bitwarden.com para aprender mais sobre Bitwarden Secrets Manager e Bitwarden Passwordless.dev! +O Bitwarden protege mais do que só senhas +As soluções criptografadas de ponta a ponta de gerenciamento de credenciaisdo Bitwarden empoderam organizações a proteger tudo, incluindo segredos de desenvolvedor, e experiências com chaves de acesso. Visite bitwarden.com para aprender mais sobre o Bitwarden Gerenciador de Segredos e o Bitwarden Passworldless.dev! - Onde quer que você esteja, a Bitwarden protege facilmente todas as suas senha, chaves de acesso e informações sensíveis. + Onde quer que você esteja, a Bitwarden protege facilmente todas as suas senhas, chaves de acesso e informações sensíveis. - Sincronize e acesse o seu cofre através de múltiplos dispositivos + Sincronize e acesse o seu cofre entre vários dispositivos Gerencie todas as suas credenciais a partir de um cofre seguro - Autopreencha rapidamente as suas credenciais dentro de qualquer site que visitar + Preencha automaticamente as suas credenciais dentro de qualquer site que visitar O seu cofre é também acessível a partir do menu de contexto pelo clique no botão direito do mouse @@ -186,6 +186,6 @@ Com nossas soluções em gerenciamento de credenciais criptografadas ponta a pon Gera automaticamente senhas fortes, aleatórias e seguras - A sua informação é gerenciada com segurança utilizando encriptação AES-256 bits + A sua informação é gerenciada com segurança utilizando criptografia de AES de 256 bits diff --git a/apps/browser/tailwind.config.js b/apps/browser/tailwind.config.js index 134001bbf13..faaa7fa4128 100644 --- a/apps/browser/tailwind.config.js +++ b/apps/browser/tailwind.config.js @@ -12,5 +12,6 @@ config.content = [ "../../libs/vault/src/**/*.{html,ts}", "../../libs/pricing/src/**/*.{html,ts}", ]; +config.corePlugins.preflight = true; module.exports = config; diff --git a/apps/cli/package.json b/apps/cli/package.json index 63aa8a2360b..0ae4bf9ce30 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2025.11.0", + "version": "2025.12.0", "keywords": [ "bitwarden", "password", @@ -75,20 +75,20 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.3", + "koa": "3.1.1", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lowdb": "1.0.0", "lunr": "2.3.9", "multer": "2.0.2", "node-fetch": "2.6.12", - "node-forge": "1.3.1", - "open": "10.1.2", + "node-forge": "1.3.2", + "open": "11.0.0", "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", "semver": "7.7.3", - "tldts": "7.0.18", + "tldts": "7.0.19", "zxcvbn": "4.4.2" } } diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 661e052fb72..d8859318b52 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -113,20 +113,14 @@ export class LoginCommand { } else if (options.sso != null && this.canInteract) { // If the optional Org SSO Identifier isn't provided, the option value is `true`. const orgSsoIdentifier = options.sso === true ? null : options.sso; - const passwordOptions: any = { - type: "password", - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; - const state = await this.passwordGenerationService.generatePassword(passwordOptions); - ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + const ssoPromptData = await this.makeSsoPromptData(); + ssoCodeVerifier = ssoPromptData.ssoCodeVerifier; try { - const ssoParams = await this.openSsoPrompt(codeChallenge, state, orgSsoIdentifier); + const ssoParams = await this.openSsoPrompt( + ssoPromptData.codeChallenge, + ssoPromptData.state, + orgSsoIdentifier, + ); ssoCode = ssoParams.ssoCode; orgIdentifier = ssoParams.orgIdentifier; } catch { @@ -231,9 +225,43 @@ export class LoginCommand { new PasswordLoginCredentials(email, password, twoFactor), ); } + + // Begin Acting on initial AuthResult + if (response.requiresEncryptionKeyMigration) { return Response.error(this.i18nService.t("legacyEncryptionUnsupported")); } + + // Opting for not checking feature flag since the server will not respond with + // SsoOrganizationIdentifier if the feature flag is not enabled. + if (response.requiresSso && this.canInteract) { + const ssoPromptData = await this.makeSsoPromptData(); + ssoCodeVerifier = ssoPromptData.ssoCodeVerifier; + try { + const ssoParams = await this.openSsoPrompt( + ssoPromptData.codeChallenge, + ssoPromptData.state, + response.ssoOrganizationIdentifier, + ); + ssoCode = ssoParams.ssoCode; + orgIdentifier = ssoParams.orgIdentifier; + if (ssoCode != null && ssoCodeVerifier != null) { + response = await this.loginStrategyService.logIn( + new SsoLoginCredentials( + ssoCode, + ssoCodeVerifier, + this.ssoRedirectUri, + orgIdentifier, + undefined, // email to look up 2FA token not required as CLI can't remember 2FA token + twoFactor, + ), + ); + } + } catch { + return Response.badRequest("Something went wrong. Try again."); + } + } + if (response.requiresTwoFactor) { const twoFactorProviders = await this.twoFactorService.getSupportedProviders(null); if (twoFactorProviders.length === 0) { @@ -279,6 +307,10 @@ export class LoginCommand { if (twoFactorToken == null && selectedProvider.type === TwoFactorProviderType.Email) { const emailReq = new TwoFactorEmailRequest(); emailReq.email = await this.loginStrategyService.getEmail(); + // if the user was logging in with SSO, we need to include the SSO session token + if (response.ssoEmail2FaSessionToken != null) { + emailReq.ssoEmail2FaSessionToken = response.ssoEmail2FaSessionToken; + } emailReq.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash(); await this.twoFactorApiService.postTwoFactorEmail(emailReq); } @@ -324,6 +356,7 @@ export class LoginCommand { response = await this.loginStrategyService.logInNewDeviceVerification(newDeviceToken); } + // We check response two factor again here since MFA could fail based on the logic on ln 226 if (response.requiresTwoFactor) { return Response.error("Login failed."); } @@ -692,6 +725,27 @@ export class LoginCommand { }; } + /// Generate SSO prompt data: code verifier, code challenge, and state + private async makeSsoPromptData(): Promise<{ + ssoCodeVerifier: string; + codeChallenge: string; + state: string; + }> { + 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); + return { ssoCodeVerifier, codeChallenge, state }; + } + private async openSsoPrompt( codeChallenge: string, state: string, diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 14a218c7141..d95e8333dca 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -186,15 +186,15 @@ export class EditCommand { return Response.notFound(); } - let folderView = await folder.decrypt(); + const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId)); + let folderView = await folder.decrypt(userKey); folderView = FolderExport.toView(req, folderView); - const userKey = await this.keyService.getUserKey(activeUserId); const encFolder = await this.folderService.encrypt(folderView, userKey); try { const folder = await this.folderApiService.save(encFolder, activeUserId); const updatedFolder = new Folder(folder); - const decFolder = await updatedFolder.decrypt(); + const decFolder = await updatedFolder.decrypt(userKey); const res = new FolderResponse(decFolder); return Response.success(res); } catch (e) { diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 93e711d748f..35816b56fb2 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -417,10 +417,11 @@ export class GetCommand extends DownloadCommand { private async getFolder(id: string) { let decFolder: FolderView = null; const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId)); if (Utils.isGuid(id)) { const folder = await this.folderService.getFromState(id, activeUserId); if (folder != null) { - decFolder = await folder.decrypt(); + decFolder = await folder.decrypt(userKey); } } else if (id.trim() !== "") { let folders = await this.folderService.getAllDecryptedFromState(activeUserId); diff --git a/apps/cli/src/key-management/session-timeout/services/cli-session-timeout-type.service.ts b/apps/cli/src/key-management/session-timeout/services/cli-session-timeout-type.service.ts new file mode 100644 index 00000000000..8143b37b8a3 --- /dev/null +++ b/apps/cli/src/key-management/session-timeout/services/cli-session-timeout-type.service.ts @@ -0,0 +1,15 @@ +import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout"; +import { + VaultTimeout, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; + +export class CliSessionTimeoutTypeService implements SessionTimeoutTypeService { + async isAvailable(timeout: VaultTimeout): Promise { + return timeout === VaultTimeoutStringType.Never; + } + + async getOrPromoteToAvailable(_: VaultTimeout): Promise { + return VaultTimeoutStringType.Never; + } +} diff --git a/apps/cli/src/platform/services/cli-platform-utils.service.ts b/apps/cli/src/platform/services/cli-platform-utils.service.ts index 27f8e5268bd..acd2009a6d9 100644 --- a/apps/cli/src/platform/services/cli-platform-utils.service.ts +++ b/apps/cli/src/platform/services/cli-platform-utils.service.ts @@ -152,4 +152,8 @@ export class CliPlatformUtilsService implements PlatformUtilsService { getAutofillKeyboardShortcut(): Promise { return null; } + + async packageType(): Promise { + return null; + } } diff --git a/apps/cli/src/platform/services/cli-sdk-load.service.ts b/apps/cli/src/platform/services/cli-sdk-load.service.ts index 638e64a8214..13a4c19d51d 100644 --- a/apps/cli/src/platform/services/cli-sdk-load.service.ts +++ b/apps/cli/src/platform/services/cli-sdk-load.service.ts @@ -3,6 +3,8 @@ import * as sdk from "@bitwarden/sdk-internal"; export class CliSdkLoadService extends SdkLoadService { async load(): Promise { + // CLI uses stdout for user interaction / automations so we cannot log info / debug here. + SdkLoadService.logLevel = sdk.LogLevel.Error; const module = await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm"); (sdk as any).init(module); } diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index a5eeac22e52..3e5b5678629 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -278,6 +278,11 @@ export class Program extends BaseProgram { }) .option("--check", "Check lock status.", async () => { await this.exitIfNotAuthed(); + const userId = (await firstValueFrom(this.serviceContainer.accountService.activeAccount$)) + ?.id; + await this.serviceContainer.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet( + userId, + ); const authStatus = await this.serviceContainer.authService.getAuthStatus(); if (authStatus === AuthenticationStatus.Unlocked) { diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index cca19815fc5..2d4ea7d00b5 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -211,6 +211,7 @@ import { import { CliBiometricsService } from "../key-management/cli-biometrics-service"; import { CliProcessReloadService } from "../key-management/cli-process-reload.service"; +import { CliSessionTimeoutTypeService } from "../key-management/session-timeout/services/cli-session-timeout-type.service"; import { flagEnabled } from "../platform/flags"; import { CliPlatformUtilsService } from "../platform/services/cli-platform-utils.service"; import { CliSdkLoadService } from "../platform/services/cli-sdk-load.service"; @@ -491,10 +492,7 @@ export class ServiceContainer { const pinStateService = new PinStateService(this.stateProvider); this.pinService = new PinService( - this.accountService, this.encryptService, - this.kdfConfigService, - this.keyGenerationService, this.logService, this.keyService, this.sdkService, @@ -523,7 +521,13 @@ export class ServiceContainer { this.ssoUrlService = new SsoUrlService(); this.organizationService = new DefaultOrganizationService(this.stateProvider); - this.policyService = new DefaultPolicyService(this.stateProvider, this.organizationService); + this.policyService = new DefaultPolicyService( + this.stateProvider, + this.organizationService, + this.accountService, + ); + + const sessionTimeoutTypeService = new CliSessionTimeoutTypeService(); this.vaultTimeoutSettingsService = new DefaultVaultTimeoutSettingsService( this.accountService, @@ -536,6 +540,7 @@ export class ServiceContainer { this.stateProvider, this.logService, VaultTimeoutStringType.Never, // default vault timeout + sessionTimeoutTypeService, ); const refreshAccessTokenErrorCallback = () => { @@ -900,7 +905,7 @@ export class ServiceContainer { this.collectionService, this.keyService, this.encryptService, - this.pinService, + this.keyGenerationService, this.accountService, this.restrictedItemTypesService, ); @@ -908,7 +913,7 @@ export class ServiceContainer { this.individualExportService = new IndividualVaultExportService( this.folderService, this.cipherService, - this.pinService, + this.keyGenerationService, this.keyService, this.encryptService, this.cryptoFunctionService, @@ -922,7 +927,7 @@ export class ServiceContainer { this.organizationExportService = new OrganizationVaultExportService( this.cipherService, this.vaultExportApiService, - this.pinService, + this.keyGenerationService, this.keyService, this.encryptService, this.cryptoFunctionService, diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts index 6c643e04cd0..33bf4518ccd 100644 --- a/apps/cli/src/tools/send/send.program.ts +++ b/apps/cli/src/tools/send/send.program.ts @@ -308,7 +308,7 @@ export class SendProgram extends BaseProgram { let sendFile = null; let sendText = null; let name = Utils.newGuid(); - let type = SendType.Text; + let type: SendType = SendType.Text; if (options.file != null) { data = path.resolve(data); if (!fs.existsSync(data)) { diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 5602c593942..d826766dc65 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -181,12 +181,12 @@ export class CreateCommand { private async createFolder(req: FolderExport) { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const userKey = await this.keyService.getUserKey(activeUserId); + const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId)); const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey); try { const folderData = await this.folderApiService.save(folder, activeUserId); const newFolder = new Folder(folderData); - const decFolder = await newFolder.decrypt(); + const decFolder = await newFolder.decrypt(userKey); const res = new FolderResponse(decFolder); return Response.success(res); } catch (e) { diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index a1cdb9f26eb..5978659f21e 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "aead" version = "0.5.2" @@ -114,9 +99,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arboard" @@ -138,14 +123,14 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +checksum = "da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd" dependencies = [ "enumflags2", "futures-channel", "futures-util", - "rand 0.9.1", + "rand 0.9.2", "serde", "serde_repr", "tokio", @@ -347,23 +332,8 @@ dependencies = [ "mockall", "serial_test", "tracing", - "windows 0.61.1", - "windows-core 0.61.0", -] - -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", + "windows", + "windows-core", ] [[package]] @@ -457,7 +427,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "windows 0.61.1", + "windows", ] [[package]] @@ -501,6 +471,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "byteorder" version = "1.5.0" @@ -509,9 +485,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "camino" @@ -556,9 +532,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.46" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "shlex", @@ -614,7 +590,7 @@ dependencies = [ "hex", "oo7", "pbkdf2", - "rand 0.9.1", + "rand 0.9.2", "rusqlite", "security-framework", "serde", @@ -623,7 +599,7 @@ dependencies = [ "tokio", "tracing", "verifysign", - "windows 0.61.1", + "windows", ] [[package]] @@ -639,9 +615,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -649,9 +625,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -709,9 +685,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" dependencies = [ "unicode-segmentation", ] @@ -770,16 +746,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "ctor" version = "0.5.0" @@ -867,7 +833,7 @@ dependencies = [ "memsec", "oo7", "pin-project", - "rand 0.9.1", + "rand 0.9.2", "scopeguard", "secmem-proc", "security-framework", @@ -877,13 +843,13 @@ dependencies = [ "sha2", "ssh-key", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", "typenum", "widestring", - "windows 0.61.1", + "windows", "windows-future", "zbus", "zbus_polkit", @@ -1409,17 +1375,11 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "goblin" @@ -1499,14 +1459,14 @@ dependencies = [ [[package]] name = "homedir" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bdbbd5bc8c5749697ccaa352fa45aff8730cf21c68029c0eef1ffed7c3d6ba2" +checksum = "68df315d2857b2d8d2898be54a85e1d001bbbe0dbb5f8ef847b48dd3a23c4527" dependencies = [ "cfg-if", - "nix 0.29.0", + "nix", "widestring", - "windows 0.57.0", + "windows", ] [[package]] @@ -1663,6 +1623,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1674,9 +1644,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libloading" @@ -1685,7 +1655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-targets 0.48.5", ] [[package]] @@ -1841,15 +1811,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" -dependencies = [ - "adler2", -] - [[package]] name = "mio" version = "1.0.3" @@ -1863,9 +1824,9 @@ dependencies = [ [[package]] name = "mockall" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +checksum = "f58d964098a5f9c6b63d0798e5372fd04708193510a7af313c22e9f29b7b620b" dependencies = [ "cfg-if", "downcast", @@ -1877,9 +1838,9 @@ dependencies = [ [[package]] name = "mockall_derive" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +checksum = "ca41ce716dda6a9be188b385aa78ee5260fc25cd3802cb2a8afdc6afbe6b6dbf" dependencies = [ "cfg-if", "proc-macro2", @@ -1889,32 +1850,33 @@ dependencies = [ [[package]] name = "napi" -version = "2.16.17" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" +checksum = "f1b74e3dce5230795bb4d2821b941706dee733c7308752507254b0497f39cad7" dependencies = [ "bitflags", - "ctor 0.2.9", - "napi-derive", + "ctor", + "napi-build", "napi-sys", - "once_cell", + "nohash-hasher", + "rustc-hash", "tokio", ] [[package]] name = "napi-build" -version = "2.2.0" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03acbfa4f156a32188bfa09b86dc11a431b5725253fc1fc6f6df5bed273382c4" +checksum = "dcae8ad5609d14afb3a3b91dee88c757016261b151e9dcecabf1b2a31a6cab14" [[package]] name = "napi-derive" -version = "2.16.13" +version = "3.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +checksum = "7552d5a579b834614bbd496db5109f1b9f1c758f08224b0dee1e408333adf0d0" dependencies = [ - "cfg-if", "convert_case", + "ctor", "napi-derive-backend", "proc-macro2", "quote", @@ -1923,40 +1885,26 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "1.0.75" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +checksum = "5f6a81ac7486b70f2532a289603340862c06eea5a1e650c1ffeda2ce1238516a" dependencies = [ "convert_case", - "once_cell", "proc-macro2", "quote", - "regex", "semver", "syn", ] [[package]] name = "napi-sys" -version = "2.4.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +checksum = "3e4e7135a8f97aa0f1509cce21a8a1f9dcec1b50d8dee006b48a5adb69a9d64d" dependencies = [ "libloading", ] -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nix" version = "0.30.1" @@ -1970,6 +1918,12 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -2173,15 +2127,6 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -2190,9 +2135,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oo7" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb23d3ec3527d65a83be1c1795cb883c52cfa57147d42acc797127df56fc489" +checksum = "e3299dd401feaf1d45afd8fd1c0586f10fcfb22f244bb9afa942cec73503b89d" dependencies = [ "aes", "ashpd", @@ -2208,7 +2153,7 @@ dependencies = [ "num", "num-bigint-dig", "pbkdf2", - "rand 0.9.1", + "rand 0.9.2", "serde", "sha2", "subtle", @@ -2548,7 +2493,7 @@ dependencies = [ name = "process_isolation" version = "0.0.0" dependencies = [ - "ctor 0.5.0", + "ctor", "desktop_core", "libc", "tracing", @@ -2591,9 +2536,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -2660,19 +2605,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "thiserror 2.0.17", ] [[package]] @@ -2748,10 +2681,10 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "rustc-hash" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -2798,6 +2731,12 @@ dependencies = [ "rustix 1.0.7", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" @@ -2870,15 +2809,15 @@ dependencies = [ "libc", "rustix 1.0.7", "rustix-linux-procfs", - "thiserror 2.0.12", - "windows 0.61.1", + "thiserror 2.0.17", + "windows", ] [[package]] name = "security-framework" -version = "3.5.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags", "core-foundation", @@ -3068,12 +3007,12 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.9" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -3188,16 +3127,16 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.35.0" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b897c8ea620e181c7955369a31be5f48d9a9121cb59fd33ecef9ff2a34323422" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows 0.61.1", + "windows", ] [[package]] @@ -3239,11 +3178,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.17", ] [[package]] @@ -3259,9 +3198,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -3289,11 +3228,10 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.0" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", "libc", "mio", @@ -3303,14 +3241,14 @@ dependencies = [ "socket2", "tokio-macros", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -3319,9 +3257,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -3680,6 +3618,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -3745,6 +3694,51 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + [[package]] name = "wayland-backend" version = "0.3.10" @@ -3852,16 +3846,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.61.1" @@ -3869,7 +3853,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" dependencies = [ "windows-collections", - "windows-core 0.61.0", + "windows-core", "windows-future", "windows-link 0.1.3", "windows-numerics", @@ -3881,19 +3865,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.0", -] - -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-core", ] [[package]] @@ -3902,8 +3874,8 @@ version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", + "windows-implement", + "windows-interface", "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", @@ -3915,21 +3887,10 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" dependencies = [ - "windows-core 0.61.0", + "windows-core", "windows-link 0.1.3", ] -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-implement" version = "0.60.0" @@ -3941,17 +3902,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-interface" version = "0.59.1" @@ -3981,7 +3931,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.0", + "windows-core", "windows-link 0.1.3", ] @@ -3996,15 +3946,6 @@ dependencies = [ "windows-strings 0.5.1", ] -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -4262,8 +4203,8 @@ name = "windows_plugin_authenticator" version = "0.0.0" dependencies = [ "hex", - "windows 0.61.1", - "windows-core 0.61.0", + "windows", + "windows-core", ] [[package]] @@ -4434,9 +4375,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" +checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" dependencies = [ "async-broadcast", "async-executor", @@ -4452,14 +4393,15 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix 0.30.1", + "nix", "ordered-stream", "serde", "serde_repr", "tokio", "tracing", "uds_windows", - "windows-sys 0.60.2", + "uuid", + "windows-sys 0.61.2", "winnow", "zbus_macros", "zbus_names", @@ -4468,9 +4410,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" +checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 0b09daa9bdd..26f791fd660 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -21,13 +21,13 @@ publish = false [workspace.dependencies] aes = "=0.8.4" aes-gcm = "=0.10.3" -anyhow = "=1.0.94" +anyhow = "=1.0.100" arboard = { version = "=3.6.1", default-features = false } -ashpd = "=0.11.0" +ashpd = "=0.12.0" base64 = "=0.22.1" bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "a641316227227f8777fdf56ac9fa2d6b5f7fe662" } byteorder = "=1.5.0" -bytes = "=1.10.1" +bytes = "=1.11.0" cbc = "=0.1.2" chacha20poly1305 = "=0.10.1" core-foundation = "=0.10.1" @@ -37,33 +37,33 @@ ed25519 = "=2.2.3" embed_plist = "=1.2.2" futures = "=0.3.31" hex = "=0.4.3" -homedir = "=0.3.4" +homedir = "=0.3.6" interprocess = "=2.2.1" -libc = "=0.2.177" +libc = "=0.2.178" linux-keyutils = "=0.2.4" memsec = "=0.7.0" -napi = "=2.16.17" -napi-build = "=2.2.0" -napi-derive = "=2.16.13" -oo7 = "=0.4.3" +napi = "=3.3.0" +napi-build = "=2.2.3" +napi-derive = "=3.2.5" +oo7 = "=0.5.0" pin-project = "=1.1.10" pkcs8 = "=0.10.2" -rand = "=0.9.1" +rand = "=0.9.2" rsa = "=0.9.6" russh-cryptovec = "=0.7.3" scopeguard = "=1.2.0" secmem-proc = "=0.3.7" -security-framework = "=3.5.0" +security-framework = "=3.5.1" security-framework-sys = "=2.15.0" serde = "=1.0.209" serde_json = "=1.0.127" sha2 = "=0.10.8" ssh-encoding = "=0.2.0" ssh-key = { version = "=0.6.7", default-features = false } -sysinfo = "=0.35.0" -thiserror = "=2.0.12" -tokio = "=1.45.0" -tokio-util = "=0.7.13" +sysinfo = "=0.37.2" +thiserror = "=2.0.17" +tokio = "=1.48.0" +tokio-util = "=0.7.17" tracing = "=0.1.41" tracing-subscriber = { version = "=0.3.20", features = [ "fmt", @@ -77,7 +77,7 @@ windows = "=0.61.1" windows-core = "=0.61.0" windows-future = "=0.2.0" windows-registry = "=0.6.1" -zbus = "=5.11.0" +zbus = "=5.12.0" zbus_polkit = "=5.0.0" zeroizing-alloc = "=0.1.0" diff --git a/apps/desktop/desktop_native/autotype/Cargo.toml b/apps/desktop/desktop_native/autotype/Cargo.toml index 267074d0bc8..580df30e72d 100644 --- a/apps/desktop/desktop_native/autotype/Cargo.toml +++ b/apps/desktop/desktop_native/autotype/Cargo.toml @@ -9,7 +9,7 @@ publish.workspace = true anyhow = { workspace = true } [target.'cfg(windows)'.dependencies] -mockall = "=0.13.1" +mockall = "=0.14.0" serial_test = "=3.2.0" tracing.workspace = true windows = { workspace = true, features = [ diff --git a/apps/desktop/desktop_native/autotype/src/windows/type_input.rs b/apps/desktop/desktop_native/autotype/src/windows/type_input.rs index 10f30f5ee4f..b2f4c6b82df 100644 --- a/apps/desktop/desktop_native/autotype/src/windows/type_input.rs +++ b/apps/desktop/desktop_native/autotype/src/windows/type_input.rs @@ -272,6 +272,7 @@ mod tests { #[serial] fn send_input_succeeds() { let ctxi = MockInputOperations::send_input_context(); + ctxi.checkpoint(); ctxi.expect().returning(|_| 1); send_input::(vec![build_unicode_input( @@ -279,6 +280,8 @@ mod tests { 0, )]) .unwrap(); + + drop(ctxi); } #[test] @@ -288,9 +291,11 @@ mod tests { )] fn send_input_fails_sent_zero() { let ctxi = MockInputOperations::send_input_context(); + ctxi.checkpoint(); ctxi.expect().returning(|_| 0); let ctxge = MockErrorOperations::get_last_error_context(); + ctxge.checkpoint(); ctxge.expect().returning(|| WIN32_ERROR(1)); send_input::(vec![build_unicode_input( @@ -298,6 +303,9 @@ mod tests { 0, )]) .unwrap(); + + drop(ctxge); + drop(ctxi); } #[test] @@ -305,9 +313,11 @@ mod tests { #[should_panic(expected = "SendInput does not match expected. sent: 2, expected: 1")] fn send_input_fails_sent_mismatch() { let ctxi = MockInputOperations::send_input_context(); + ctxi.checkpoint(); ctxi.expect().returning(|_| 2); let ctxge = MockErrorOperations::get_last_error_context(); + ctxge.checkpoint(); ctxge.expect().returning(|| WIN32_ERROR(1)); send_input::(vec![build_unicode_input( @@ -315,5 +325,8 @@ mod tests { 0, )]) .unwrap(); + + drop(ctxge); + drop(ctxi); } } diff --git a/apps/desktop/desktop_native/autotype/src/windows/window_title.rs b/apps/desktop/desktop_native/autotype/src/windows/window_title.rs index d56a811ab5c..4fc0b3bb3ad 100644 --- a/apps/desktop/desktop_native/autotype/src/windows/window_title.rs +++ b/apps/desktop/desktop_native/autotype/src/windows/window_title.rs @@ -186,6 +186,7 @@ mod tests { let mut mock_handle = MockWindowHandleOperations::new(); let ctxse = MockErrorOperations::set_last_error_context(); + ctxse.checkpoint(); ctxse .expect() .once() @@ -198,6 +199,7 @@ mod tests { .returning(|| Ok(0)); let ctxge = MockErrorOperations::get_last_error_context(); + ctxge.checkpoint(); ctxge.expect().returning(|| WIN32_ERROR(0)); let len = get_window_title_length::( @@ -206,6 +208,9 @@ mod tests { .unwrap(); assert_eq!(len, 0); + + drop(ctxge); + drop(ctxse); } #[test] @@ -215,6 +220,7 @@ mod tests { let mut mock_handle = MockWindowHandleOperations::new(); let ctxse = MockErrorOperations::set_last_error_context(); + ctxse.checkpoint(); ctxse.expect().with(predicate::eq(0)).returning(|_| {}); mock_handle @@ -223,13 +229,18 @@ mod tests { .returning(|| Ok(0)); let ctxge = MockErrorOperations::get_last_error_context(); + ctxge.checkpoint(); ctxge.expect().returning(|| WIN32_ERROR(1)); get_window_title_length::(&mock_handle) .unwrap(); + + drop(ctxge); + drop(ctxse); } #[test] + #[serial] fn get_window_title_succeeds() { let mut mock_handle = MockWindowHandleOperations::new(); @@ -246,11 +257,11 @@ mod tests { .unwrap(); assert_eq!(title.len(), 43); // That extra slot in the buffer for null char - assert_eq!(title, "*******************************************"); } #[test] + #[serial] fn get_window_title_returns_empty_string() { let mock_handle = MockWindowHandleOperations::new(); @@ -273,10 +284,13 @@ mod tests { .returning(|_| Ok(0)); let ctxge = MockErrorOperations::get_last_error_context(); + ctxge.checkpoint(); ctxge.expect().returning(|| WIN32_ERROR(1)); get_window_title::(&mock_handle, 42) .unwrap(); + + drop(ctxge); } #[test] @@ -290,9 +304,12 @@ mod tests { .returning(|_| Ok(0)); let ctxge = MockErrorOperations::get_last_error_context(); + ctxge.checkpoint(); ctxge.expect().returning(|| WIN32_ERROR(0)); get_window_title::(&mock_handle, 42) .unwrap(); + + drop(ctxge); } } diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml index 6455142023a..ff641731661 100644 --- a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml @@ -11,7 +11,7 @@ publish.workspace = true aes-gcm = { workspace = true } chacha20poly1305 = { workspace = true } chromium_importer = { path = "../chromium_importer" } -clap = { version = "=4.5.51", features = ["derive"] } +clap = { version = "=4.5.53", features = ["derive"] } scopeguard = { workspace = true } sysinfo = { workspace = true } windows = { workspace = true, features = [ diff --git a/apps/desktop/desktop_native/build.js b/apps/desktop/desktop_native/build.js index a7ed89a9c17..54a6dba8326 100644 --- a/apps/desktop/desktop_native/build.js +++ b/apps/desktop/desktop_native/build.js @@ -11,8 +11,8 @@ const rustTargetsMap = { "aarch64-pc-windows-msvc": { nodeArch: 'arm64', platform: 'win32' }, "x86_64-apple-darwin": { nodeArch: 'x64', platform: 'darwin' }, "aarch64-apple-darwin": { nodeArch: 'arm64', platform: 'darwin' }, - 'x86_64-unknown-linux-musl': { nodeArch: 'x64', platform: 'linux' }, - 'aarch64-unknown-linux-musl': { nodeArch: 'arm64', platform: 'linux' }, + 'x86_64-unknown-linux-gnu': { nodeArch: 'x64', platform: 'linux' }, + 'aarch64-unknown-linux-gnu': { nodeArch: 'arm64', platform: 'linux' }, } // Ensure the dist directory exists @@ -113,8 +113,8 @@ if (process.platform === "linux") { platformTargets.forEach(([target, _]) => { installTarget(target); - buildNapiModule(target); - buildProxyBin(target); - buildImporterBinaries(target); + buildNapiModule(target, mode === "release"); + buildProxyBin(target, mode === "release"); + buildImporterBinaries(target, mode === "release"); buildProcessIsolation(); }); diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs index e57b40b5778..7011a2cce63 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs @@ -61,8 +61,8 @@ impl InstalledBrowserRetriever for DefaultInstalledBrowserRetriever { let mut browsers = Vec::with_capacity(SUPPORTED_BROWSER_MAP.len()); for (browser, config) in SUPPORTED_BROWSER_MAP.iter() { - let data_dir = get_browser_data_dir(config)?; - if data_dir.exists() { + let data_dir = get_and_validate_data_dir(config); + if data_dir.is_ok() { browsers.push((*browser).to_string()); } } @@ -114,7 +114,7 @@ pub async fn import_logins( #[derive(Debug, Clone, Copy)] pub(crate) struct BrowserConfig { pub name: &'static str, - pub data_dir: &'static str, + pub data_dir: &'static [&'static str], } pub(crate) static SUPPORTED_BROWSER_MAP: LazyLock< @@ -126,11 +126,19 @@ pub(crate) static SUPPORTED_BROWSER_MAP: LazyLock< .collect::>() }); -fn get_browser_data_dir(config: &BrowserConfig) -> Result { - let dir = dirs::home_dir() - .ok_or_else(|| anyhow!("Home directory not found"))? - .join(config.data_dir); - Ok(dir) +fn get_and_validate_data_dir(config: &BrowserConfig) -> Result { + for data_dir in config.data_dir.iter() { + let dir = dirs::home_dir() + .ok_or_else(|| anyhow!("Home directory not found"))? + .join(data_dir); + if dir.exists() { + return Ok(dir); + } + } + Err(anyhow!( + "Browser user data directory '{:?}' not found", + config.data_dir + )) } // @@ -174,13 +182,7 @@ fn load_local_state_for_browser(browser_name: &String) -> Result<(PathBuf, Local .get(browser_name.as_str()) .ok_or_else(|| anyhow!("Unsupported browser: {}", browser_name))?; - let data_dir = get_browser_data_dir(config)?; - if !data_dir.exists() { - return Err(anyhow!( - "Browser user data directory '{}' not found", - data_dir.display() - )); - } + let data_dir = get_and_validate_data_dir(config)?; let local_state = load_local_state(&data_dir)?; diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs index 14e38797640..f542e23129a 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs @@ -18,19 +18,22 @@ use crate::{ pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[ BrowserConfig { name: "Chrome", - data_dir: ".config/google-chrome", + data_dir: &[".config/google-chrome"], }, BrowserConfig { name: "Chromium", - data_dir: "snap/chromium/common/chromium", + data_dir: &["snap/chromium/common/chromium"], }, BrowserConfig { name: "Brave", - data_dir: "snap/brave/current/.config/BraveSoftware/Brave-Browser", + data_dir: &[ + "snap/brave/current/.config/BraveSoftware/Brave-Browser", + ".config/BraveSoftware/Brave-Browser", + ], }, BrowserConfig { name: "Opera", - data_dir: "snap/opera/current/.config/opera", + data_dir: &["snap/opera/current/.config/opera", ".config/opera"], }, ]; diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs index 5d0b4f0c75c..6cd746d60b6 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs @@ -14,31 +14,31 @@ use crate::{ pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[ BrowserConfig { name: "Chrome", - data_dir: "Library/Application Support/Google/Chrome", + data_dir: &["Library/Application Support/Google/Chrome"], }, BrowserConfig { name: "Chromium", - data_dir: "Library/Application Support/Chromium", + data_dir: &["Library/Application Support/Chromium"], }, BrowserConfig { name: "Microsoft Edge", - data_dir: "Library/Application Support/Microsoft Edge", + data_dir: &["Library/Application Support/Microsoft Edge"], }, BrowserConfig { name: "Brave", - data_dir: "Library/Application Support/BraveSoftware/Brave-Browser", + data_dir: &["Library/Application Support/BraveSoftware/Brave-Browser"], }, BrowserConfig { name: "Arc", - data_dir: "Library/Application Support/Arc/User Data", + data_dir: &["Library/Application Support/Arc/User Data"], }, BrowserConfig { name: "Opera", - data_dir: "Library/Application Support/com.operasoftware.Opera", + data_dir: &["Library/Application Support/com.operasoftware.Opera"], }, BrowserConfig { name: "Vivaldi", - data_dir: "Library/Application Support/Vivaldi", + data_dir: &["Library/Application Support/Vivaldi"], }, ]; diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs index 9cc89ed2161..524b5994873 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs @@ -25,27 +25,27 @@ pub use signature::*; pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[ BrowserConfig { name: "Brave", - data_dir: "AppData/Local/BraveSoftware/Brave-Browser/User Data", + data_dir: &["AppData/Local/BraveSoftware/Brave-Browser/User Data"], }, BrowserConfig { name: "Chrome", - data_dir: "AppData/Local/Google/Chrome/User Data", + data_dir: &["AppData/Local/Google/Chrome/User Data"], }, BrowserConfig { name: "Chromium", - data_dir: "AppData/Local/Chromium/User Data", + data_dir: &["AppData/Local/Chromium/User Data"], }, BrowserConfig { name: "Microsoft Edge", - data_dir: "AppData/Local/Microsoft/Edge/User Data", + data_dir: &["AppData/Local/Microsoft/Edge/User Data"], }, BrowserConfig { name: "Opera", - data_dir: "AppData/Roaming/Opera Software/Opera Stable", + data_dir: &["AppData/Roaming/Opera Software/Opera Stable"], }, BrowserConfig { name: "Vivaldi", - data_dir: "AppData/Local/Vivaldi/User Data", + data_dir: &["AppData/Local/Vivaldi/User Data"], }, ]; diff --git a/apps/desktop/desktop_native/chromium_importer/src/metadata.rs b/apps/desktop/desktop_native/chromium_importer/src/metadata.rs index 114c9f8df84..9aa2cea6e5e 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/metadata.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/metadata.rs @@ -7,9 +7,9 @@ pub struct NativeImporterMetadata { /// Identifies the importer pub id: String, /// Describes the strategies used to obtain imported data - pub loaders: Vec<&'static str>, + pub loaders: Vec, /// Identifies the instructions for the importer - pub instructions: &'static str, + pub instructions: String, } /// Returns a map of supported importers based on the current platform. @@ -36,9 +36,9 @@ pub fn get_supported_importers( PLATFORM_SUPPORTED_BROWSERS.iter().map(|b| b.name).collect(); for (id, browser_name) in IMPORTERS { - let mut loaders: Vec<&'static str> = vec!["file"]; + let mut loaders: Vec = vec!["file".to_string()]; if supported.contains(browser_name) { - loaders.push("chromium"); + loaders.push("chromium".to_string()); } if installed_browsers.contains(&browser_name.to_string()) { @@ -47,7 +47,7 @@ pub fn get_supported_importers( NativeImporterMetadata { id: id.to_string(), loaders, - instructions: "chromium", + instructions: "chromium".to_string(), }, ); } @@ -79,12 +79,9 @@ mod tests { map.keys().cloned().collect() } - fn get_loaders( - map: &HashMap, - id: &str, - ) -> HashSet<&'static str> { + fn get_loaders(map: &HashMap, id: &str) -> HashSet { map.get(id) - .map(|m| m.loaders.iter().copied().collect::>()) + .map(|m| m.loaders.iter().cloned().collect::>()) .unwrap_or_default() } @@ -107,7 +104,7 @@ mod tests { for (key, meta) in map.iter() { assert_eq!(&meta.id, key); assert_eq!(meta.instructions, "chromium"); - assert!(meta.loaders.iter().any(|l| *l == "file")); + assert!(meta.loaders.contains(&"file".to_owned())); } } @@ -147,7 +144,7 @@ mod tests { for (key, meta) in map.iter() { assert_eq!(&meta.id, key); assert_eq!(meta.instructions, "chromium"); - assert!(meta.loaders.iter().any(|l| *l == "file")); + assert!(meta.loaders.contains(&"file".to_owned())); } } @@ -183,7 +180,7 @@ mod tests { for (key, meta) in map.iter() { assert_eq!(&meta.id, key); assert_eq!(meta.instructions, "chromium"); - assert!(meta.loaders.iter().any(|l| *l == "file")); + assert!(meta.loaders.contains(&"file".to_owned())); } } diff --git a/apps/desktop/desktop_native/core/src/biometric/mod.rs b/apps/desktop/desktop_native/core/src/biometric/mod.rs index 937c67ff30a..6ed5fbe08a7 100644 --- a/apps/desktop/desktop_native/core/src/biometric/mod.rs +++ b/apps/desktop/desktop_native/core/src/biometric/mod.rs @@ -3,16 +3,12 @@ use anyhow::{anyhow, Result}; #[allow(clippy::module_inception)] #[cfg_attr(target_os = "linux", path = "unix.rs")] -#[cfg_attr(target_os = "macos", path = "macos.rs")] -#[cfg_attr(target_os = "windows", path = "windows.rs")] +#[cfg_attr(target_os = "macos", path = "unimplemented.rs")] +#[cfg_attr(target_os = "windows", path = "unimplemented.rs")] mod biometric; -pub use biometric::Biometric; - -#[cfg(target_os = "windows")] -pub mod windows_focus; - use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; +pub use biometric::Biometric; use sha2::{Digest, Sha256}; use crate::crypto::{self, CipherString}; diff --git a/apps/desktop/desktop_native/core/src/biometric/macos.rs b/apps/desktop/desktop_native/core/src/biometric/unimplemented.rs similarity index 94% rename from apps/desktop/desktop_native/core/src/biometric/macos.rs rename to apps/desktop/desktop_native/core/src/biometric/unimplemented.rs index ec09d566e1f..3f3d034924a 100644 --- a/apps/desktop/desktop_native/core/src/biometric/macos.rs +++ b/apps/desktop/desktop_native/core/src/biometric/unimplemented.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Result}; use crate::biometric::{KeyMaterial, OsDerivedKey}; -/// The MacOS implementation of the biometric trait. +/// Unimplemented stub for unsupported platforms pub struct Biometric {} impl super::BiometricTrait for Biometric { diff --git a/apps/desktop/desktop_native/core/src/biometric/windows.rs b/apps/desktop/desktop_native/core/src/biometric/windows.rs deleted file mode 100644 index f72282d9284..00000000000 --- a/apps/desktop/desktop_native/core/src/biometric/windows.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::{ffi::c_void, str::FromStr}; - -use anyhow::{anyhow, Result}; -use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; -use rand::RngCore; -use sha2::{Digest, Sha256}; -use windows::{ - core::{factory, HSTRING}, - Security::Credentials::UI::{ - UserConsentVerificationResult, UserConsentVerifier, UserConsentVerifierAvailability, - }, - Win32::{ - Foundation::HWND, System::WinRT::IUserConsentVerifierInterop, - UI::WindowsAndMessaging::GetForegroundWindow, - }, -}; -use windows_future::IAsyncOperation; - -use super::{decrypt, encrypt, windows_focus::set_focus}; -use crate::{ - biometric::{KeyMaterial, OsDerivedKey}, - crypto::CipherString, -}; - -/// The Windows OS implementation of the biometric trait. -pub struct Biometric {} - -impl super::BiometricTrait for Biometric { - // FIXME: Remove unwraps! They panic and terminate the whole application. - #[allow(clippy::unwrap_used)] - async fn prompt(hwnd: Vec, message: String) -> Result { - let h = isize::from_le_bytes(hwnd.clone().try_into().unwrap()); - - let h = h as *mut c_void; - let window = HWND(h); - - // The Windows Hello prompt is displayed inside the application window. For best result we - // should set the window to the foreground and focus it. - set_focus(window); - - // Windows Hello prompt must be in foreground, focused, otherwise the face or fingerprint - // unlock will not work. We get the current foreground window, which will either be the - // Bitwarden desktop app or the browser extension. - let foreground_window = unsafe { GetForegroundWindow() }; - - let interop = factory::()?; - let operation: IAsyncOperation = unsafe { - interop.RequestVerificationForWindowAsync(foreground_window, &HSTRING::from(message))? - }; - let result = operation.get()?; - - match result { - UserConsentVerificationResult::Verified => Ok(true), - _ => Ok(false), - } - } - - async fn available() -> Result { - let ucv_available = UserConsentVerifier::CheckAvailabilityAsync()?.get()?; - - match ucv_available { - UserConsentVerifierAvailability::Available => Ok(true), - // TODO: look into removing this and making the check more ad-hoc - UserConsentVerifierAvailability::DeviceBusy => Ok(true), - _ => Ok(false), - } - } - - fn derive_key_material(challenge_str: Option<&str>) -> Result { - let challenge: [u8; 16] = match challenge_str { - Some(challenge_str) => base64_engine - .decode(challenge_str)? - .try_into() - .map_err(|e: Vec<_>| anyhow!("Expect length {}, got {}", 16, e.len()))?, - None => random_challenge(), - }; - - // Uses a key derived from the iv. This key is not intended to add any security - // but only a place-holder - let key = Sha256::digest(challenge); - let key_b64 = base64_engine.encode(key); - let iv_b64 = base64_engine.encode(challenge); - Ok(OsDerivedKey { key_b64, iv_b64 }) - } - - async fn set_biometric_secret( - service: &str, - account: &str, - secret: &str, - key_material: Option, - iv_b64: &str, - ) -> Result { - let key_material = key_material.ok_or(anyhow!( - "Key material is required for Windows Hello protected keys" - ))?; - - let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; - crate::password::set_password(service, account, &encrypted_secret).await?; - Ok(encrypted_secret) - } - - async fn get_biometric_secret( - service: &str, - account: &str, - key_material: Option, - ) -> Result { - let key_material = key_material.ok_or(anyhow!( - "Key material is required for Windows Hello protected keys" - ))?; - - let encrypted_secret = crate::password::get_password(service, account).await?; - match CipherString::from_str(&encrypted_secret) { - Ok(secret) => { - // If the secret is a CipherString, it is encrypted and we need to decrypt it. - let secret = decrypt(&secret, &key_material)?; - Ok(secret) - } - Err(_) => { - // If the secret is not a CipherString, it is not encrypted and we can return it - // directly. - Ok(encrypted_secret) - } - } - } -} - -fn random_challenge() -> [u8; 16] { - let mut challenge = [0u8; 16]; - rand::rng().fill_bytes(&mut challenge); - challenge -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::biometric::BiometricTrait; - - #[test] - fn test_derive_key_material() { - let iv_input = "l9fhDUP/wDJcKwmEzcb/3w=="; - let result = ::derive_key_material(Some(iv_input)).unwrap(); - let key = base64_engine.decode(result.key_b64).unwrap(); - assert_eq!(key.len(), 32); - assert_eq!(result.iv_b64, iv_input) - } - - #[test] - fn test_derive_key_material_no_iv() { - let result = ::derive_key_material(None).unwrap(); - let key = base64_engine.decode(result.key_b64).unwrap(); - assert_eq!(key.len(), 32); - let iv = base64_engine.decode(result.iv_b64).unwrap(); - assert_eq!(iv.len(), 16); - } - - #[tokio::test] - #[cfg(feature = "manual_test")] - async fn test_prompt() { - ::prompt( - vec![0, 0, 0, 0, 0, 0, 0, 0], - String::from("Hello from Rust"), - ) - .await - .unwrap(); - } - - #[tokio::test] - #[cfg(feature = "manual_test")] - async fn test_available() { - assert!(::available().await.unwrap()) - } - - #[tokio::test] - #[cfg(feature = "manual_test")] - async fn get_biometric_secret_requires_key() { - let result = ::get_biometric_secret("", "", None).await; - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "Key material is required for Windows Hello protected keys" - ); - } - - #[tokio::test] - #[cfg(feature = "manual_test")] - async fn get_biometric_secret_handles_unencrypted_secret() { - let test = "test"; - let secret = "password"; - let key_material = KeyMaterial { - os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), - client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), - }; - crate::password::set_password(test, test, secret) - .await - .unwrap(); - let result = - ::get_biometric_secret(test, test, Some(key_material)) - .await - .unwrap(); - crate::password::delete_password("test", "test") - .await - .unwrap(); - assert_eq!(result, secret); - } - - #[tokio::test] - #[cfg(feature = "manual_test")] - async fn get_biometric_secret_handles_encrypted_secret() { - let test = "test"; - let secret = - CipherString::from_str("0.l9fhDUP/wDJcKwmEzcb/3w==|uP4LcqoCCj5FxBDP77NV6Q==").unwrap(); // output from test_encrypt - let key_material = KeyMaterial { - os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), - client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), - }; - crate::password::set_password(test, test, &secret.to_string()) - .await - .unwrap(); - - let result = - ::get_biometric_secret(test, test, Some(key_material)) - .await - .unwrap(); - crate::password::delete_password("test", "test") - .await - .unwrap(); - assert_eq!(result, "secret"); - } - - #[tokio::test] - async fn set_biometric_secret_requires_key() { - let result = - ::set_biometric_secret("", "", "", None, "").await; - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "Key material is required for Windows Hello protected keys" - ); - } -} diff --git a/apps/desktop/desktop_native/core/src/biometric/windows_focus.rs b/apps/desktop/desktop_native/core/src/biometric/windows_focus.rs deleted file mode 100644 index ce51f82862d..00000000000 --- a/apps/desktop/desktop_native/core/src/biometric/windows_focus.rs +++ /dev/null @@ -1,28 +0,0 @@ -use windows::{ - core::s, - Win32::{ - Foundation::HWND, - UI::{ - Input::KeyboardAndMouse::SetFocus, - WindowsAndMessaging::{FindWindowA, SetForegroundWindow}, - }, - }, -}; - -/// Searches for a window that looks like a security prompt and set it as focused. -/// Only works when the process has permission to foreground, either by being in foreground -/// Or by being given foreground permission https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setforegroundwindow#remarks -pub fn focus_security_prompt() { - let class_name = s!("Credential Dialog Xaml Host"); - let hwnd = unsafe { FindWindowA(class_name, None) }; - if let Ok(hwnd) = hwnd { - set_focus(hwnd); - } -} - -pub(crate) fn set_focus(window: HWND) { - unsafe { - let _ = SetForegroundWindow(window); - let _ = SetFocus(Some(window)); - } -} diff --git a/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs b/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs index 32d2eb7e6e6..669dd757c40 100644 --- a/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs +++ b/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs @@ -285,8 +285,8 @@ async fn windows_hello_authenticate_with_crypto( return Err(anyhow!("Failed to sign data")); } - let signature_buffer = signature.Result()?; - let signature_value = unsafe { as_mut_bytes(&signature_buffer)? }; + let mut signature_buffer = signature.Result()?; + let signature_value = unsafe { as_mut_bytes(&mut signature_buffer)? }; // The signature is deterministic based on the challenge and keychain key. Thus, it can be // hashed to a key. It is unclear what entropy this key provides. @@ -368,7 +368,7 @@ fn decrypt_data( Ok(plaintext) } -unsafe fn as_mut_bytes(buffer: &IBuffer) -> Result<&mut [u8]> { +unsafe fn as_mut_bytes(buffer: &mut IBuffer) -> Result<&mut [u8]> { let interop = buffer.cast::()?; unsafe { diff --git a/apps/desktop/desktop_native/macos_provider/Cargo.toml b/apps/desktop/desktop_native/macos_provider/Cargo.toml index 50f1834851d..8a34460268a 100644 --- a/apps/desktop/desktop_native/macos_provider/Cargo.toml +++ b/apps/desktop/desktop_native/macos_provider/Cargo.toml @@ -24,7 +24,7 @@ serde_json = { workspace = true } tokio = { workspace = true, features = ["sync"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } -tracing-oslog = "0.3.0" +tracing-oslog = "=0.3.0" [build-dependencies] uniffi = { workspace = true, features = ["build"] } diff --git a/apps/desktop/desktop_native/macos_provider/README.md b/apps/desktop/desktop_native/macos_provider/README.md new file mode 100644 index 00000000000..1d4c1902465 --- /dev/null +++ b/apps/desktop/desktop_native/macos_provider/README.md @@ -0,0 +1,35 @@ +# Explainer: Mac OS Native Passkey Provider + +This document describes the changes introduced in https://github.com/bitwarden/clients/pull/13963, where we introduce the MacOS Native Passkey Provider. It gives the high level explanation of the architecture and some of the quirks and additional good to know context. + +## The high level +MacOS has native APIs (similar to iOS) to allow Credential Managers to provide credentials to the MacOS autofill system (in the PR referenced above, we only provide passkeys). + +We’ve written a Swift-based native autofill-extension. It’s bundled in the app-bundle in PlugIns, similar to the safari-extension. + +This swift extension currently communicates with our Electron app through IPC based on a unix socket. The IPC implementation is done in Rust and utilized through UniFFI + NAPI bindings. + +Footnotes: + +* We're not using the IPC framework as the implementation pre-dates the IPC framework. +* Alternatives like XPC or CFMessagePort may have better support for when the app is sandboxed. + +Electron receives the messages and passes it to Angular (through the electron-renderer event system). + +Our existing fido2 services in the renderer respond to events, displaying UI as necessary, and returns the signature back through the same mechanism, allowing people to authenticate with passkeys through the native system + UI. See [Mac OS Native Passkey Workflows](https://bitwarden.atlassian.net/wiki/spaces/EN/pages/1828356098/Mac+OS+Native+Passkey+Workflows) for demo videos. + +## Typescript + UI implementations + +We utilize the same FIDO2 implementation and interface that is already present for our browser authentication. It was designed by @coroiu with multiple ‘ui environments' in mind. + +Therefore, a lot of the plumbing is implemented in /autofill/services/desktop-fido2-user-interface.service.ts, which implements the interface that our fido2 authenticator/client expects to drive UI related behaviors. + +We’ve also implemented a couple FIDO2 UI components to handle registration/sign in flows, but also improved the “modal mode” of the desktop app. + +## Modal mode + +When modal mode is activated, the desktop app turns into a smaller modal that is always on top and cannot be resized. This is done to improve the UX of performing a passkey operation (or SSH operation). Once the operation is completed, the app returns to normal mode and its previous position. + +We are not using electron modal windows, for a couple reason. It would require us to send data in yet another layer of IPC, but also because we'd need to bootstrap entire renderer/app instead of reusing the existing window. + +Some modal modes may hide the 'traffic buttons' (window controls) due to design requirements. diff --git a/apps/desktop/desktop_native/macos_provider/build.sh b/apps/desktop/desktop_native/macos_provider/build.sh index 21e2e045af4..2f7a2d03541 100755 --- a/apps/desktop/desktop_native/macos_provider/build.sh +++ b/apps/desktop/desktop_native/macos_provider/build.sh @@ -8,6 +8,9 @@ rm -r tmp mkdir -p ./tmp/target/universal-darwin/release/ +rustup target add aarch64-apple-darwin +rustup target add x86_64-apple-darwin + cargo build --package macos_provider --target aarch64-apple-darwin --release cargo build --package macos_provider --target x86_64-apple-darwin --release diff --git a/apps/desktop/desktop_native/macos_provider/src/lib.rs b/apps/desktop/desktop_native/macos_provider/src/lib.rs index a5a134b0bfe..8619a77a0f2 100644 --- a/apps/desktop/desktop_native/macos_provider/src/lib.rs +++ b/apps/desktop/desktop_native/macos_provider/src/lib.rs @@ -57,6 +57,14 @@ trait Callback: Send + Sync { fn error(&self, error: BitwardenError); } +#[derive(uniffi::Enum, Debug)] +/// Store the connection status between the macOS credential provider extension +/// and the desktop application's IPC server. +pub enum ConnectionStatus { + Connected, + Disconnected, +} + #[derive(uniffi::Object)] pub struct MacOSProviderClient { to_server_send: tokio::sync::mpsc::Sender, @@ -65,8 +73,24 @@ pub struct MacOSProviderClient { response_callbacks_counter: AtomicU32, #[allow(clippy::type_complexity)] response_callbacks_queue: Arc, Instant)>>>, + + // Flag to track connection status - atomic for thread safety without locks + connection_status: Arc, } +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +/// Store native desktop status information to use for IPC communication +/// between the application and the macOS credential provider. +pub struct NativeStatus { + key: String, + value: String, +} + +// In our callback management, 0 is a reserved sequence number indicating that a message does not +// have a callback. +const NO_CALLBACK_INDICATOR: u32 = 0; + #[uniffi::export] impl MacOSProviderClient { // FIXME: Remove unwraps! They panic and terminate the whole application. @@ -93,13 +117,16 @@ impl MacOSProviderClient { let client = MacOSProviderClient { to_server_send, - response_callbacks_counter: AtomicU32::new(0), + response_callbacks_counter: AtomicU32::new(1), /* Start at 1 since 0 is reserved for + * "no callback" scenarios */ response_callbacks_queue: Arc::new(Mutex::new(HashMap::new())), + connection_status: Arc::new(std::sync::atomic::AtomicBool::new(false)), }; let path = desktop_core::ipc::path("af"); let queue = client.response_callbacks_queue.clone(); + let connection_status = client.connection_status.clone(); std::thread::spawn(move || { let rt = tokio::runtime::Builder::new_current_thread() @@ -117,9 +144,11 @@ impl MacOSProviderClient { match serde_json::from_str::(&message) { Ok(SerializedMessage::Command(CommandMessage::Connected)) => { info!("Connected to server"); + connection_status.store(true, std::sync::atomic::Ordering::Relaxed); } Ok(SerializedMessage::Command(CommandMessage::Disconnected)) => { info!("Disconnected from server"); + connection_status.store(false, std::sync::atomic::Ordering::Relaxed); } Ok(SerializedMessage::Message { sequence_number, @@ -157,12 +186,17 @@ impl MacOSProviderClient { client } + pub fn send_native_status(&self, key: String, value: String) { + let status = NativeStatus { key, value }; + self.send_message(status, None); + } + pub fn prepare_passkey_registration( &self, request: PasskeyRegistrationRequest, callback: Arc, ) { - self.send_message(request, Box::new(callback)); + self.send_message(request, Some(Box::new(callback))); } pub fn prepare_passkey_assertion( @@ -170,7 +204,7 @@ impl MacOSProviderClient { request: PasskeyAssertionRequest, callback: Arc, ) { - self.send_message(request, Box::new(callback)); + self.send_message(request, Some(Box::new(callback))); } pub fn prepare_passkey_assertion_without_user_interface( @@ -178,7 +212,18 @@ impl MacOSProviderClient { request: PasskeyAssertionWithoutUserInterfaceRequest, callback: Arc, ) { - self.send_message(request, Box::new(callback)); + self.send_message(request, Some(Box::new(callback))); + } + + pub fn get_connection_status(&self) -> ConnectionStatus { + let is_connected = self + .connection_status + .load(std::sync::atomic::Ordering::Relaxed); + if is_connected { + ConnectionStatus::Connected + } else { + ConnectionStatus::Disconnected + } } } @@ -200,7 +245,6 @@ enum SerializedMessage { } impl MacOSProviderClient { - // FIXME: Remove unwraps! They panic and terminate the whole application. #[allow(clippy::unwrap_used)] fn add_callback(&self, callback: Box) -> u32 { let sequence_number = self @@ -209,20 +253,23 @@ impl MacOSProviderClient { self.response_callbacks_queue .lock() - .unwrap() + .expect("response callbacks queue mutex should not be poisoned") .insert(sequence_number, (callback, Instant::now())); sequence_number } - // FIXME: Remove unwraps! They panic and terminate the whole application. #[allow(clippy::unwrap_used)] fn send_message( &self, message: impl Serialize + DeserializeOwned, - callback: Box, + callback: Option>, ) { - let sequence_number = self.add_callback(callback); + let sequence_number = if let Some(callback) = callback { + self.add_callback(callback) + } else { + NO_CALLBACK_INDICATOR + }; let message = serde_json::to_string(&SerializedMessage::Message { sequence_number, @@ -232,15 +279,17 @@ impl MacOSProviderClient { if let Err(e) = self.to_server_send.blocking_send(message) { // Make sure we remove the callback from the queue if we can't send the message - if let Some((cb, _)) = self - .response_callbacks_queue - .lock() - .unwrap() - .remove(&sequence_number) - { - cb.error(BitwardenError::Internal(format!( - "Error sending message: {e}" - ))); + if sequence_number != NO_CALLBACK_INDICATOR { + if let Some((callback, _)) = self + .response_callbacks_queue + .lock() + .expect("response callbacks queue mutex should not be poisoned") + .remove(&sequence_number) + { + callback.error(BitwardenError::Internal(format!( + "Error sending message: {e}" + ))); + } } } } diff --git a/apps/desktop/desktop_native/macos_provider/src/registration.rs b/apps/desktop/desktop_native/macos_provider/src/registration.rs index 9e697b75c16..c961566a86c 100644 --- a/apps/desktop/desktop_native/macos_provider/src/registration.rs +++ b/apps/desktop/desktop_native/macos_provider/src/registration.rs @@ -14,6 +14,7 @@ pub struct PasskeyRegistrationRequest { user_verification: UserVerification, supported_algorithms: Vec, window_xy: Position, + excluded_credentials: Vec>, } #[derive(uniffi::Record, Serialize, Deserialize)] diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 01bfa65d571..375c65edb8d 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -1,125 +1,7 @@ -/* tslint:disable */ -/* eslint-disable */ - /* auto-generated by NAPI-RS */ - -export declare namespace passwords { - /** The error message returned when a password is not found during retrieval or deletion. */ - export const PASSWORD_NOT_FOUND: string - /** - * Fetch the stored password from the keychain. - * Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. - */ - export function getPassword(service: string, account: string): Promise - /** - * Save the password to the keychain. Adds an entry if none exists otherwise updates the - * existing entry. - */ - export function setPassword(service: string, account: string, password: string): Promise - /** - * Delete the stored password from the keychain. - * Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. - */ - export function deletePassword(service: string, account: string): Promise - /** Checks if the os secure storage is available */ - export function isAvailable(): Promise -} -export declare namespace biometrics { - export function prompt(hwnd: Buffer, message: string): Promise - export function available(): Promise - export function setBiometricSecret(service: string, account: string, secret: string, keyMaterial: KeyMaterial | undefined | null, ivB64: string): Promise - /** - * Retrieves the biometric secret for the given service and account. - * Throws Error with message [`passwords::PASSWORD_NOT_FOUND`] if the secret does not exist. - */ - export function getBiometricSecret(service: string, account: string, keyMaterial?: KeyMaterial | undefined | null): Promise - /** - * Derives key material from biometric data. Returns a string encoded with a - * base64 encoded key and the base64 encoded challenge used to create it - * separated by a `|` character. - * - * If the iv is provided, it will be used as the challenge. Otherwise a random challenge will - * be generated. - * - * `format!("|")` - */ - export function deriveKeyMaterial(iv?: string | undefined | null): Promise - export interface KeyMaterial { - osKeyPartB64: string - clientKeyPartB64?: string - } - export interface OsDerivedKey { - keyB64: string - ivB64: string - } -} -export declare namespace biometrics_v2 { - export function initBiometricSystem(): BiometricLockSystem - export function authenticate(biometricLockSystem: BiometricLockSystem, hwnd: Buffer, message: string): Promise - export function authenticateAvailable(biometricLockSystem: BiometricLockSystem): Promise - export function enrollPersistent(biometricLockSystem: BiometricLockSystem, userId: string, key: Buffer): Promise - export function provideKey(biometricLockSystem: BiometricLockSystem, userId: string, key: Buffer): Promise - export function unlock(biometricLockSystem: BiometricLockSystem, userId: string, hwnd: Buffer): Promise - export function unlockAvailable(biometricLockSystem: BiometricLockSystem, userId: string): Promise - export function hasPersistent(biometricLockSystem: BiometricLockSystem, userId: string): Promise - export function unenroll(biometricLockSystem: BiometricLockSystem, userId: string): Promise - export class BiometricLockSystem { } -} -export declare namespace clipboards { - export function read(): Promise - export function write(text: string, password: boolean): Promise -} -export declare namespace sshagent { - export interface PrivateKey { - privateKey: string - name: string - cipherId: string - } - export interface SshKey { - privateKey: string - publicKey: string - keyFingerprint: string - } - export interface SshUiRequest { - cipherId?: string - isList: boolean - processName: string - isForwarding: boolean - namespace?: string - } - export function serve(callback: (err: Error | null, arg: SshUiRequest) => any): Promise - export function stop(agentState: SshAgentState): void - export function isRunning(agentState: SshAgentState): boolean - export function setKeys(agentState: SshAgentState, newKeys: Array): void - export function lock(agentState: SshAgentState): void - export function clearKeys(agentState: SshAgentState): void - export class SshAgentState { } -} -export declare namespace processisolations { - export function disableCoredumps(): Promise - export function isCoreDumpingDisabled(): Promise - export function isolateProcess(): Promise -} -export declare namespace powermonitors { - export function onLock(callback: (err: Error | null, ) => any): Promise - export function isLockMonitorAvailable(): Promise -} -export declare namespace windows_registry { - export function createKey(key: string, subkey: string, value: string): Promise - export function deleteKey(key: string, subkey: string): Promise -} -export declare namespace ipc { - export interface IpcMessage { - clientId: number - kind: IpcMessageType - message?: string - } - export const enum IpcMessageType { - Connected = 0, - Disconnected = 1, - Message = 2 - } - export class IpcServer { +/* eslint-disable */ +export declare namespace autofill { + export class AutofillIpcServer { /** * Create and start the IPC server without blocking. * @@ -127,49 +9,18 @@ export declare namespace ipc { * connection and must be the same for both the server and client. @param callback * This function will be called whenever a message is received from a client. */ - static listen(name: string, callback: (error: null | Error, message: IpcMessage) => void): Promise + static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void, assertionWithoutUserInterfaceCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void, nativeStatusCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: NativeStatus) => void): Promise /** Return the path to the IPC server. */ getPath(): string /** Stop the IPC server. */ stop(): void - /** - * Send a message over the IPC server to all the connected clients - * - * @return The number of clients that the message was sent to. Note that the number of - * messages actually received may be less, as some clients could disconnect before - * receiving the message. - */ - send(message: string): number + completeRegistration(clientId: number, sequenceNumber: number, response: PasskeyRegistrationResponse): number + completeAssertion(clientId: number, sequenceNumber: number, response: PasskeyAssertionResponse): number + completeError(clientId: number, sequenceNumber: number, error: string): number } -} -export declare namespace autostart { - export function setAutostart(autostart: boolean, params: Array): Promise -} -export declare namespace autofill { - export function runCommand(value: string): Promise - export const enum UserVerification { - Preferred = 'preferred', - Required = 'required', - Discouraged = 'discouraged' - } - export interface Position { - x: number - y: number - } - export interface PasskeyRegistrationRequest { - rpId: string - userName: string - userHandle: Array - clientDataHash: Array - userVerification: UserVerification - supportedAlgorithms: Array - windowXy: Position - } - export interface PasskeyRegistrationResponse { - rpId: string - clientDataHash: Array - credentialId: Array - attestationObject: Array + export interface NativeStatus { + key: string + value: string } export interface PasskeyAssertionRequest { rpId: string @@ -178,6 +29,14 @@ export declare namespace autofill { allowedCredentials: Array> windowXy: Position } + export interface PasskeyAssertionResponse { + rpId: string + userHandle: Array + signature: Array + clientDataHash: Array + authenticatorData: Array + credentialId: Array + } export interface PasskeyAssertionWithoutUserInterfaceRequest { rpId: string credentialId: Array @@ -188,50 +47,93 @@ export declare namespace autofill { userVerification: UserVerification windowXy: Position } - export interface PasskeyAssertionResponse { + export interface PasskeyRegistrationRequest { rpId: string + userName: string userHandle: Array - signature: Array clientDataHash: Array - authenticatorData: Array + userVerification: UserVerification + supportedAlgorithms: Array + windowXy: Position + excludedCredentials: Array> + } + export interface PasskeyRegistrationResponse { + rpId: string + clientDataHash: Array credentialId: Array + attestationObject: Array } - export class IpcServer { - /** - * Create and start the IPC server without blocking. - * - * @param name The endpoint name to listen on. This name uniquely identifies the IPC - * connection and must be the same for both the server and client. @param callback - * This function will be called whenever a message is received from a client. - */ - static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void, assertionWithoutUserInterfaceCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void): Promise - /** Return the path to the IPC server. */ - getPath(): string - /** Stop the IPC server. */ - stop(): void - completeRegistration(clientId: number, sequenceNumber: number, response: PasskeyRegistrationResponse): number - completeAssertion(clientId: number, sequenceNumber: number, response: PasskeyAssertionResponse): number - completeError(clientId: number, sequenceNumber: number, error: string): number + export interface Position { + x: number + y: number + } + export function runCommand(value: string): Promise + export const enum UserVerification { + Preferred = 'preferred', + Required = 'required', + Discouraged = 'discouraged' } } -export declare namespace passkey_authenticator { - export function register(): void + +export declare namespace autostart { + export function setAutostart(autostart: boolean, params: Array): Promise } -export declare namespace logging { - export const enum LogLevel { - Trace = 0, - Debug = 1, - Info = 2, - Warn = 3, - Error = 4 + +export declare namespace autotype { + export function getForegroundWindowTitle(): string + export function typeInput(input: Array, keyboardShortcut: Array): void +} + +export declare namespace biometrics { + export function available(): Promise + /** + * Derives key material from biometric data. Returns a string encoded with a + * base64 encoded key and the base64 encoded challenge used to create it + * separated by a `|` character. + * + * If the iv is provided, it will be used as the challenge. Otherwise a random challenge will + * be generated. + * + * `format!("|")` + */ + export function deriveKeyMaterial(iv?: string | undefined | null): Promise + /** + * Retrieves the biometric secret for the given service and account. + * Throws Error with message [`passwords::PASSWORD_NOT_FOUND`] if the secret does not exist. + */ + export function getBiometricSecret(service: string, account: string, keyMaterial?: KeyMaterial | undefined | null): Promise + export interface KeyMaterial { + osKeyPartB64: string + clientKeyPartB64?: string } - export function initNapiLog(jsLogFn: (err: Error | null, arg0: LogLevel, arg1: string) => any): void + export interface OsDerivedKey { + keyB64: string + ivB64: string + } + export function prompt(hwnd: Buffer, message: string): Promise + export function setBiometricSecret(service: string, account: string, secret: string, keyMaterial: KeyMaterial | undefined | null, ivB64: string): Promise } + +export declare namespace biometrics_v2 { + export class BiometricLockSystem { + + } + export function authenticate(biometricLockSystem: BiometricLockSystem, hwnd: Buffer, message: string): Promise + export function authenticateAvailable(biometricLockSystem: BiometricLockSystem): Promise + export function enrollPersistent(biometricLockSystem: BiometricLockSystem, userId: string, key: Buffer): Promise + export function hasPersistent(biometricLockSystem: BiometricLockSystem, userId: string): Promise + export function initBiometricSystem(): BiometricLockSystem + export function provideKey(biometricLockSystem: BiometricLockSystem, userId: string, key: Buffer): Promise + export function unenroll(biometricLockSystem: BiometricLockSystem, userId: string): Promise + export function unlock(biometricLockSystem: BiometricLockSystem, userId: string, hwnd: Buffer): Promise + export function unlockAvailable(biometricLockSystem: BiometricLockSystem, userId: string): Promise +} + export declare namespace chromium_importer { - export interface ProfileInfo { - id: string - name: string - } + export function getAvailableProfiles(browser: string): Array + /** Returns OS aware metadata describing supported Chromium based importers as a JSON string. */ + export function getMetadata(): Record + export function importLogins(browser: string, profileId: string): Promise> export interface Login { url: string username: string @@ -252,12 +154,130 @@ export declare namespace chromium_importer { loaders: Array instructions: string } - /** Returns OS aware metadata describing supported Chromium based importers as a JSON string. */ - export function getMetadata(): Record - export function getAvailableProfiles(browser: string): Array - export function importLogins(browser: string, profileId: string): Promise> + export interface ProfileInfo { + id: string + name: string + } } -export declare namespace autotype { - export function getForegroundWindowTitle(): string - export function typeInput(input: Array, keyboardShortcut: Array): void + +export declare namespace clipboards { + export function read(): Promise + export function write(text: string, password: boolean): Promise +} + +export declare namespace ipc { + export class NativeIpcServer { + /** + * Create and start the IPC server without blocking. + * + * @param name The endpoint name to listen on. This name uniquely identifies the IPC + * connection and must be the same for both the server and client. @param callback + * This function will be called whenever a message is received from a client. + */ + static listen(name: string, callback: (error: null | Error, message: IpcMessage) => void): Promise + /** Return the path to the IPC server. */ + getPath(): string + /** Stop the IPC server. */ + stop(): void + /** + * Send a message over the IPC server to all the connected clients + * + * @return The number of clients that the message was sent to. Note that the number of + * messages actually received may be less, as some clients could disconnect before + * receiving the message. + */ + send(message: string): number + } + export interface IpcMessage { + clientId: number + kind: IpcMessageType + message?: string + } + export const enum IpcMessageType { + Connected = 0, + Disconnected = 1, + Message = 2 + } +} + +export declare namespace logging { + export function initNapiLog(jsLogFn: ((err: Error | null, arg0: LogLevel, arg1: string) => any)): void + export const enum LogLevel { + Trace = 0, + Debug = 1, + Info = 2, + Warn = 3, + Error = 4 + } +} + +export declare namespace passkey_authenticator { + export function register(): void +} + +export declare namespace passwords { + /** + * Delete the stored password from the keychain. + * Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. + */ + export function deletePassword(service: string, account: string): Promise + /** + * Fetch the stored password from the keychain. + * Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. + */ + export function getPassword(service: string, account: string): Promise + /** Checks if the os secure storage is available */ + export function isAvailable(): Promise + /** The error message returned when a password is not found during retrieval or deletion. */ + export const PASSWORD_NOT_FOUND: string + /** + * Save the password to the keychain. Adds an entry if none exists otherwise updates the + * existing entry. + */ + export function setPassword(service: string, account: string, password: string): Promise +} + +export declare namespace powermonitors { + export function isLockMonitorAvailable(): Promise + export function onLock(callback: ((err: Error | null, ) => any)): Promise +} + +export declare namespace processisolations { + export function disableCoredumps(): Promise + export function isCoreDumpingDisabled(): Promise + export function isolateProcess(): Promise +} + +export declare namespace sshagent { + export class SshAgentState { + + } + export function clearKeys(agentState: SshAgentState): void + export function isRunning(agentState: SshAgentState): boolean + export function lock(agentState: SshAgentState): void + export interface PrivateKey { + privateKey: string + name: string + cipherId: string + } + export function serve(callback: ((err: Error | null, arg: SshUiRequest) => Promise)): Promise + export function setKeys(agentState: SshAgentState, newKeys: Array): void + export interface SshKey { + privateKey: string + publicKey: string + keyFingerprint: string + } + export interface SshUiRequest { + cipherId?: string + isList: boolean + processName: string + isForwarding: boolean + namespace?: string + } + export function stop(agentState: SshAgentState): void +} + +export declare namespace windows_registry { + export function createKey(key: string, subkey: string, value: string): Promise + export function deleteKey(key: string, subkey: string): Promise } diff --git a/apps/desktop/desktop_native/napi/index.js b/apps/desktop/desktop_native/napi/index.js index 64819be4405..0362d9ee2bb 100644 --- a/apps/desktop/desktop_native/napi/index.js +++ b/apps/desktop/desktop_native/napi/index.js @@ -82,20 +82,20 @@ switch (platform) { switch (arch) { case "x64": nativeBinding = loadFirstAvailable( - ["desktop_napi.linux-x64-musl.node", "desktop_napi.linux-x64-gnu.node"], - "@bitwarden/desktop-napi-linux-x64-musl", + ["desktop_napi.linux-x64-gnu.node"], + "@bitwarden/desktop-napi-linux-x64-gnu", ); break; case "arm64": nativeBinding = loadFirstAvailable( - ["desktop_napi.linux-arm64-musl.node", "desktop_napi.linux-arm64-gnu.node"], - "@bitwarden/desktop-napi-linux-arm64-musl", + ["desktop_napi.linux-arm64-gnu.node"], + "@bitwarden/desktop-napi-linux-arm64-gnu", ); break; case "arm": nativeBinding = loadFirstAvailable( - ["desktop_napi.linux-arm-musl.node", "desktop_napi.linux-arm-gnu.node"], - "@bitwarden/desktop-napi-linux-arm-musl", + ["desktop_napi.linux-arm-gnu.node"], + "@bitwarden/desktop-napi-linux-arm-gnu", ); localFileExisted = existsSync(join(__dirname, "desktop_napi.linux-arm-gnueabihf.node")); try { diff --git a/apps/desktop/desktop_native/napi/package.json b/apps/desktop/desktop_native/napi/package.json index ca17377c9f2..0717bfd53ea 100644 --- a/apps/desktop/desktop_native/napi/package.json +++ b/apps/desktop/desktop_native/napi/package.json @@ -9,21 +9,17 @@ "author": "", "license": "GPL-3.0", "devDependencies": { - "@napi-rs/cli": "2.18.4" + "@napi-rs/cli": "3.2.0" }, "napi": { - "name": "desktop_napi", - "triples": { - "defaults": true, - "additional": [ - "x86_64-unknown-linux-musl", - "aarch64-unknown-linux-gnu", - "i686-pc-windows-msvc", - "armv7-unknown-linux-gnueabihf", - "aarch64-apple-darwin", - "aarch64-unknown-linux-musl", - "aarch64-pc-windows-msvc" - ] - } + "binaryName": "desktop_napi", + "targets": [ + "aarch64-apple-darwin", + "aarch64-pc-windows-msvc", + "aarch64-unknown-linux-gnu", + "armv7-unknown-linux-gnueabihf", + "i686-pc-windows-msvc", + "x86_64-unknown-linux-gnu" + ] } } diff --git a/apps/desktop/desktop_native/napi/scripts/build.js b/apps/desktop/desktop_native/napi/scripts/build.js index a6680f5d311..ad24b99d2fb 100644 --- a/apps/desktop/desktop_native/napi/scripts/build.js +++ b/apps/desktop/desktop_native/napi/scripts/build.js @@ -2,13 +2,21 @@ const { execSync } = require('child_process'); const args = process.argv.slice(2); + const isRelease = args.includes('--release'); +const argsString = args.join(' '); + if (isRelease) { console.log('Building release mode.'); + + execSync(`napi build --platform --no-js ${argsString}`, { stdio: 'inherit'}); + } else { console.log('Building debug mode.'); - process.env.RUST_LOG = 'debug'; -} -execSync(`napi build --platform --js false`, { stdio: 'inherit', env: process.env }); + execSync(`napi build --platform --no-js ${argsString}`, { + stdio: 'inherit', + env: { ...process.env, RUST_LOG: 'debug' } + }); +} diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index c34e7574f68..fe084349501 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -290,7 +290,7 @@ pub mod sshagent { use napi::{ bindgen_prelude::Promise, - threadsafe_function::{ErrorStrategy::CalleeHandled, ThreadsafeFunction}, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; use tokio::{self, sync::Mutex}; use tracing::error; @@ -326,13 +326,15 @@ pub mod sshagent { #[allow(clippy::unused_async)] // FIXME: Remove unused async! #[napi] pub async fn serve( - callback: ThreadsafeFunction, + callback: ThreadsafeFunction>, ) -> napi::Result { let (auth_request_tx, mut auth_request_rx) = tokio::sync::mpsc::channel::(32); let (auth_response_tx, auth_response_rx) = tokio::sync::broadcast::channel::<(u32, bool)>(32); let auth_response_tx_arc = Arc::new(Mutex::new(auth_response_tx)); + // Wrap callback in Arc so it can be shared across spawned tasks + let callback = Arc::new(callback); tokio::spawn(async move { let _ = auth_response_rx; @@ -342,42 +344,50 @@ pub mod sshagent { tokio::spawn(async move { let auth_response_tx_arc = cloned_response_tx_arc; let callback = cloned_callback; - let promise_result: Result, napi::Error> = callback - .call_async(Ok(SshUIRequest { + // In NAPI v3, obtain the JS callback return as a Promise and await it + // in Rust + let (tx, rx) = std::sync::mpsc::channel::>(); + let status = callback.call_with_return_value( + Ok(SshUIRequest { cipher_id: request.cipher_id, is_list: request.is_list, process_name: request.process_name, is_forwarding: request.is_forwarding, namespace: request.namespace, - })) - .await; - match promise_result { - Ok(promise_result) => match promise_result.await { - Ok(result) => { - let _ = auth_response_tx_arc - .lock() - .await - .send((request.request_id, result)) - .expect("should be able to send auth response to agent"); - } - Err(e) => { - error!(error = %e, "Calling UI callback promise was rejected"); - let _ = auth_response_tx_arc - .lock() - .await - .send((request.request_id, false)) - .expect("should be able to send auth response to agent"); + }), + ThreadsafeFunctionCallMode::Blocking, + move |ret: Result, napi::Error>, _env| { + if let Ok(p) = ret { + let _ = tx.send(p); } + Ok(()) }, - Err(e) => { - error!(error = %e, "Calling UI callback could not create promise"); - let _ = auth_response_tx_arc - .lock() - .await - .send((request.request_id, false)) - .expect("should be able to send auth response to agent"); + ); + + let result = if status == napi::Status::Ok { + match rx.recv() { + Ok(promise) => match promise.await { + Ok(v) => v, + Err(e) => { + error!(error = %e, "UI callback promise rejected"); + false + } + }, + Err(e) => { + error!(error = %e, "Failed to receive UI callback promise"); + false + } } - } + } else { + error!(error = ?status, "Calling UI callback failed"); + false + }; + + let _ = auth_response_tx_arc + .lock() + .await + .send((request.request_id, result)) + .expect("should be able to send auth response to agent"); }); } }); @@ -465,14 +475,12 @@ pub mod processisolations { #[napi] pub mod powermonitors { use napi::{ - threadsafe_function::{ - ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode, - }, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, tokio, }; #[napi] - pub async fn on_lock(callback: ThreadsafeFunction<(), CalleeHandled>) -> napi::Result<()> { + pub async fn on_lock(callback: ThreadsafeFunction<()>) -> napi::Result<()> { let (tx, mut rx) = tokio::sync::mpsc::channel::<()>(32); desktop_core::powermonitor::on_lock(tx) .await @@ -511,9 +519,7 @@ pub mod windows_registry { #[napi] pub mod ipc { use desktop_core::ipc::server::{Message, MessageType}; - use napi::threadsafe_function::{ - ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode, - }; + use napi::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}; #[napi(object)] pub struct IpcMessage { @@ -550,12 +556,12 @@ pub mod ipc { } #[napi] - pub struct IpcServer { + pub struct NativeIpcServer { server: desktop_core::ipc::server::Server, } #[napi] - impl IpcServer { + impl NativeIpcServer { /// Create and start the IPC server without blocking. /// /// @param name The endpoint name to listen on. This name uniquely identifies the IPC @@ -566,7 +572,7 @@ pub mod ipc { pub async fn listen( name: String, #[napi(ts_arg_type = "(error: null | Error, message: IpcMessage) => void")] - callback: ThreadsafeFunction, + callback: ThreadsafeFunction, ) -> napi::Result { let (send, mut recv) = tokio::sync::mpsc::channel::(32); tokio::spawn(async move { @@ -583,7 +589,7 @@ pub mod ipc { )) })?; - Ok(IpcServer { server }) + Ok(NativeIpcServer { server }) } /// Return the path to the IPC server. @@ -630,8 +636,9 @@ pub mod autostart { #[napi] pub mod autofill { use desktop_core::ipc::server::{Message, MessageType}; - use napi::threadsafe_function::{ - ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode, + use napi::{ + bindgen_prelude::FnArgs, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use tracing::error; @@ -686,6 +693,7 @@ pub mod autofill { pub user_verification: UserVerification, pub supported_algorithms: Vec, pub window_xy: Position, + pub excluded_credentials: Vec>, } #[napi(object)] @@ -724,6 +732,14 @@ pub mod autofill { pub window_xy: Position, } + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct NativeStatus { + pub key: String, + pub value: String, + } + #[napi(object)] #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -737,14 +753,14 @@ pub mod autofill { } #[napi] - pub struct IpcServer { + pub struct AutofillIpcServer { server: desktop_core::ipc::server::Server, } // FIXME: Remove unwraps! They panic and terminate the whole application. #[allow(clippy::unwrap_used)] #[napi] - impl IpcServer { + impl AutofillIpcServer { /// Create and start the IPC server without blocking. /// /// @param name The endpoint name to listen on. This name uniquely identifies the IPC @@ -760,23 +776,24 @@ pub mod autofill { ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void" )] registration_callback: ThreadsafeFunction< - (u32, u32, PasskeyRegistrationRequest), - ErrorStrategy::CalleeHandled, + FnArgs<(u32, u32, PasskeyRegistrationRequest)>, >, #[napi( ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void" )] assertion_callback: ThreadsafeFunction< - (u32, u32, PasskeyAssertionRequest), - ErrorStrategy::CalleeHandled, + FnArgs<(u32, u32, PasskeyAssertionRequest)>, >, #[napi( ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void" )] assertion_without_user_interface_callback: ThreadsafeFunction< - (u32, u32, PasskeyAssertionWithoutUserInterfaceRequest), - ErrorStrategy::CalleeHandled, + FnArgs<(u32, u32, PasskeyAssertionWithoutUserInterfaceRequest)>, >, + #[napi( + ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: NativeStatus) => void" + )] + native_status_callback: ThreadsafeFunction<(u32, u32, NativeStatus)>, ) -> napi::Result { let (send, mut recv) = tokio::sync::mpsc::channel::(32); tokio::spawn(async move { @@ -801,7 +818,7 @@ pub mod autofill { Ok(msg) => { let value = msg .value - .map(|value| (client_id, msg.sequence_number, value)) + .map(|value| (client_id, msg.sequence_number, value).into()) .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); assertion_callback @@ -820,7 +837,7 @@ pub mod autofill { Ok(msg) => { let value = msg .value - .map(|value| (client_id, msg.sequence_number, value)) + .map(|value| (client_id, msg.sequence_number, value).into()) .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); assertion_without_user_interface_callback @@ -838,7 +855,7 @@ pub mod autofill { Ok(msg) => { let value = msg .value - .map(|value| (client_id, msg.sequence_number, value)) + .map(|value| (client_id, msg.sequence_number, value).into()) .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); registration_callback .call(value, ThreadsafeFunctionCallMode::NonBlocking); @@ -849,6 +866,21 @@ pub mod autofill { } } + match serde_json::from_str::>(&message) { + Ok(msg) => { + let value = msg + .value + .map(|value| (client_id, msg.sequence_number, value)) + .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); + native_status_callback + .call(value, ThreadsafeFunctionCallMode::NonBlocking); + continue; + } + Err(error) => { + error!(%error, "Unable to deserialze native status."); + } + } + error!(message, "Received an unknown message2"); } } @@ -863,7 +895,7 @@ pub mod autofill { )) })?; - Ok(IpcServer { server }) + Ok(AutofillIpcServer { server }) } /// Return the path to the IPC server. @@ -956,8 +988,9 @@ pub mod logging { use std::{fmt::Write, sync::OnceLock}; - use napi::threadsafe_function::{ - ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode, + use napi::{ + bindgen_prelude::FnArgs, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; use tracing::Level; use tracing_subscriber::{ @@ -968,7 +1001,7 @@ pub mod logging { Layer, }; - struct JsLogger(OnceLock>); + struct JsLogger(OnceLock>>); static JS_LOGGER: JsLogger = JsLogger(OnceLock::new()); #[napi] @@ -1040,13 +1073,13 @@ pub mod logging { let msg = (event.metadata().level().into(), buffer); if let Some(logger) = JS_LOGGER.0.get() { - let _ = logger.call(Ok(msg), ThreadsafeFunctionCallMode::NonBlocking); + let _ = logger.call(Ok(msg.into()), ThreadsafeFunctionCallMode::NonBlocking); }; } } #[napi] - pub fn init_napi_log(js_log_fn: ThreadsafeFunction<(LogLevel, String), CalleeHandled>) { + pub fn init_napi_log(js_log_fn: ThreadsafeFunction>) { let _ = JS_LOGGER.0.set(js_log_fn); // the log level hierarchy is determined by: @@ -1117,8 +1150,8 @@ pub mod chromium_importer { #[napi(object)] pub struct NativeImporterMetadata { pub id: String, - pub loaders: Vec<&'static str>, - pub instructions: &'static str, + pub loaders: Vec, + pub instructions: String, } impl From<_LoginImportResult> for LoginImportResult { @@ -1195,7 +1228,7 @@ pub mod chromium_importer { #[napi] pub mod autotype { #[napi] - pub fn get_foreground_window_title() -> napi::Result { + pub fn get_foreground_window_title() -> napi::Result { autotype::get_foreground_window_title().map_err(|_| { napi::Error::from_reason( "Autotype Error: failed to get foreground window title".to_string(), diff --git a/apps/desktop/desktop_native/objc/Cargo.toml b/apps/desktop/desktop_native/objc/Cargo.toml index 5ef791fb586..dd808537c28 100644 --- a/apps/desktop/desktop_native/objc/Cargo.toml +++ b/apps/desktop/desktop_native/objc/Cargo.toml @@ -14,8 +14,8 @@ tokio = { workspace = true } tracing = { workspace = true } [target.'cfg(target_os = "macos")'.build-dependencies] -cc = "=1.2.46" -glob = "=0.3.2" +cc = "=1.2.49" +glob = "=0.3.3" [lints] workspace = true diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m index fc13c04591a..037a97c7590 100644 --- a/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m +++ b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m @@ -14,40 +14,64 @@ void runSync(void* context, NSDictionary *params) { // Map credentials to ASPasswordCredential objects NSMutableArray *mappedCredentials = [NSMutableArray arrayWithCapacity:credentials.count]; + for (NSDictionary *credential in credentials) { - NSString *type = credential[@"type"]; - - if ([type isEqualToString:@"password"]) { - NSString *cipherId = credential[@"cipherId"]; - NSString *uri = credential[@"uri"]; - NSString *username = credential[@"username"]; - - ASCredentialServiceIdentifier *serviceId = [[ASCredentialServiceIdentifier alloc] - initWithIdentifier:uri type:ASCredentialServiceIdentifierTypeURL]; - ASPasswordCredentialIdentity *credential = [[ASPasswordCredentialIdentity alloc] - initWithServiceIdentifier:serviceId user:username recordIdentifier:cipherId]; - - [mappedCredentials addObject:credential]; - } - - if (@available(macos 14, *)) { - if ([type isEqualToString:@"fido2"]) { + @try { + NSString *type = credential[@"type"]; + + if ([type isEqualToString:@"password"]) { NSString *cipherId = credential[@"cipherId"]; - NSString *rpId = credential[@"rpId"]; - NSString *userName = credential[@"userName"]; - NSData *credentialId = decodeBase64URL(credential[@"credentialId"]); - NSData *userHandle = decodeBase64URL(credential[@"userHandle"]); + NSString *uri = credential[@"uri"]; + NSString *username = credential[@"username"]; + + // Skip credentials with null username since MacOS crashes if we send credentials with empty usernames + if ([username isKindOfClass:[NSNull class]] || username.length == 0) { + NSLog(@"Skipping credential, username is empty: %@", credential); + continue; + } - Class passkeyCredentialIdentityClass = NSClassFromString(@"ASPasskeyCredentialIdentity"); - id credential = [[passkeyCredentialIdentityClass alloc] - initWithRelyingPartyIdentifier:rpId - userName:userName - credentialID:credentialId - userHandle:userHandle - recordIdentifier:cipherId]; + ASCredentialServiceIdentifier *serviceId = [[ASCredentialServiceIdentifier alloc] + initWithIdentifier:uri type:ASCredentialServiceIdentifierTypeURL]; + ASPasswordCredentialIdentity *passwordIdentity = [[ASPasswordCredentialIdentity alloc] + initWithServiceIdentifier:serviceId user:username recordIdentifier:cipherId]; - [mappedCredentials addObject:credential]; + [mappedCredentials addObject:passwordIdentity]; + } + else if (@available(macos 14, *)) { + // Fido2CredentialView uses `userName` (camelCase) while Login uses `username`. + // This is intentional. Fido2 fields are flattened from the FIDO2 spec's nested structure + // (user.name -> userName, rp.id -> rpId) to maintain a clear distinction between these fields. + if ([type isEqualToString:@"fido2"]) { + NSString *cipherId = credential[@"cipherId"]; + NSString *rpId = credential[@"rpId"]; + NSString *userName = credential[@"userName"]; + + // Skip credentials with null username since MacOS crashes if we send credentials with empty usernames + if ([userName isKindOfClass:[NSNull class]] || userName.length == 0) { + NSLog(@"Skipping credential, username is empty: %@", credential); + continue; + } + + NSData *credentialId = decodeBase64URL(credential[@"credentialId"]); + NSData *userHandle = decodeBase64URL(credential[@"userHandle"]); + + Class passkeyCredentialIdentityClass = NSClassFromString(@"ASPasskeyCredentialIdentity"); + id passkeyIdentity = [[passkeyCredentialIdentityClass alloc] + initWithRelyingPartyIdentifier:rpId + userName:userName + credentialID:credentialId + userHandle:userHandle + recordIdentifier:cipherId]; + + [mappedCredentials addObject:passkeyIdentity]; + } } + } @catch (NSException *exception) { + // Silently skip any credential that causes an exception + // to make sure we don't fail the entire sync + // There is likely some invalid data in the credential, and not something the user should/could be asked to correct. + NSLog(@"ERROR: Exception processing credential: %@ - %@", exception.name, exception.reason); + continue; } } diff --git a/apps/desktop/desktop_native/objc/src/native/utils.m b/apps/desktop/desktop_native/objc/src/native/utils.m index 040c723a8ac..8f9493a7afb 100644 --- a/apps/desktop/desktop_native/objc/src/native/utils.m +++ b/apps/desktop/desktop_native/objc/src/native/utils.m @@ -18,9 +18,26 @@ NSString *serializeJson(NSDictionary *dictionary, NSError *error) { } NSData *decodeBase64URL(NSString *base64URLString) { + if (base64URLString.length == 0) { + return nil; + } + + // Replace URL-safe characters with standard base64 characters NSString *base64String = [base64URLString stringByReplacingOccurrencesOfString:@"-" withString:@"+"]; base64String = [base64String stringByReplacingOccurrencesOfString:@"_" withString:@"/"]; - + + // Add padding if needed + // Base 64 strings should be a multiple of 4 in length + NSUInteger paddingLength = 4 - (base64String.length % 4); + if (paddingLength < 4) { + NSMutableString *paddedString = [NSMutableString stringWithString:base64String]; + for (NSUInteger i = 0; i < paddingLength; i++) { + [paddedString appendString:@"="]; + } + base64String = paddedString; + } + + // Decode the string NSData *nsdataFromBase64String = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; diff --git a/apps/desktop/desktop_native/proxy/src/main.rs b/apps/desktop/desktop_native/proxy/src/main.rs index 21957d8ba32..a2a0b834bca 100644 --- a/apps/desktop/desktop_native/proxy/src/main.rs +++ b/apps/desktop/desktop_native/proxy/src/main.rs @@ -8,9 +8,6 @@ use tracing_subscriber::{ fmt, layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter, Layer as _, }; -#[cfg(target_os = "windows")] -mod windows; - #[cfg(target_os = "macos")] embed_plist::embed_info_plist!("../../../resources/info.desktop_proxy.plist"); @@ -64,9 +61,6 @@ fn init_logging(log_path: &Path, console_level: LevelFilter, file_level: LevelFi #[allow(clippy::unwrap_used)] #[tokio::main(flavor = "current_thread")] async fn main() { - #[cfg(target_os = "windows")] - let should_foreground = windows::allow_foreground(); - let sock_path = desktop_core::ipc::path("bw"); let log_path = { @@ -158,9 +152,6 @@ async fn main() { // Listen to stdin and send messages to ipc processor. msg = stdin.next() => { - #[cfg(target_os = "windows")] - should_foreground.store(true, std::sync::atomic::Ordering::Relaxed); - match msg { Some(Ok(msg)) => { let msg = String::from_utf8(msg.to_vec()).unwrap(); diff --git a/apps/desktop/desktop_native/proxy/src/windows.rs b/apps/desktop/desktop_native/proxy/src/windows.rs deleted file mode 100644 index cb0656fc7f8..00000000000 --- a/apps/desktop/desktop_native/proxy/src/windows.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, -}; - -pub fn allow_foreground() -> Arc { - let should_foreground = Arc::new(AtomicBool::new(false)); - let should_foreground_clone = should_foreground.clone(); - let _ = std::thread::spawn(move || loop { - if !should_foreground_clone.load(Ordering::Relaxed) { - std::thread::sleep(std::time::Duration::from_millis(100)); - continue; - } - should_foreground_clone.store(false, Ordering::Relaxed); - - for _ in 0..60 { - desktop_core::biometric::windows_focus::focus_security_prompt(); - std::thread::sleep(std::time::Duration::from_millis(1000)); - } - }); - - should_foreground -} diff --git a/apps/desktop/desktop_native/rust-toolchain.toml b/apps/desktop/desktop_native/rust-toolchain.toml index 898a61f3f4b..0992ce9d294 100644 --- a/apps/desktop/desktop_native/rust-toolchain.toml +++ b/apps/desktop/desktop_native/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.85.0" +channel = "1.91.1" components = [ "rustfmt", "clippy" ] profile = "minimal" diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs index 893fdf765fc..b38a1c725f2 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -153,7 +153,7 @@ fn add_authenticator() -> std::result::Result<(), String> { } } -type EXPERIMENTAL_WebAuthNPluginAddAuthenticatorFnDeclaration = unsafe extern "cdecl" fn( +type EXPERIMENTAL_WebAuthNPluginAddAuthenticatorFnDeclaration = unsafe extern "C" fn( pPluginAddAuthenticatorOptions: *const webauthn::ExperimentalWebAuthnPluginAddAuthenticatorOptions, ppPluginAddAuthenticatorResponse: *mut *mut webauthn::ExperimentalWebAuthnPluginAddAuthenticatorResponse, ) -> HRESULT; diff --git a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib index 1e47cc54de2..132882c6477 100644 --- a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib +++ b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib @@ -8,63 +8,56 @@ + + + + + diff --git a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift index 5568b2e75db..3de9468c8ab 100644 --- a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift +++ b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift @@ -11,63 +11,138 @@ import os class CredentialProviderViewController: ASCredentialProviderViewController { let logger: Logger - // There is something a bit strange about the initialization/deinitialization in this class. - // Sometimes deinit won't be called after a request has successfully finished, - // which would leave this class hanging in memory and the IPC connection open. - // - // If instead I make this a static, the deinit gets called correctly after each request. - // I think we still might want a static regardless, to be able to reuse the connection if possible. - let client: MacOsProviderClient = { - let logger = Logger(subsystem: "com.bitwarden.desktop.autofill-extension", category: "credential-provider") + @IBOutlet weak var statusLabel: NSTextField! + @IBOutlet weak var logoImageView: NSImageView! + + // The IPC client to communicate with the Bitwarden desktop app + private var client: MacOsProviderClient? + + // Timer for checking connection status + private var connectionMonitorTimer: Timer? + private var lastConnectionStatus: ConnectionStatus = .disconnected + + // We changed the getClient method to be async, here's why: + // This is so that we can check if the app is running, and launch it, without blocking the main thread + // Blocking the main thread caused MacOS layouting to 'fail' or at least be very delayed, which caused our getWindowPositioning code to sent 0,0. + // We also properly retry the IPC connection which sometimes would take some time to be up and running, depending on CPU load, phase of jupiters moon, etc. + private func getClient() async -> MacOsProviderClient { + if let client = self.client { + return client + } + 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 + logger.log("[autofill-extension] Bitwarden Desktop not running, attempting to launch") + + // Launch the app and wait for it to be ready 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") + await withCheckedContinuation { continuation in + 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 { + logger.log("[autofill-extension] Successfully launched Bitwarden Desktop") + } + continuation.resume() } - 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") + + // Retry connecting to the Bitwarden IPC with an increasing delay + let maxRetries = 20 + let delayMs = 500 + var newClient: MacOsProviderClient? + + for attempt in 1...maxRetries { + logger.log("[autofill-extension] Connection attempt \(attempt)") + + // Create a new client instance for each retry + newClient = MacOsProviderClient.connect() + try? await Task.sleep(nanoseconds: UInt64(100 * attempt + (delayMs * 1_000_000))) // Convert ms to nanoseconds + let connectionStatus = newClient!.getConnectionStatus() + + logger.log("[autofill-extension] Connection attempt \(attempt), status: \(connectionStatus == .connected ? "connected" : "disconnected")") + + if connectionStatus == .connected { + logger.log("[autofill-extension] Successfully connected to Bitwarden (attempt \(attempt))") + break + } else { + if attempt < maxRetries { + logger.log("[autofill-extension] Retrying connection") + } else { + logger.error("[autofill-extension] Failed to connect after \(maxRetries) attempts, final status: \(connectionStatus == .connected ? "connected" : "disconnected")") + } + } } - logger.log("[autofill-extension] Connecting to Bitwarden over IPC") - - return MacOsProviderClient.connect() - }() + self.client = newClient + return newClient! + } + + // Setup the connection monitoring timer + private func setupConnectionMonitoring() { + // Check connection status every 1 second + connectionMonitorTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in + self?.checkConnectionStatus() + } + + // Make sure timer runs even when UI is busy + RunLoop.current.add(connectionMonitorTimer!, forMode: .common) + + // Initial check + checkConnectionStatus() + } + + // Check the connection status by calling into Rust + // If the connection is has changed and is now disconnected, cancel the request + private func checkConnectionStatus() { + // Only check connection status if the client has been initialized. + // Initialization is done asynchronously, so we might be called before it's ready + // In that case we just skip this check and wait for the next timer tick and re-check + guard let client = self.client else { + return + } + + // Get the current connection status from Rust + let currentStatus = client.getConnectionStatus() + + // Only post notification if state changed + if currentStatus != lastConnectionStatus { + if(currentStatus == .connected) { + logger.log("[autofill-extension] Connection status changed: Connected") + } else { + logger.log("[autofill-extension] Connection status changed: Disconnected") + } + + // Save the new status + lastConnectionStatus = currentStatus + + // If we just disconnected, try to cancel the request + if currentStatus == .disconnected { + self.extensionContext.cancelRequest(withError: BitwardenError.Internal("Bitwarden desktop app disconnected")) + } + } + } init() { logger = Logger(subsystem: "com.bitwarden.desktop.autofill-extension", category: "credential-provider") logger.log("[autofill-extension] initializing extension") - super.init(nibName: nil, bundle: nil) + super.init(nibName: "CredentialProviderViewController", bundle: nil) + + // Setup connection monitoring now that self is available + setupConnectionMonitoring() } required init?(coder: NSCoder) { @@ -76,45 +151,109 @@ class CredentialProviderViewController: ASCredentialProviderViewController { deinit { logger.log("[autofill-extension] deinitializing extension") - } - - - @IBAction func cancel(_ sender: AnyObject?) { - self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) - } - - @IBAction func passwordSelected(_ sender: AnyObject?) { - let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") - self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) - } - - private func getWindowPosition() -> Position { - let frame = self.view.window?.frame ?? .zero - let screenHeight = NSScreen.main?.frame.height ?? 0 - // 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) + // Stop the connection monitor timer + connectionMonitorTimer?.invalidate() + connectionMonitorTimer = nil } - override func loadView() { - let view = NSView() - // Hide the native window since we only need the IPC connection - view.isHidden = true - self.view = view + private func getWindowPosition() async -> Position { + let screenHeight = NSScreen.main?.frame.height ?? 1440 + + logger.log("[autofill-extension] position: Getting window position") + + // To whomever is reading this. Sorry. But MacOS couldn't give us an accurate window positioning, possibly due to animations + // So I added some retry logic, as well as a fall back to the mouse position which is likely at the sort of the right place. + // In my testing we often succed after 4-7 attempts. + // Wait for window frame to stabilize (animation to complete) + var lastFrame: CGRect = .zero + var stableCount = 0 + let requiredStableChecks = 3 + let maxAttempts = 20 + var attempts = 0 + + while stableCount < requiredStableChecks && attempts < maxAttempts { + let currentFrame: CGRect = self.view.window?.frame ?? .zero + + if currentFrame.equalTo(lastFrame) && !currentFrame.equalTo(.zero) { + stableCount += 1 + } else { + stableCount = 0 + lastFrame = currentFrame + } + + try? await Task.sleep(nanoseconds: 16_666_666) // ~60fps (16.67ms) + attempts += 1 + } + + let finalWindowFrame = self.view.window?.frame ?? .zero + logger.log("[autofill-extension] position: Final window frame: \(NSStringFromRect(finalWindowFrame))") + + // Use stabilized window frame if available, otherwise fallback to mouse position + if finalWindowFrame.origin.x != 0 || finalWindowFrame.origin.y != 0 { + let centerX = Int32(round(finalWindowFrame.origin.x)) + let centerY = Int32(round(screenHeight - finalWindowFrame.origin.y)) + logger.log("[autofill-extension] position: Using window position: x=\(centerX), y=\(centerY)") + return Position(x: centerX, y: centerY) + } else { + // Fallback to mouse position + let mouseLocation = NSEvent.mouseLocation + let mouseX = Int32(round(mouseLocation.x)) + let mouseY = Int32(round(screenHeight - mouseLocation.y)) + logger.log("[autofill-extension] position: Using mouse position fallback: x=\(mouseX), y=\(mouseY)") + return Position(x: mouseX, y: mouseY) + } } - + + override func viewDidLoad() { + super.viewDidLoad() + + // Initially hide the view + self.view.isHidden = true + } + + override func prepareInterfaceForExtensionConfiguration() { + // Show the configuration UI + self.view.isHidden = false + + // Set the localized message + statusLabel.stringValue = NSLocalizedString("autofillConfigurationMessage", comment: "Message shown when Bitwarden is enabled in system settings") + + // Send the native status request asynchronously + Task { + let client = await getClient() + client.sendNativeStatus(key: "request-sync", value: "") + } + + // Complete the configuration after 2 seconds + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in + self?.extensionContext.completeExtensionConfigurationRequest() + } + } + + /* + In order to implement this method, we need to query the state of the vault to be unlocked and have one and only one matching credential so that it doesn't need to show ui. + If we do show UI, it's going to fail and disconnect after the platform timeout which is 3s. + For now we just claim to always need UI displayed. + */ override func provideCredentialWithoutUserInteraction(for credentialRequest: any ASCredentialRequest) { + let error = ASExtensionError(.userInteractionRequired) + self.extensionContext.cancelRequest(withError: error) + return + } + + /* + 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 credentialRequest: ASCredentialRequest) { let timeoutTimer = createTimer() - if let request = credentialRequest as? ASPasskeyCredentialRequest { if let passkeyIdentity = request.credentialIdentity as? ASPasskeyCredentialIdentity { - - logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2(passkey) called \(request)") + + logger.log("[autofill-extension] prepareInterfaceToProvideCredential (passkey) called \(request)") class CallbackImpl: PreparePasskeyAssertionCallback { let ctx: ASCredentialProviderExtensionContext @@ -154,18 +293,25 @@ class CredentialProviderViewController: ASCredentialProviderViewController { UserVerification.discouraged } - let req = PasskeyAssertionWithoutUserInterfaceRequest( - rpId: passkeyIdentity.relyingPartyIdentifier, - credentialId: passkeyIdentity.credentialID, - userName: passkeyIdentity.userName, - userHandle: passkeyIdentity.userHandle, - recordIdentifier: passkeyIdentity.recordIdentifier, - clientDataHash: request.clientDataHash, - userVerification: userVerification, - windowXy: self.getWindowPosition() - ) - - self.client.preparePasskeyAssertionWithoutUserInterface(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer)) + /* + We're still using the old request type here, because we're sending the same data, we're expecting a single credential to be used + */ + Task { + let windowPosition = await self.getWindowPosition() + let req = PasskeyAssertionWithoutUserInterfaceRequest( + rpId: passkeyIdentity.relyingPartyIdentifier, + credentialId: passkeyIdentity.credentialID, + userName: passkeyIdentity.userName, + userHandle: passkeyIdentity.userHandle, + recordIdentifier: passkeyIdentity.recordIdentifier, + clientDataHash: request.clientDataHash, + userVerification: userVerification, + windowXy: windowPosition + ) + + let client = await getClient() + client.preparePasskeyAssertionWithoutUserInterface(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer)) + } return } } @@ -176,16 +322,6 @@ class CredentialProviderViewController: ASCredentialProviderViewController { 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) { - } - */ - private func createTimer() -> DispatchWorkItem { // Create a timer for 600 second timeout let timeoutTimer = DispatchWorkItem { [weak self] in @@ -246,18 +382,32 @@ class CredentialProviderViewController: ASCredentialProviderViewController { 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) }, - windowXy: self.getWindowPosition() - ) + // Convert excluded credentials to an array of credential IDs + var excludedCredentialIds: [Data] = [] + if #available(macOSApplicationExtension 15.0, *) { + if let excludedCreds = request.excludedCredentials { + excludedCredentialIds = excludedCreds.map { $0.credentialID } + } + } + logger.log("[autofill-extension] prepareInterface(passkey) calling preparePasskeyRegistration") - self.client.preparePasskeyRegistration(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer)) + Task { + let windowPosition = await self.getWindowPosition() + let req = PasskeyRegistrationRequest( + rpId: passkeyIdentity.relyingPartyIdentifier, + userName: passkeyIdentity.userName, + userHandle: passkeyIdentity.userHandle, + clientDataHash: request.clientDataHash, + userVerification: userVerification, + supportedAlgorithms: request.supportedAlgorithms.map{ Int32($0.rawValue) }, + windowXy: windowPosition, + excludedCredentials: excludedCredentialIds + ) + + let client = await getClient() + client.preparePasskeyRegistration(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer)) + } return } } @@ -310,18 +460,22 @@ class CredentialProviderViewController: ASCredentialProviderViewController { 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)) + Task { + let windowPosition = await self.getWindowPosition() + let req = PasskeyAssertionRequest( + rpId: requestParameters.relyingPartyIdentifier, + clientDataHash: requestParameters.clientDataHash, + userVerification: userVerification, + allowedCredentials: requestParameters.allowedCredentials, + windowXy: windowPosition + //extensionInput: requestParameters.extensionInput, // We don't support extensions yet + ) + + let client = await getClient() + client.preparePasskeyAssertion(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer)) + } return } } diff --git a/apps/desktop/macos/autofill-extension/Info.plist b/apps/desktop/macos/autofill-extension/Info.plist index 539cfa35b9d..7de0d4d152b 100644 --- a/apps/desktop/macos/autofill-extension/Info.plist +++ b/apps/desktop/macos/autofill-extension/Info.plist @@ -10,9 +10,9 @@ ProvidesPasskeys + ShowsConfigurationUI + - ASCredentialProviderExtensionShowsConfigurationUI - NSExtensionPointIdentifier com.apple.authentication-services-credential-provider-ui diff --git a/apps/desktop/macos/autofill-extension/autofill_extension.entitlements b/apps/desktop/macos/autofill-extension/autofill_extension.entitlements index 86c7195768e..d5c7b8a2cc8 100644 --- a/apps/desktop/macos/autofill-extension/autofill_extension.entitlements +++ b/apps/desktop/macos/autofill-extension/autofill_extension.entitlements @@ -2,11 +2,9 @@ - com.apple.developer.authentication-services.autofill-credential-provider - - com.apple.security.app-sandbox - - com.apple.security.application-groups + com.apple.security.app-sandbox + + com.apple.security.application-groups LTZ2PFU5D6.com.bitwarden.desktop diff --git a/apps/desktop/macos/autofill-extension/bitwarden-icon.png b/apps/desktop/macos/autofill-extension/bitwarden-icon.png new file mode 100644 index 00000000000..9a05bc7bbdd Binary files /dev/null and b/apps/desktop/macos/autofill-extension/bitwarden-icon.png differ diff --git a/apps/desktop/macos/autofill-extension/en.lproj/Localizable.strings b/apps/desktop/macos/autofill-extension/en.lproj/Localizable.strings new file mode 100644 index 00000000000..95730dff286 --- /dev/null +++ b/apps/desktop/macos/autofill-extension/en.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Message shown during passkey configuration */ +"autofillConfigurationMessage" = "Enabling Bitwarden..."; diff --git a/apps/desktop/macos/desktop.xcodeproj/project.pbxproj b/apps/desktop/macos/desktop.xcodeproj/project.pbxproj index ff257097f26..ed19fc9ef5d 100644 --- a/apps/desktop/macos/desktop.xcodeproj/project.pbxproj +++ b/apps/desktop/macos/desktop.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 3368DB392C654B8100896B75 /* BitwardenMacosProviderFFI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3368DB382C654B8100896B75 /* BitwardenMacosProviderFFI.xcframework */; }; 3368DB3B2C654F3800896B75 /* BitwardenMacosProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3368DB3A2C654F3800896B75 /* BitwardenMacosProvider.swift */; }; + 9AE299092DF9D82E00AAE454 /* bitwarden-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 9AE299082DF9D82E00AAE454 /* bitwarden-icon.png */; }; + 9AE299122DFB57A200AAE454 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9AE2990D2DFB57A200AAE454 /* Localizable.strings */; }; E1DF713F2B342F6900F29026 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */; }; E1DF71422B342F6900F29026 /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */; }; E1DF71452B342F6900F29026 /* CredentialProviderViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */; }; @@ -18,6 +20,8 @@ 3368DB382C654B8100896B75 /* BitwardenMacosProviderFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = BitwardenMacosProviderFFI.xcframework; path = ../desktop_native/macos_provider/BitwardenMacosProviderFFI.xcframework; sourceTree = ""; }; 3368DB3A2C654F3800896B75 /* BitwardenMacosProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitwardenMacosProvider.swift; sourceTree = ""; }; 968ED08A2C52A47200FFFEE6 /* ReleaseAppStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ReleaseAppStore.xcconfig; sourceTree = ""; }; + 9AE299082DF9D82E00AAE454 /* bitwarden-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bitwarden-icon.png"; sourceTree = ""; }; + 9AE2990C2DFB57A200AAE454 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Localizable.strings; sourceTree = ""; }; D83832AB2D67B9AE003FB9F8 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; D83832AD2D67B9D0003FB9F8 /* ReleaseDeveloper.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ReleaseDeveloper.xcconfig; sourceTree = ""; }; E1DF713C2B342F6900F29026 /* autofill-extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "autofill-extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -41,6 +45,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 9AE2990E2DFB57A200AAE454 /* en.lproj */ = { + isa = PBXGroup; + children = ( + 9AE2990D2DFB57A200AAE454 /* Localizable.strings */, + ); + path = en.lproj; + sourceTree = ""; + }; E1DF711D2B342E2800F29026 = { isa = PBXGroup; children = ( @@ -73,6 +85,8 @@ E1DF71402B342F6900F29026 /* autofill-extension */ = { isa = PBXGroup; children = ( + 9AE2990E2DFB57A200AAE454 /* en.lproj */, + 9AE299082DF9D82E00AAE454 /* bitwarden-icon.png */, 3368DB3A2C654F3800896B75 /* BitwardenMacosProvider.swift */, E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */, E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */, @@ -124,6 +138,7 @@ knownRegions = ( en, Base, + sv, ); mainGroup = E1DF711D2B342E2800F29026; productRefGroup = E1DF71272B342E2800F29026 /* Products */; @@ -141,6 +156,8 @@ buildActionMask = 2147483647; files = ( E1DF71452B342F6900F29026 /* CredentialProviderViewController.xib in Resources */, + 9AE299122DFB57A200AAE454 /* Localizable.strings in Resources */, + 9AE299092DF9D82E00AAE454 /* bitwarden-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -159,6 +176,14 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ + 9AE2990D2DFB57A200AAE454 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 9AE2990C2DFB57A200AAE454 /* en */, + ); + name = Localizable.strings; + sourceTree = ""; + }; E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */ = { isa = PBXVariantGroup; children = ( diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 9ad1ffb3ec0..1f4a56de18a 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -19,7 +19,7 @@ "yargs": "18.0.0" }, "devDependencies": { - "@types/node": "22.19.1", + "@types/node": "22.19.2", "typescript": "5.4.2" } }, @@ -117,9 +117,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", - "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "version": "22.19.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", + "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", "license": "MIT", "peer": true, "dependencies": { diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 21a6ba3626a..83e9f01afed 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -24,7 +24,7 @@ "yargs": "18.0.0" }, "devDependencies": { - "@types/node": "22.19.1", + "@types/node": "22.19.2", "typescript": "5.4.2" }, "_moduleAliases": { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 8ea17d96b91..97ab8585a69 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.11.3", + "version": "2025.12.1", "keywords": [ "bitwarden", "password", @@ -18,6 +18,7 @@ "scripts": { "postinstall": "electron-rebuild", "start": "cross-env ELECTRON_IS_DEV=0 ELECTRON_NO_UPDATER=1 electron ./build", + "build-native-macos": "cd desktop_native && ./macos_provider/build.sh && node build.js cross-platform", "build-native": "cd desktop_native && node build.js", "build": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", "build:dev": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\" \"npm run build:preload:dev\"", @@ -39,15 +40,14 @@ "clean:dist": "rimraf ./dist", "pack:dir": "npm run clean:dist && electron-builder --dir -p never", "pack:lin:flatpak": "flatpak-builder --repo=../../.flatpak-repo ../../.flatpak ./resources/com.bitwarden.desktop.devel.yaml --install-deps-from=flathub --force-clean && flatpak build-bundle ../../.flatpak-repo/ ./dist/com.bitwarden.desktop.flatpak com.bitwarden.desktop", - "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snap pack --compression=lzo ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/", + "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snap pack --compression=lzo ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/ && tar -czvf ./dist/bitwarden_desktop_x64.tar.gz -C ./dist/linux-unpacked/ .", "pack:lin:arm64": "npm run clean:dist && electron-builder --linux --arm64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snap pack --compression=lzo ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/ && tar -czvf ./dist/bitwarden_desktop_arm64.tar.gz -C ./dist/linux-arm64-unpacked/ .", "pack:mac": "npm run clean:dist && electron-builder --mac --universal -p never", "pack:mac:with-extension": "npm run clean:dist && npm run build:macos-extension:mac && electron-builder --mac --universal -p never", "pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never", - "pack:mac:mas": "npm run clean:dist && electron-builder --mac mas --universal -p never", - "pack:mac:mas:with-extension": "npm run clean:dist && npm run build:macos-extension:mas && electron-builder --mac mas --universal -p never", - "pack:mac:masdev": "npm run clean:dist && electron-builder --mac mas-dev --universal -p never", - "pack:mac:masdev:with-extension": "npm run clean:dist && npm run build:macos-extension:masdev && electron-builder --mac mas-dev --universal -p never", + "pack:mac:mas": "npm run clean:dist && npm run build:macos-extension:mas && electron-builder --mac mas --universal -p never", + "pack:mac:masdev": "npm run clean:dist && npm run build:macos-extension:masdev && electron-builder --mac mas-dev --universal -p never", + "pack:local:mac": "npm run clean:dist && npm run build:macos-extension:masdev && electron-builder --mac mas-dev --universal -p never -c.mac.provisioningProfile=\"\" -c.mas.provisioningProfile=\"\"", "pack:win": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never -c.win.signtoolOptions.certificateSubjectName=\"8bit Solutions LLC\"", "pack:win:beta": "npm run clean:dist && electron-builder --config electron-builder.beta.json --win --x64 --arm64 --ia32 -p never -c.win.signtoolOptions.certificateSubjectName=\"8bit Solutions LLC\"", "pack:win:ci": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never", @@ -55,11 +55,8 @@ "dist:lin": "npm run build && npm run pack:lin", "dist:lin:arm64": "npm run build && npm run pack:lin:arm64", "dist:mac": "npm run build && npm run pack:mac", - "dist:mac:with-extension": "npm run build && npm run pack:mac:with-extension", "dist:mac:mas": "npm run build && npm run pack:mac:mas", - "dist:mac:mas:with-extension": "npm run build && npm run pack:mac:mas:with-extension", - "dist:mac:masdev": "npm run build:dev && npm run pack:mac:masdev", - "dist:mac:masdev:with-extension": "npm run build:dev && npm run pack:mac:masdev:with-extension", + "dist:mac:masdev": "npm run build && npm run pack:mac:masdev", "dist:win": "npm run build && npm run pack:win", "dist:win:ci": "npm run build && npm run pack:win:ci", "publish:lin": "npm run build && npm run clean:dist && electron-builder --linux --x64 -p always", diff --git a/apps/desktop/resources/entitlements.mac.plist b/apps/desktop/resources/entitlements.mac.plist index fe49256d71c..7763b84624d 100644 --- a/apps/desktop/resources/entitlements.mac.plist +++ b/apps/desktop/resources/entitlements.mac.plist @@ -6,8 +6,6 @@ LTZ2PFU5D6.com.bitwarden.desktop com.apple.developer.team-identifier LTZ2PFU5D6 - com.apple.developer.authentication-services.autofill-credential-provider - com.apple.security.cs.allow-jit diff --git a/apps/desktop/resources/entitlements.mas.inherit.plist b/apps/desktop/resources/entitlements.mas.inherit.plist index fca5f02d52d..7194d9409fc 100644 --- a/apps/desktop/resources/entitlements.mas.inherit.plist +++ b/apps/desktop/resources/entitlements.mas.inherit.plist @@ -4,9 +4,9 @@ com.apple.security.app-sandbox - com.apple.security.inherit - com.apple.security.cs.allow-jit + com.apple.security.inherit + diff --git a/apps/desktop/resources/entitlements.mas.plist b/apps/desktop/resources/entitlements.mas.plist index 3ebd56f0fd7..226e9827e37 100644 --- a/apps/desktop/resources/entitlements.mas.plist +++ b/apps/desktop/resources/entitlements.mas.plist @@ -6,19 +6,19 @@ LTZ2PFU5D6.com.bitwarden.desktop com.apple.developer.team-identifier LTZ2PFU5D6 - com.apple.developer.authentication-services.autofill-credential-provider - com.apple.security.app-sandbox com.apple.security.application-groups LTZ2PFU5D6.com.bitwarden.desktop - com.apple.security.network.client + com.apple.security.cs.allow-jit + + com.apple.security.device.usb com.apple.security.files.user-selected.read-write - com.apple.security.device.usb + com.apple.security.network.client com.apple.security.temporary-exception.files.home-relative-path.read-write @@ -32,10 +32,9 @@ /Library/Application Support/Microsoft Edge Beta/NativeMessagingHosts/ /Library/Application Support/Microsoft Edge Dev/NativeMessagingHosts/ /Library/Application Support/Microsoft Edge Canary/NativeMessagingHosts/ - /Library/Application Support/Vivaldi/NativeMessagingHosts/ + /Library/Application Support/Vivaldi/NativeMessagingHosts/ /Library/Application Support/Zen/NativeMessagingHosts/ + /Library/Application Support/net.imput.helium - com.apple.security.cs.allow-jit - diff --git a/apps/desktop/resources/linux-wrapper.sh b/apps/desktop/resources/linux-wrapper.sh index 50a323e1c18..3c5d16c3a3d 100644 --- a/apps/desktop/resources/linux-wrapper.sh +++ b/apps/desktop/resources/linux-wrapper.sh @@ -12,14 +12,6 @@ if [ -e "/usr/lib/x86_64-linux-gnu/libdbus-1.so.3" ]; then export LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libdbus-1.so.3" fi -# If running in non-snap, add libprocess_isolation.so from app path to LD_PRELOAD -# This prevents debugger / memory dumping on all desktop processes -if [ -z "$SNAP" ] && [ -f "$APP_PATH/libprocess_isolation.so" ]; then - LIBPROCESS_ISOLATION_SO="$APP_PATH/libprocess_isolation.so" - LD_PRELOAD="$LIBPROCESS_ISOLATION_SO${LD_PRELOAD:+:$LD_PRELOAD}" - export LD_PRELOAD -fi - PARAMS="--enable-features=UseOzonePlatform,WaylandWindowDecorations --ozone-platform-hint=auto" if [ "$USE_X11" = "true" ]; then PARAMS="" diff --git a/apps/desktop/scripts/after-sign.js b/apps/desktop/scripts/after-sign.js index 7c9ad381dc2..4275ec7d051 100644 --- a/apps/desktop/scripts/after-sign.js +++ b/apps/desktop/scripts/after-sign.js @@ -16,7 +16,7 @@ async function run(context) { const appPath = `${context.appOutDir}/${appName}.app`; const macBuild = context.electronPlatformName === "darwin"; const copySafariExtension = ["darwin", "mas"].includes(context.electronPlatformName); - const copyAutofillExtension = ["darwin", "mas"].includes(context.electronPlatformName); + const copyAutofillExtension = ["darwin"].includes(context.electronPlatformName); // Disabled for mas builds let shouldResign = false; diff --git a/apps/desktop/scripts/nx-serve.js b/apps/desktop/scripts/nx-serve.js index b92a045f8e8..235691f9ce8 100644 --- a/apps/desktop/scripts/nx-serve.js +++ b/apps/desktop/scripts/nx-serve.js @@ -37,6 +37,6 @@ concurrently( { prefix: "name", outputStream: process.stdout, - killOthers: ["success", "failure"], + killOthersOn: ["success", "failure"], }, ); diff --git a/apps/desktop/scripts/start.js b/apps/desktop/scripts/start.js index 0e11ebd9083..4ffbe2eebeb 100644 --- a/apps/desktop/scripts/start.js +++ b/apps/desktop/scripts/start.js @@ -34,6 +34,6 @@ concurrently( { prefix: "name", outputStream: process.stdout, - killOthers: ["success", "failure"], + killOthersOn: ["success", "failure"], }, ); diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index bf3c46a311f..d5042918d2f 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -44,12 +44,12 @@

{{ "vaultTimeoutHeader" | i18n }}

- - + {{ @@ -101,8 +101,7 @@ supportsBiometric && form.value.biometric && isWindows && - (userHasMasterPassword || (form.value.pin && userHasPinSet)) && - isWindowsV2BiometricsEnabled + (userHasMasterPassword || (form.value.pin && userHasPinSet)) " >
diff --git a/apps/desktop/src/app/accounts/settings.component.spec.ts b/apps/desktop/src/app/accounts/settings.component.spec.ts index 115f7436979..a424f230778 100644 --- a/apps/desktop/src/app/accounts/settings.component.spec.ts +++ b/apps/desktop/src/app/accounts/settings.component.spec.ts @@ -302,7 +302,6 @@ describe("SettingsComponent", () => { describe("windows desktop", () => { beforeEach(() => { platformUtilsService.getDevice.mockReturnValue(DeviceType.WindowsDesktop); - desktopBiometricsService.isWindowsV2BiometricsEnabled.mockResolvedValue(true); // Recreate component to apply the correct device fixture = TestBed.createComponent(SettingsComponent); @@ -449,7 +448,6 @@ describe("SettingsComponent", () => { desktopBiometricsService.hasPersistentKey.mockResolvedValue(enrolled); await component.ngOnInit(); - component.isWindowsV2BiometricsEnabled = true; component.isWindows = true; component.form.value.requireMasterPasswordOnAppRestart = true; component.userHasMasterPassword = false; @@ -558,7 +556,6 @@ describe("SettingsComponent", () => { desktopBiometricsService.hasPersistentKey.mockResolvedValue(false); await component.ngOnInit(); - component.isWindowsV2BiometricsEnabled = true; component.isWindows = true; component.form.value.requireMasterPasswordOnAppRestart = requireMasterPasswordOnAppRestart; @@ -659,6 +656,7 @@ describe("SettingsComponent", () => { describe("windows test cases", () => { beforeEach(() => { platformUtilsService.getDevice.mockReturnValue(DeviceType.WindowsDesktop); + keyService.userKey$ = jest.fn().mockReturnValue(of(mockUserKey)); component.isWindows = true; component.isLinux = false; @@ -683,8 +681,6 @@ describe("SettingsComponent", () => { describe("when windows v2 biometrics is enabled", () => { beforeEach(() => { - component.isWindowsV2BiometricsEnabled = true; - keyService.userKey$ = jest.fn().mockReturnValue(of(mockUserKey)); }); diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index c0798f1bdf0..e3022428421 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -55,7 +55,7 @@ import { } from "@bitwarden/components"; import { KeyService, BiometricStateService, BiometricsStatus } from "@bitwarden/key-management"; import { - SessionTimeoutInputComponent, + SessionTimeoutInputLegacyComponent, SessionTimeoutSettingsComponent, } from "@bitwarden/key-management-ui"; import { PermitCipherDetailsPopoverComponent } from "@bitwarden/vault"; @@ -97,7 +97,7 @@ import { NativeMessagingManifestService } from "../services/native-messaging-man SectionHeaderComponent, SelectModule, TypographyModule, - SessionTimeoutInputComponent, + SessionTimeoutInputLegacyComponent, SessionTimeoutSettingsComponent, PermitCipherDetailsPopoverComponent, PremiumBadgeComponent, @@ -148,7 +148,6 @@ export class SettingsComponent implements OnInit, OnDestroy { userHasPinSet: boolean; pinEnabled$: Observable = of(true); - isWindowsV2BiometricsEnabled: boolean = false; consolidatedSessionTimeoutComponent$: Observable; @@ -297,8 +296,6 @@ export class SettingsComponent implements OnInit, OnDestroy { async ngOnInit() { this.vaultTimeoutOptions = await this.generateVaultTimeoutOptions(); - this.isWindowsV2BiometricsEnabled = await this.biometricsService.isWindowsV2BiometricsEnabled(); - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); // Autotype is for Windows initially @@ -621,7 +618,6 @@ export class SettingsComponent implements OnInit, OnDestroy { // On Windows if a user turned off PIN without having a MP and has biometrics + require MP/PIN on restart enabled. if ( this.isWindows && - this.isWindowsV2BiometricsEnabled && this.supportsBiometric && this.form.value.requireMasterPasswordOnAppRestart && this.form.value.biometric && @@ -682,14 +678,12 @@ export class SettingsComponent implements OnInit, OnDestroy { this.form.controls.autoPromptBiometrics.setValue(false); await this.biometricStateService.setPromptAutomatically(false); - if (this.isWindowsV2BiometricsEnabled) { - // If the user doesn't have a MP or PIN then they have to use biometrics on app restart. - if (!this.userHasMasterPassword && !this.userHasPinSet) { - // Allow biometric unlock on app restart so the user doesn't get into a bad state. - await this.enrollPersistentBiometricIfNeeded(activeUserId); - } else { - this.form.controls.requireMasterPasswordOnAppRestart.setValue(true); - } + // If the user doesn't have a MP or PIN then they have to use biometrics on app restart. + if (!this.userHasMasterPassword && !this.userHasPinSet) { + // Allow biometric unlock on app restart so the user doesn't get into a bad state. + await this.enrollPersistentBiometricIfNeeded(activeUserId); + } else { + this.form.controls.requireMasterPasswordOnAppRestart.setValue(true); } } else if (this.isLinux) { // Similar to Windows diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index b6e86ba19ff..6077afa8c12 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -14,6 +14,7 @@ import { } from "@bitwarden/angular/auth/guards"; import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password"; import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component"; +import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { DevicesIcon, RegistrationUserAddIcon, @@ -39,15 +40,25 @@ import { TwoFactorAuthGuard, NewDeviceVerificationComponent, } from "@bitwarden/auth/angular"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; -import { LockComponent, ConfirmKeyConnectorDomainComponent } from "@bitwarden/key-management-ui"; +import { + LockComponent, + ConfirmKeyConnectorDomainComponent, + RemovePasswordComponent, +} from "@bitwarden/key-management-ui"; import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; +import { reactiveUnlockVaultGuard } from "../autofill/guards/reactive-vault-guard"; +import { Fido2CreateComponent } from "../autofill/modal/credentials/fido2-create.component"; +import { Fido2ExcludedCiphersComponent } from "../autofill/modal/credentials/fido2-excluded-ciphers.component"; +import { Fido2VaultComponent } from "../autofill/modal/credentials/fido2-vault.component"; import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; +import { VaultComponent } from "../vault/app/vault-v3/vault.component"; -import { Fido2PlaceholderComponent } from "./components/fido2placeholder.component"; +import { DesktopLayoutComponent } from "./layout/desktop-layout.component"; import { SendComponent } from "./tools/send/send.component"; +import { SendV2Component } from "./tools/send-v2/send-v2.component"; /** * Data properties acceptable for use in route objects in the desktop @@ -99,7 +110,10 @@ const routes: Routes = [ { path: "vault", component: VaultV2Component, - canActivate: [authGuard], + canActivate: [ + authGuard, + canAccessFeature(FeatureFlag.DesktopUiMigrationMilestone1, false, "new-vault", false), + ], }, { path: "send", @@ -107,17 +121,16 @@ const routes: Routes = [ canActivate: [authGuard], }, { - path: "remove-password", - component: RemovePasswordComponent, - canActivate: [authGuard], + path: "fido2-assertion", + component: Fido2VaultComponent, }, { - path: "passkeys", - component: Fido2PlaceholderComponent, + path: "fido2-creation", + component: Fido2CreateComponent, }, { - path: "passkeys", - component: Fido2PlaceholderComponent, + path: "fido2-excluded", + component: Fido2ExcludedCiphersComponent, }, { path: "", @@ -263,7 +276,7 @@ const routes: Routes = [ }, { path: "lock", - canActivate: [lockGuard()], + canActivate: [lockGuard(), reactiveUnlockVaultGuard], data: { pageIcon: LockIcon, pageTitle: { @@ -312,19 +325,45 @@ const routes: Routes = [ pageIcon: LockIcon, } satisfies AnonLayoutWrapperData, }, + { + path: "remove-password", + component: RemovePasswordComponent, + canActivate: [authGuard], + data: { + pageTitle: { + key: "verifyYourOrganization", + }, + pageIcon: LockIcon, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + }, { path: "confirm-key-connector-domain", component: ConfirmKeyConnectorDomainComponent, canActivate: [], data: { pageTitle: { - key: "confirmKeyConnectorDomain", + key: "verifyYourOrganization", }, pageIcon: DomainIcon, } satisfies RouteDataProperties & AnonLayoutWrapperData, }, ], }, + { + path: "", + component: DesktopLayoutComponent, + canActivate: [authGuard], + children: [ + { + path: "new-vault", + component: VaultComponent, + }, + { + path: "new-sends", + component: SendV2Component, + }, + ], + }, ]; @NgModule({ diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 6243ba1e538..836328142b5 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -104,7 +104,7 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours - +
@@ -141,6 +141,7 @@ export class AppComponent implements OnInit, OnDestroy { @ViewChild("loginApproval", { read: ViewContainerRef, static: true }) loginApprovalModalRef: ViewContainerRef; + showHeader$ = this.accountService.showHeader$; loading = false; private lastActivity: Date = null; diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index 4f53e587994..31131c6202a 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -15,7 +15,6 @@ import { DeleteAccountComponent } from "../auth/delete-account.component"; import { LoginModule } from "../auth/login/login.module"; import { SshAgentService } from "../autofill/services/ssh-agent.service"; import { PremiumComponent } from "../billing/app/accounts/premium.component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { VaultFilterModule } from "../vault/app/vault/vault-filter/vault-filter.module"; import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; @@ -50,7 +49,6 @@ import { SharedModule } from "./shared/shared.module"; ColorPasswordCountPipe, HeaderComponent, PremiumComponent, - RemovePasswordComponent, SearchComponent, ], providers: [SshAgentService], diff --git a/apps/desktop/src/app/components/fido2placeholder.component.ts b/apps/desktop/src/app/components/fido2placeholder.component.ts deleted file mode 100644 index f1f52dae439..00000000000 --- a/apps/desktop/src/app/components/fido2placeholder.component.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { BehaviorSubject, Observable } from "rxjs"; - -import { - DesktopFido2UserInterfaceService, - DesktopFido2UserInterfaceSession, -} from "../../autofill/services/desktop-fido2-user-interface.service"; -import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - standalone: true, - imports: [CommonModule], - template: ` -
-

Select your passkey

- -
- -
- -
- - -
- `, -}) -export class Fido2PlaceholderComponent implements OnInit, OnDestroy { - session?: DesktopFido2UserInterfaceSession = null; - private cipherIdsSubject = new BehaviorSubject([]); - cipherIds$: Observable; - - constructor( - private readonly desktopSettingsService: DesktopSettingsService, - private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService, - private readonly router: Router, - ) {} - - ngOnInit() { - this.session = this.fido2UserInterfaceService.getCurrentSession(); - this.cipherIds$ = this.session?.availableCipherIds$; - } - - async chooseCipher(cipherId: string) { - // For now: Set UV to true - this.session?.confirmChosenCipher(cipherId, true); - - await this.router.navigate(["/"]); - await this.desktopSettingsService.setModalMode(false); - } - - ngOnDestroy() { - this.cipherIdsSubject.complete(); // Clean up the BehaviorSubject - } - - async confirmPasskey() { - try { - // Retrieve the current UI session to control the flow - if (!this.session) { - // todo: handle error - throw new Error("No session found"); - } - - // If we want to we could submit information to the session in order to create the credential - // const cipher = await session.createCredential({ - // userHandle: "userHandle2", - // userName: "username2", - // credentialName: "zxsd2", - // rpId: "webauthn.io", - // userVerification: true, - // }); - - this.session.notifyConfirmNewCredential(true); - - // Not sure this clean up should happen here or in session. - // The session currently toggles modal on and send us here - // But if this route is somehow opened outside of session we want to make sure we clean up? - await this.router.navigate(["/"]); - await this.desktopSettingsService.setModalMode(false); - } catch { - // TODO: Handle error appropriately - } - } - - async closeModal() { - await this.router.navigate(["/"]); - await this.desktopSettingsService.setModalMode(false); - - this.session.notifyConfirmNewCredential(false); - // little bit hacky: - this.session.confirmChosenCipher(null); - } -} diff --git a/apps/desktop/src/app/layout/desktop-layout.component.html b/apps/desktop/src/app/layout/desktop-layout.component.html new file mode 100644 index 00000000000..1717b29acd1 --- /dev/null +++ b/apps/desktop/src/app/layout/desktop-layout.component.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/desktop/src/app/layout/desktop-layout.component.spec.ts b/apps/desktop/src/app/layout/desktop-layout.component.spec.ts new file mode 100644 index 00000000000..74cddd02495 --- /dev/null +++ b/apps/desktop/src/app/layout/desktop-layout.component.spec.ts @@ -0,0 +1,84 @@ +import { ChangeDetectionStrategy, Component } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { RouterModule } from "@angular/router"; +import { mock } from "jest-mock-extended"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { NavigationModule } from "@bitwarden/components"; + +import { SendFiltersNavComponent } from "../tools/send-v2/send-filters-nav.component"; + +import { DesktopLayoutComponent } from "./desktop-layout.component"; + +// Mock the child component to isolate DesktopLayoutComponent testing +@Component({ + selector: "app-send-filters-nav", + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class MockSendFiltersNavComponent {} + +Object.defineProperty(window, "matchMedia", { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: true, + media: query, + onchange: null, + addListener: jest.fn(), + removeListener: jest.fn(), + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); + +describe("DesktopLayoutComponent", () => { + let component: DesktopLayoutComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DesktopLayoutComponent, RouterModule.forRoot([]), NavigationModule], + providers: [ + { + provide: I18nService, + useValue: mock(), + }, + ], + }) + .overrideComponent(DesktopLayoutComponent, { + remove: { imports: [SendFiltersNavComponent] }, + add: { imports: [MockSendFiltersNavComponent] }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(DesktopLayoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("creates component", () => { + expect(component).toBeTruthy(); + }); + + it("renders bit-layout component", () => { + const compiled = fixture.nativeElement; + const layoutElement = compiled.querySelector("bit-layout"); + + expect(layoutElement).toBeTruthy(); + }); + + it("supports content projection for side-nav", () => { + const compiled = fixture.nativeElement; + const ngContent = compiled.querySelectorAll("ng-content"); + + expect(ngContent).toBeTruthy(); + }); + + it("renders send filters navigation component", () => { + const compiled = fixture.nativeElement; + const sendFiltersNav = compiled.querySelector("app-send-filters-nav"); + + expect(sendFiltersNav).toBeTruthy(); + }); +}); diff --git a/apps/desktop/src/app/layout/desktop-layout.component.ts b/apps/desktop/src/app/layout/desktop-layout.component.ts new file mode 100644 index 00000000000..0ee7065fba8 --- /dev/null +++ b/apps/desktop/src/app/layout/desktop-layout.component.ts @@ -0,0 +1,28 @@ +import { Component } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { PasswordManagerLogo } from "@bitwarden/assets/svg"; +import { LayoutComponent, NavigationModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { SendFiltersNavComponent } from "../tools/send-v2/send-filters-nav.component"; + +import { DesktopSideNavComponent } from "./desktop-side-nav.component"; + +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +@Component({ + selector: "app-layout", + imports: [ + RouterModule, + I18nPipe, + LayoutComponent, + NavigationModule, + DesktopSideNavComponent, + SendFiltersNavComponent, + ], + templateUrl: "./desktop-layout.component.html", +}) +export class DesktopLayoutComponent { + protected readonly logo = PasswordManagerLogo; +} diff --git a/apps/desktop/src/app/layout/desktop-side-nav.component.html b/apps/desktop/src/app/layout/desktop-side-nav.component.html new file mode 100644 index 00000000000..ede3f9131b7 --- /dev/null +++ b/apps/desktop/src/app/layout/desktop-side-nav.component.html @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop/src/app/layout/desktop-side-nav.component.spec.ts b/apps/desktop/src/app/layout/desktop-side-nav.component.spec.ts new file mode 100644 index 00000000000..4d5c3a90253 --- /dev/null +++ b/apps/desktop/src/app/layout/desktop-side-nav.component.spec.ts @@ -0,0 +1,74 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { NavigationModule } from "@bitwarden/components"; + +import { DesktopSideNavComponent } from "./desktop-side-nav.component"; + +Object.defineProperty(window, "matchMedia", { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: true, + media: query, + onchange: null, + addListener: jest.fn(), + removeListener: jest.fn(), + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); + +describe("DesktopSideNavComponent", () => { + let component: DesktopSideNavComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DesktopSideNavComponent, NavigationModule], + providers: [ + { + provide: I18nService, + useValue: mock(), + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DesktopSideNavComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("creates component", () => { + expect(component).toBeTruthy(); + }); + + it("renders bit-side-nav component", () => { + const compiled = fixture.nativeElement; + const sideNavElement = compiled.querySelector("bit-side-nav"); + + expect(sideNavElement).toBeTruthy(); + }); + + it("uses primary variant by default", () => { + expect(component.variant()).toBe("primary"); + }); + + it("accepts variant input", () => { + fixture.componentRef.setInput("variant", "secondary"); + fixture.detectChanges(); + + expect(component.variant()).toBe("secondary"); + }); + + it.skip("passes variant to bit-side-nav", () => { + fixture.componentRef.setInput("variant", "secondary"); + fixture.detectChanges(); + + const compiled = fixture.nativeElement; + const sideNavElement = compiled.querySelector("bit-side-nav"); + + expect(sideNavElement.getAttribute("ng-reflect-variant")).toBe("secondary"); + }); +}); diff --git a/apps/desktop/src/app/layout/desktop-side-nav.component.ts b/apps/desktop/src/app/layout/desktop-side-nav.component.ts new file mode 100644 index 00000000000..b0d9fd16fcc --- /dev/null +++ b/apps/desktop/src/app/layout/desktop-side-nav.component.ts @@ -0,0 +1,14 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, input } from "@angular/core"; + +import { NavigationModule, SideNavVariant } from "@bitwarden/components"; + +@Component({ + selector: "app-side-nav", + templateUrl: "desktop-side-nav.component.html", + imports: [CommonModule, NavigationModule], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DesktopSideNavComponent { + readonly variant = input("primary"); +} diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 73c4d38d3b2..17115825bf6 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -1,5 +1,4 @@ -import { DOCUMENT } from "@angular/common"; -import { Inject, Injectable } from "@angular/core"; +import { Inject, Injectable, DOCUMENT } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 03d6eb5c908..59021a556e4 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -51,6 +51,7 @@ import { } from "@bitwarden/common/auth/abstractions/auth.service"; import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ClientType } from "@bitwarden/common/enums"; @@ -62,6 +63,7 @@ import { WebCryptoFunctionService } from "@bitwarden/common/key-management/crypt import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { DefaultProcessReloadService } from "@bitwarden/common/key-management/services/default-process-reload.service"; +import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout"; import { VaultTimeoutSettingsService, VaultTimeoutStringType, @@ -101,6 +103,7 @@ import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/s import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { GeneratorServicesModule } from "@bitwarden/generator-components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfigService, @@ -128,7 +131,7 @@ import { DesktopBiometricsService } from "../../key-management/biometrics/deskto import { RendererBiometricsService } from "../../key-management/biometrics/renderer-biometrics.service"; import { ElectronKeyService } from "../../key-management/electron-key.service"; import { DesktopLockComponentService } from "../../key-management/lock/services/desktop-lock-component.service"; -import { DesktopSessionTimeoutSettingsComponentService } from "../../key-management/session-timeout/services/desktop-session-timeout-settings-component.service"; +import { DesktopSessionTimeoutTypeService } from "../../key-management/session-timeout/services/desktop-session-timeout-type.service"; import { flagEnabled } from "../../platform/flags"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service"; @@ -165,12 +168,12 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: BiometricsService, useClass: RendererBiometricsService, - deps: [], + deps: [TokenService], }), safeProvider({ provide: DesktopBiometricsService, useClass: RendererBiometricsService, - deps: [], + deps: [TokenService], }), safeProvider(NativeMessagingService), safeProvider(BiometricMessageHandlerService), @@ -200,8 +203,16 @@ const safeProviders: SafeProvider[] = [ // We manually override the value of SUPPORTS_SECURE_STORAGE here to avoid // the TokenService having to inject the PlatformUtilsService which introduces a // circular dependency on Desktop only. + // + // For Windows portable builds, we disable secure storage to ensure tokens are + // stored on disk (in bitwarden-appdata) rather than in Windows Credential + // Manager, making them portable across machines. This allows users to move the USB drive + // between computers while maintaining authentication. + // + // Note: Portable mode does not use secure storage for read/write/clear operations, + // preventing any collision with tokens from a regular desktop installation. provide: SUPPORTS_SECURE_STORAGE, - useValue: ELECTRON_SUPPORTS_SECURE_STORAGE, + useValue: ELECTRON_SUPPORTS_SECURE_STORAGE && !ipc.platform.isWindowsPortable, }), safeProvider({ provide: DEFAULT_VAULT_TIMEOUT, @@ -344,6 +355,7 @@ const safeProviders: SafeProvider[] = [ ConfigService, Fido2AuthenticatorServiceAbstraction, AccountService, + AuthService, PlatformUtilsService, ], }), @@ -484,15 +496,20 @@ const safeProviders: SafeProvider[] = [ useClass: DesktopAutotypeDefaultSettingPolicy, deps: [AccountServiceAbstraction, AuthServiceAbstraction, InternalPolicyService, ConfigService], }), + safeProvider({ + provide: SessionTimeoutTypeService, + useClass: DesktopSessionTimeoutTypeService, + deps: [], + }), safeProvider({ provide: SessionTimeoutSettingsComponentService, - useClass: DesktopSessionTimeoutSettingsComponentService, - deps: [I18nServiceAbstraction], + useClass: SessionTimeoutSettingsComponentService, + deps: [I18nServiceAbstraction, SessionTimeoutTypeService, PolicyServiceAbstraction], }), ]; @NgModule({ - imports: [JslibServicesModule], + imports: [JslibServicesModule, GeneratorServicesModule], declarations: [], // Do not register your dependency here! Add it to the typesafeProviders array using the helper function providers: safeProviders, diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.html b/apps/desktop/src/app/tools/import/import-desktop.component.html index 796d61e1b69..3ee2384691b 100644 --- a/apps/desktop/src/app/tools/import/import-desktop.component.html +++ b/apps/desktop/src/app/tools/import/import-desktop.component.html @@ -1,13 +1,21 @@ {{ "importData" | i18n }} - +
+ + @if (loading) { +
+ +
+ } +
+
+
+ + + +

{{ "noItemsInList" | i18n }}

+
+
+ +
+
+ + +
diff --git a/apps/desktop/src/app/tools/send-v2/send-v2.component.spec.ts b/apps/desktop/src/app/tools/send-v2/send-v2.component.spec.ts new file mode 100644 index 00000000000..8657f3e375e --- /dev/null +++ b/apps/desktop/src/app/tools/send-v2/send-v2.component.spec.ts @@ -0,0 +1,386 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { ChangeDetectorRef } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { FormBuilder } from "@angular/forms"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject, of } from "rxjs"; + +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"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { SendListFiltersService } from "@bitwarden/send-ui"; + +import * as utils from "../../../utils"; +import { SearchBarService } from "../../layout/search/search-bar.service"; +import { AddEditComponent } from "../send/add-edit.component"; + +import { SendV2Component } from "./send-v2.component"; + +// Mock the invokeMenu utility function +jest.mock("../../../utils", () => ({ + invokeMenu: jest.fn(), +})); + +describe("SendV2Component", () => { + let component: SendV2Component; + let fixture: ComponentFixture; + let sendService: MockProxy; + let searchBarService: MockProxy; + let broadcasterService: MockProxy; + let accountService: MockProxy; + let policyService: MockProxy; + let sendListFiltersService: SendListFiltersService; + let changeDetectorRef: MockProxy; + + beforeEach(async () => { + sendService = mock(); + searchBarService = mock(); + broadcasterService = mock(); + accountService = mock(); + policyService = mock(); + changeDetectorRef = mock(); + + // Create real SendListFiltersService with mocked dependencies + const formBuilder = new FormBuilder(); + const i18nService = mock(); + i18nService.t.mockImplementation((key: string) => key); + sendListFiltersService = new SendListFiltersService(i18nService, formBuilder); + + // Mock sendViews$ observable + sendService.sendViews$ = of([]); + searchBarService.searchText$ = new BehaviorSubject(""); + + // Mock activeAccount$ observable for parent class ngOnInit + accountService.activeAccount$ = of({ id: "test-user-id" } as any); + policyService.policyAppliesToUser$ = jest.fn().mockReturnValue(of(false)); + + // Mock SearchService methods needed by base component + const mockSearchService = mock(); + mockSearchService.isSearchable.mockResolvedValue(false); + + await TestBed.configureTestingModule({ + imports: [SendV2Component], + providers: [ + { provide: SendService, useValue: sendService }, + { provide: I18nService, useValue: mock() }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: EnvironmentService, useValue: mock() }, + { provide: BroadcasterService, useValue: broadcasterService }, + { provide: SearchService, useValue: mockSearchService }, + { provide: PolicyService, useValue: policyService }, + { provide: SearchBarService, useValue: searchBarService }, + { provide: LogService, useValue: mock() }, + { provide: SendApiService, useValue: mock() }, + { provide: DialogService, useValue: mock() }, + { provide: ToastService, useValue: mock() }, + { provide: AccountService, useValue: accountService }, + { provide: SendListFiltersService, useValue: sendListFiltersService }, + { provide: ChangeDetectorRef, useValue: changeDetectorRef }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(SendV2Component); + component = fixture.componentInstance; + }); + + it("creates component", () => { + expect(component).toBeTruthy(); + }); + + it("initializes with correct default action", () => { + expect(component.action).toBe(""); + }); + + it("subscribes to broadcaster service on init", async () => { + await component.ngOnInit(); + expect(broadcasterService.subscribe).toHaveBeenCalledWith( + "SendV2Component", + expect.any(Function), + ); + }); + + it("unsubscribes from broadcaster service on destroy", () => { + component.ngOnDestroy(); + expect(broadcasterService.unsubscribe).toHaveBeenCalledWith("SendV2Component"); + }); + + it("enables search bar on init", async () => { + await component.ngOnInit(); + expect(searchBarService.setEnabled).toHaveBeenCalledWith(true); + }); + + it("disables search bar on destroy", () => { + component.ngOnDestroy(); + expect(searchBarService.setEnabled).toHaveBeenCalledWith(false); + }); + + describe("addSend", () => { + it("sets action to Add", async () => { + await component.addSend(); + expect(component.action).toBe("add"); + }); + + it("calls resetAndLoad on addEditComponent when component exists", async () => { + const mockAddEdit = mock(); + component.addEditComponent = mockAddEdit; + + await component.addSend(); + + expect(mockAddEdit.resetAndLoad).toHaveBeenCalled(); + }); + + it("does not throw when addEditComponent is null", async () => { + component.addEditComponent = null; + await expect(component.addSend()).resolves.not.toThrow(); + }); + }); + + describe("cancel", () => { + it("resets action to None", () => { + component.action = "edit"; + component.sendId = "test-id"; + + component.cancel(new SendView()); + + expect(component.action).toBe(""); + expect(component.sendId).toBeNull(); + }); + }); + + describe("deletedSend", () => { + it("refreshes the list and resets action and sendId", async () => { + component.action = "edit"; + component.sendId = "test-id"; + jest.spyOn(component, "refresh").mockResolvedValue(); + + const mockSend = new SendView(); + await component.deletedSend(mockSend); + + expect(component.refresh).toHaveBeenCalled(); + expect(component.action).toBe(""); + expect(component.sendId).toBeNull(); + }); + }); + + describe("savedSend", () => { + it("refreshes the list and selects the saved send", async () => { + jest.spyOn(component, "refresh").mockResolvedValue(); + jest.spyOn(component, "selectSend").mockResolvedValue(); + + const mockSend = new SendView(); + mockSend.id = "saved-send-id"; + + await component.savedSend(mockSend); + + expect(component.refresh).toHaveBeenCalled(); + expect(component.selectSend).toHaveBeenCalledWith("saved-send-id"); + }); + }); + + describe("selectSend", () => { + it("sets action to Edit and updates sendId", async () => { + await component.selectSend("new-send-id"); + + expect(component.action).toBe("edit"); + expect(component.sendId).toBe("new-send-id"); + }); + + it("updates addEditComponent when it exists", async () => { + const mockAddEdit = mock(); + component.addEditComponent = mockAddEdit; + + await component.selectSend("test-send-id"); + + expect(mockAddEdit.sendId).toBe("test-send-id"); + expect(mockAddEdit.refresh).toHaveBeenCalled(); + }); + + it("does not reload if same send is already selected in edit mode", async () => { + const mockAddEdit = mock(); + component.addEditComponent = mockAddEdit; + component.sendId = "same-id"; + component.action = "edit"; + + await component.selectSend("same-id"); + + expect(mockAddEdit.refresh).not.toHaveBeenCalled(); + }); + + it("reloads if selecting different send", async () => { + const mockAddEdit = mock(); + component.addEditComponent = mockAddEdit; + component.sendId = "old-id"; + component.action = "edit"; + + await component.selectSend("new-id"); + + expect(mockAddEdit.refresh).toHaveBeenCalled(); + }); + }); + + describe("selectedSendType", () => { + it("returns the type of the currently selected send", () => { + const mockSend1 = new SendView(); + mockSend1.id = "send-1"; + mockSend1.type = SendType.Text; + + const mockSend2 = new SendView(); + mockSend2.id = "send-2"; + mockSend2.type = SendType.File; + + component.sends = [mockSend1, mockSend2]; + component.sendId = "send-2"; + + expect(component.selectedSendType).toBe(SendType.File); + }); + + it("returns undefined when no send is selected", () => { + component.sends = []; + component.sendId = "non-existent"; + + expect(component.selectedSendType).toBeUndefined(); + }); + + it("returns undefined when sendId is null", () => { + const mockSend = new SendView(); + mockSend.id = "send-1"; + mockSend.type = SendType.Text; + + component.sends = [mockSend]; + component.sendId = null; + + expect(component.selectedSendType).toBeUndefined(); + }); + }); + + describe("viewSendMenu", () => { + let mockSend: SendView; + + beforeEach(() => { + mockSend = new SendView(); + mockSend.id = "test-send"; + mockSend.name = "Test Send"; + jest.clearAllMocks(); + }); + + it("creates menu with copy link option", () => { + jest.spyOn(component, "copy").mockResolvedValue(); + + component.viewSendMenu(mockSend); + + expect(utils.invokeMenu).toHaveBeenCalled(); + const menuItems = (utils.invokeMenu as jest.Mock).mock.calls[0][0]; + expect(menuItems.length).toBeGreaterThanOrEqual(2); // At minimum: copy link + delete + }); + + it("includes remove password option when send has password and is not disabled", () => { + mockSend.password = "test-password"; + mockSend.disabled = false; + jest.spyOn(component, "removePassword").mockResolvedValue(true); + + component.viewSendMenu(mockSend); + + expect(utils.invokeMenu).toHaveBeenCalled(); + const menuItems = (utils.invokeMenu as jest.Mock).mock.calls[0][0]; + expect(menuItems.length).toBe(3); // copy link + remove password + delete + }); + + it("excludes remove password option when send has no password", () => { + mockSend.password = null; + mockSend.disabled = false; + + component.viewSendMenu(mockSend); + + expect(utils.invokeMenu).toHaveBeenCalled(); + const menuItems = (utils.invokeMenu as jest.Mock).mock.calls[0][0]; + expect(menuItems.length).toBe(2); // copy link + delete (no remove password) + }); + + it("excludes remove password option when send is disabled", () => { + mockSend.password = "test-password"; + mockSend.disabled = true; + + component.viewSendMenu(mockSend); + + expect(utils.invokeMenu).toHaveBeenCalled(); + const menuItems = (utils.invokeMenu as jest.Mock).mock.calls[0][0]; + expect(menuItems.length).toBe(2); // copy link + delete (no remove password) + }); + + it("always includes delete option", () => { + jest.spyOn(component, "delete").mockResolvedValue(true); + jest.spyOn(component, "deletedSend").mockResolvedValue(); + + component.viewSendMenu(mockSend); + + expect(utils.invokeMenu).toHaveBeenCalled(); + const menuItems = (utils.invokeMenu as jest.Mock).mock.calls[0][0]; + // Delete is always the last item in the menu + expect(menuItems.length).toBeGreaterThan(0); + expect(menuItems[menuItems.length - 1]).toHaveProperty("label"); + expect(menuItems[menuItems.length - 1]).toHaveProperty("click"); + }); + }); + + describe("search bar subscription", () => { + it("updates searchText when search bar text changes", () => { + const searchSubject = new BehaviorSubject("initial"); + searchBarService.searchText$ = searchSubject; + + // Create new component to trigger constructor subscription + fixture = TestBed.createComponent(SendV2Component); + component = fixture.componentInstance; + + searchSubject.next("new search text"); + + expect(component.searchText).toBe("new search text"); + }); + }); + + describe("load", () => { + it("sets loading states correctly", async () => { + jest.spyOn(component, "search").mockResolvedValue(); + + expect(component.loaded).toBeFalsy(); + + await component.load(); + + expect(component.loading).toBe(false); + expect(component.loaded).toBe(true); + }); + + it("sets up sendViews$ subscription", async () => { + const mockSends = [new SendView(), new SendView()]; + sendService.sendViews$ = of(mockSends); + jest.spyOn(component, "search").mockResolvedValue(); + + await component.load(); + + // Give observable time to emit + await new Promise((resolve) => setTimeout(resolve, 10)); + + expect(component.sends).toEqual(mockSends); + }); + + it("calls onSuccessfulLoad when it is set", async () => { + jest.spyOn(component, "search").mockResolvedValue(); + const mockCallback = jest.fn().mockResolvedValue(undefined); + component.onSuccessfulLoad = mockCallback; + + await component.load(); + + expect(mockCallback).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/desktop/src/app/tools/send-v2/send-v2.component.ts b/apps/desktop/src/app/tools/send-v2/send-v2.component.ts new file mode 100644 index 00000000000..eb0856b76af --- /dev/null +++ b/apps/desktop/src/app/tools/send-v2/send-v2.component.ts @@ -0,0 +1,261 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { CommonModule } from "@angular/common"; +import { Component, OnInit, OnDestroy, ViewChild, NgZone, ChangeDetectorRef } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormsModule } from "@angular/forms"; +import { mergeMap, Subscription } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component"; +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"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { SendListFiltersService } from "@bitwarden/send-ui"; + +import { invokeMenu, RendererMenuItem } from "../../../utils"; +import { SearchBarService } from "../../layout/search/search-bar.service"; +import { AddEditComponent } from "../send/add-edit.component"; + +const Action = Object.freeze({ + /** No action is currently active. */ + None: "", + /** The user is adding a new Send. */ + Add: "add", + /** The user is editing an existing Send. */ + Edit: "edit", +} as const); + +type Action = (typeof Action)[keyof typeof Action]; + +const BroadcasterSubscriptionId = "SendV2Component"; + +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +@Component({ + selector: "app-send-v2", + imports: [CommonModule, JslibModule, FormsModule, AddEditComponent], + templateUrl: "./send-v2.component.html", +}) +export class SendV2Component extends BaseSendComponent implements OnInit, OnDestroy { + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals + @ViewChild(AddEditComponent) addEditComponent: AddEditComponent; + + // The ID of the currently selected Send item being viewed or edited + sendId: string; + + // Tracks the current UI state: viewing list (None), adding new Send (Add), or editing existing Send (Edit) + action: Action = Action.None; + + // Subscription for sendViews$ cleanup + private sendViewsSubscription: Subscription; + + constructor( + sendService: SendService, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + environmentService: EnvironmentService, + private broadcasterService: BroadcasterService, + ngZone: NgZone, + searchService: SearchService, + policyService: PolicyService, + private searchBarService: SearchBarService, + logService: LogService, + sendApiService: SendApiService, + dialogService: DialogService, + toastService: ToastService, + accountService: AccountService, + private cdr: ChangeDetectorRef, + private sendListFiltersService: SendListFiltersService, + ) { + super( + sendService, + i18nService, + platformUtilsService, + environmentService, + ngZone, + searchService, + policyService, + logService, + sendApiService, + dialogService, + toastService, + accountService, + ); + + // Listen to search bar changes and update the Send list filter + this.searchBarService.searchText$.pipe(takeUntilDestroyed()).subscribe((searchText) => { + this.searchText = searchText; + this.searchTextChanged(); + }); + + // Listen to filter changes from sidebar navigation + this.sendListFiltersService.filterForm.valueChanges + .pipe(takeUntilDestroyed()) + .subscribe((filters) => { + this.applySendTypeFilter(filters); + }); + } + + // Initialize the component: enable search bar, subscribe to sync events, and load Send items + async ngOnInit() { + this.searchBarService.setEnabled(true); + this.searchBarService.setPlaceholderText(this.i18nService.t("searchSends")); + + await super.ngOnInit(); + + // Read current filter synchronously to avoid race condition on navigation + const currentFilter = this.sendListFiltersService.filterForm.value; + this.applySendTypeFilter(currentFilter); + + // Listen for sync completion events to refresh the Send list + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.ngZone.run(async () => { + switch (message.command) { + case "syncCompleted": + await this.load(); + break; + } + }); + }); + await this.load(); + } + + // Apply send type filter to display: centralized logic for initial load and filter changes + private applySendTypeFilter(filters: Partial<{ sendType: SendType | null }>): void { + if (filters.sendType === null || filters.sendType === undefined) { + this.selectAll(); + } else { + this.selectType(filters.sendType); + } + } + + // Clean up subscriptions and disable search bar when component is destroyed + ngOnDestroy() { + this.sendViewsSubscription?.unsubscribe(); + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + this.searchBarService.setEnabled(false); + } + + // Load Send items from the service and display them in the list. + // Subscribes to sendViews$ observable to get updates when Sends change. + // Manually triggers change detection to ensure UI updates immediately. + // Note: The filter parameter is ignored in this implementation for desktop-specific behavior. + async load(filter: (send: SendView) => boolean = null) { + this.loading = true; + + // Recreate subscription on each load (required for sync refresh) + // Manual cleanup in ngOnDestroy is intentional - load() is called multiple times + this.sendViewsSubscription?.unsubscribe(); + + this.sendViewsSubscription = this.sendService.sendViews$ + .pipe( + mergeMap(async (sends) => { + this.sends = sends; + await this.search(null); + // Trigger change detection after data updates + this.cdr.detectChanges(); + }), + ) + // eslint-disable-next-line rxjs-angular/prefer-takeuntil + .subscribe(); + if (this.onSuccessfulLoad != null) { + await this.onSuccessfulLoad(); + } + this.loading = false; + this.loaded = true; + } + + // Open the add Send form to create a new Send item + async addSend() { + this.action = Action.Add; + if (this.addEditComponent != null) { + await this.addEditComponent.resetAndLoad(); + } + } + + // Close the add/edit form and return to the list view + cancel(s: SendView) { + this.action = Action.None; + this.sendId = null; + } + + // Handle when a Send is deleted: refresh the list and close the edit form + async deletedSend(s: SendView) { + await this.refresh(); + this.action = Action.None; + this.sendId = null; + } + + // Handle when a Send is saved: refresh the list and re-select the saved Send + async savedSend(s: SendView) { + await 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 + this.selectSend(s.id); + } + + // Select a Send from the list and open it in the edit form. + // If the same Send is already selected and in edit mode, do nothing to avoid unnecessary reloads. + async selectSend(sendId: string) { + if (sendId === this.sendId && this.action === Action.Edit) { + return; + } + this.action = Action.Edit; + this.sendId = sendId; + if (this.addEditComponent != null) { + this.addEditComponent.sendId = sendId; + await this.addEditComponent.refresh(); + } + } + + // Get the type (text or file) of the currently selected Send for the edit form + get selectedSendType() { + return this.sends.find((s) => s.id === this.sendId)?.type; + } + + // Show the right-click context menu for a Send with options to copy link, remove password, or delete + viewSendMenu(send: SendView) { + const menu: RendererMenuItem[] = []; + menu.push({ + label: this.i18nService.t("copyLink"), + click: () => this.copy(send), + }); + if (send.password && !send.disabled) { + menu.push({ + label: this.i18nService.t("removePassword"), + click: async () => { + await this.removePassword(send); + if (this.sendId === send.id) { + this.sendId = 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.selectSend(send.id); + } + }, + }); + } + menu.push({ + label: this.i18nService.t("delete"), + click: async () => { + await this.delete(send); + await this.deletedSend(send); + }, + }); + + invokeMenu(menu); + } +} diff --git a/apps/desktop/src/app/tools/send/add-edit.component.html b/apps/desktop/src/app/tools/send/add-edit.component.html index 0bf2e1778e0..639c80c9060 100644 --- a/apps/desktop/src/app/tools/send/add-edit.component.html +++ b/apps/desktop/src/app/tools/send/add-edit.component.html @@ -46,7 +46,9 @@
-
{{ send.file.fileName }} ({{ send.file.sizeName }})
+
+ {{ send.file.fileName }} ({{ send.file.sizeName }}) +
diff --git a/apps/desktop/src/app/tools/send/send.component.ts b/apps/desktop/src/app/tools/send/send.component.ts index 3605ca3d2dc..b58c3961bde 100644 --- a/apps/desktop/src/app/tools/send/send.component.ts +++ b/apps/desktop/src/app/tools/send/send.component.ts @@ -25,13 +25,16 @@ import { SearchBarService } from "../../layout/search/search-bar.service"; import { AddEditComponent } from "./add-edit.component"; -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -enum Action { - None = "", - Add = "add", - Edit = "edit", -} +const Action = Object.freeze({ + /** No action is currently active. */ + None: "", + /** The user is adding a new Send. */ + Add: "add", + /** The user is editing an existing Send. */ + Edit: "edit", +} as const); + +type Action = (typeof Action)[keyof typeof Action]; const BroadcasterSubscriptionId = "SendComponent"; diff --git a/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts b/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts index c88627250c9..414bbaca56f 100644 --- a/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts +++ b/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts @@ -136,6 +136,7 @@ describe("DesktopLoginComponentService", () => { codeChallenge, state, email, + undefined, ); } else { expect(ssoLoginService.setSsoState).toHaveBeenCalledWith(state); @@ -145,4 +146,55 @@ describe("DesktopLoginComponentService", () => { }); }); }); + + describe("redirectToSsoLoginWithOrganizationSsoIdentifier", () => { + // Array of all permutations of isAppImage and isDev + const permutations = [ + [true, false], // Case 1: isAppImage true + [false, true], // Case 2: isDev true + [true, true], // Case 3: all true + [false, false], // Case 4: all false + ]; + + permutations.forEach(([isAppImage, isDev]) => { + it("calls redirectToSso with orgSsoIdentifier", async () => { + (global as any).ipc.platform.isAppImage = isAppImage; + (global as any).ipc.platform.isDev = isDev; + + const email = "test@bitwarden.com"; + const state = "testState"; + const codeVerifier = "testCodeVerifier"; + const codeChallenge = "testCodeChallenge"; + const orgSsoIdentifier = "orgSsoId"; + + passwordGenerationService.generatePassword.mockResolvedValueOnce(state); + passwordGenerationService.generatePassword.mockResolvedValueOnce(codeVerifier); + jest.spyOn(Utils, "fromBufferToUrlB64").mockReturnValue(codeChallenge); + + await service.redirectToSsoLoginWithOrganizationSsoIdentifier(email, orgSsoIdentifier); + + if (isAppImage || isDev) { + expect(ipc.platform.localhostCallbackService.openSsoPrompt).toHaveBeenCalledWith( + codeChallenge, + state, + email, + orgSsoIdentifier, + ); + } else { + expect(ssoUrlService.buildSsoUrl).toHaveBeenCalledWith( + expect.any(String), + expect.any(String), + expect.any(String), + expect.any(String), + expect.any(String), + email, + orgSsoIdentifier, + ); + expect(ssoLoginService.setSsoState).toHaveBeenCalledWith(state); + expect(ssoLoginService.setCodeVerifier).toHaveBeenCalledWith(codeVerifier); + expect(platformUtilsService.launchUri).toHaveBeenCalled(); + } + }); + }); + }); }); diff --git a/apps/desktop/src/auth/login/desktop-login-component.service.ts b/apps/desktop/src/auth/login/desktop-login-component.service.ts index d7e7ba0178b..6ef39eaa018 100644 --- a/apps/desktop/src/auth/login/desktop-login-component.service.ts +++ b/apps/desktop/src/auth/login/desktop-login-component.service.ts @@ -48,11 +48,12 @@ export class DesktopLoginComponentService email: string, state: string, codeChallenge: string, + orgSsoIdentifier?: string, ): Promise { // For platforms that cannot support a protocol-based (e.g. bitwarden://) callback, we use a localhost callback // Otherwise, we launch the SSO component in a browser window and wait for the callback if (ipc.platform.isAppImage || ipc.platform.isDev) { - await this.initiateSsoThroughLocalhostCallback(email, state, codeChallenge); + await this.initiateSsoThroughLocalhostCallback(email, state, codeChallenge, orgSsoIdentifier); } else { const env = await firstValueFrom(this.environmentService.environment$); const webVaultUrl = env.getWebVaultUrl(); @@ -66,6 +67,7 @@ export class DesktopLoginComponentService state, codeChallenge, email, + orgSsoIdentifier, ); this.platformUtilsService.launchUri(ssoWebAppUrl); @@ -76,9 +78,15 @@ export class DesktopLoginComponentService email: string, state: string, challenge: string, + orgSsoIdentifier?: string, ): Promise { try { - await ipc.platform.localhostCallbackService.openSsoPrompt(challenge, state, email); + await ipc.platform.localhostCallbackService.openSsoPrompt( + challenge, + state, + email, + orgSsoIdentifier, + ); // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { diff --git a/apps/desktop/src/autofill/guards/reactive-vault-guard.ts b/apps/desktop/src/autofill/guards/reactive-vault-guard.ts new file mode 100644 index 00000000000..d16787ef46a --- /dev/null +++ b/apps/desktop/src/autofill/guards/reactive-vault-guard.ts @@ -0,0 +1,42 @@ +import { inject } from "@angular/core"; +import { CanActivateFn, Router } from "@angular/router"; +import { combineLatest, map, switchMap, distinctUntilChanged } 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 { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; + +/** + * Reactive route guard that redirects to the unlocked vault. + * Redirects to vault when unlocked in main window. + */ +export const reactiveUnlockVaultGuard: CanActivateFn = () => { + const router = inject(Router); + const authService = inject(AuthService); + const accountService = inject(AccountService); + const desktopSettingsService = inject(DesktopSettingsService); + + return combineLatest([accountService.activeAccount$, desktopSettingsService.modalMode$]).pipe( + switchMap(([account, modalMode]) => { + if (!account) { + return [true]; + } + + // Monitor when the vault has been unlocked. + return authService.authStatusFor$(account.id).pipe( + distinctUntilChanged(), + map((authStatus) => { + // If vault is unlocked and we're not in modal mode, redirect to vault + if (authStatus === AuthenticationStatus.Unlocked && !modalMode?.isModalModeActive) { + return router.createUrlTree(["/vault"]); + } + + // Otherwise keep user on the lock screen + return true; + }), + ); + }), + ); +}; diff --git a/apps/desktop/src/autofill/main/main-ssh-agent.service.ts b/apps/desktop/src/autofill/main/main-ssh-agent.service.ts index 595ef778bcf..31196e4cf98 100644 --- a/apps/desktop/src/autofill/main/main-ssh-agent.service.ts +++ b/apps/desktop/src/autofill/main/main-ssh-agent.service.ts @@ -37,7 +37,7 @@ export class MainSshAgentService { init() { // handle sign request passing to UI sshagent - .serve(async (err: Error, sshUiRequest: sshagent.SshUiRequest) => { + .serve(async (err: Error | null, sshUiRequest: sshagent.SshUiRequest): Promise => { // clear all old (> SIGN_TIMEOUT) requests this.requestResponses = this.requestResponses.filter( (response) => response.timestamp > new Date(Date.now() - this.SIGN_TIMEOUT), diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-create.component.html b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.html new file mode 100644 index 00000000000..67fc76aa317 --- /dev/null +++ b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.html @@ -0,0 +1,66 @@ +
+ + +
+ + +

+ {{ "savePasskeyQuestion" | i18n }} +

+
+ + +
+
+ + +
+
+ +
+ {{ "noMatchingLoginsForSite" | i18n }} +
+ +
+
+ + + + {{ c.subTitle }} + {{ "save" | i18n }} + + + + + + +
+
diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-create.component.spec.ts b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.spec.ts new file mode 100644 index 00000000000..778215895ee --- /dev/null +++ b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.spec.ts @@ -0,0 +1,238 @@ +import { TestBed } from "@angular/core/testing"; +import { Router } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject, of } from "rxjs"; + +import { AccountService, Account } 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 { UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService } from "@bitwarden/components"; +import { PasswordRepromptService } from "@bitwarden/vault"; + +import { DesktopAutofillService } from "../../../autofill/services/desktop-autofill.service"; +import { DesktopSettingsService } from "../../../platform/services/desktop-settings.service"; +import { + DesktopFido2UserInterfaceService, + DesktopFido2UserInterfaceSession, +} from "../../services/desktop-fido2-user-interface.service"; + +import { Fido2CreateComponent } from "./fido2-create.component"; + +describe("Fido2CreateComponent", () => { + let component: Fido2CreateComponent; + let mockDesktopSettingsService: MockProxy; + let mockFido2UserInterfaceService: MockProxy; + let mockAccountService: MockProxy; + let mockCipherService: MockProxy; + let mockDesktopAutofillService: MockProxy; + let mockDialogService: MockProxy; + let mockDomainSettingsService: MockProxy; + let mockLogService: MockProxy; + let mockPasswordRepromptService: MockProxy; + let mockRouter: MockProxy; + let mockSession: MockProxy; + let mockI18nService: MockProxy; + + const activeAccountSubject = new BehaviorSubject({ + id: "test-user-id" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); + + beforeEach(async () => { + mockDesktopSettingsService = mock(); + mockFido2UserInterfaceService = mock(); + mockAccountService = mock(); + mockCipherService = mock(); + mockDesktopAutofillService = mock(); + mockDialogService = mock(); + mockDomainSettingsService = mock(); + mockLogService = mock(); + mockPasswordRepromptService = mock(); + mockRouter = mock(); + mockSession = mock(); + mockI18nService = mock(); + + mockFido2UserInterfaceService.getCurrentSession.mockReturnValue(mockSession); + mockAccountService.activeAccount$ = activeAccountSubject; + + await TestBed.configureTestingModule({ + providers: [ + Fido2CreateComponent, + { provide: DesktopSettingsService, useValue: mockDesktopSettingsService }, + { provide: DesktopFido2UserInterfaceService, useValue: mockFido2UserInterfaceService }, + { provide: AccountService, useValue: mockAccountService }, + { provide: CipherService, useValue: mockCipherService }, + { provide: DesktopAutofillService, useValue: mockDesktopAutofillService }, + { provide: DialogService, useValue: mockDialogService }, + { provide: DomainSettingsService, useValue: mockDomainSettingsService }, + { provide: LogService, useValue: mockLogService }, + { provide: PasswordRepromptService, useValue: mockPasswordRepromptService }, + { provide: Router, useValue: mockRouter }, + { provide: I18nService, useValue: mockI18nService }, + ], + }).compileComponents(); + + component = TestBed.inject(Fido2CreateComponent); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + function createMockCiphers(): CipherView[] { + const cipher1 = new CipherView(); + cipher1.id = "cipher-1"; + cipher1.name = "Test Cipher 1"; + cipher1.type = CipherType.Login; + cipher1.login = { + username: "test1@example.com", + uris: [{ uri: "https://example.com", match: null }], + matchesUri: jest.fn().mockReturnValue(true), + get hasFido2Credentials() { + return false; + }, + } as any; + cipher1.reprompt = CipherRepromptType.None; + cipher1.deletedDate = null; + + return [cipher1]; + } + + describe("ngOnInit", () => { + beforeEach(() => { + mockSession.getRpId.mockResolvedValue("example.com"); + Object.defineProperty(mockDesktopAutofillService, "lastRegistrationRequest", { + get: jest.fn().mockReturnValue({ + userHandle: new Uint8Array([1, 2, 3]), + }), + configurable: true, + }); + mockDomainSettingsService.getUrlEquivalentDomains.mockReturnValue(of(new Set())); + }); + + it("should initialize session and set show header to false", async () => { + const mockCiphers = createMockCiphers(); + mockCipherService.getAllDecrypted.mockResolvedValue(mockCiphers); + + await component.ngOnInit(); + + expect(mockFido2UserInterfaceService.getCurrentSession).toHaveBeenCalled(); + expect(component.session).toBe(mockSession); + }); + + it("should show error dialog when no active session found", async () => { + mockFido2UserInterfaceService.getCurrentSession.mockReturnValue(null); + mockDialogService.openSimpleDialog.mockResolvedValue(false); + + await component.ngOnInit(); + + expect(mockDialogService.openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "unableToSavePasskey" }, + content: { key: "closeThisBitwardenWindow" }, + type: "danger", + acceptButtonText: { key: "closeThisWindow" }, + acceptAction: expect.any(Function), + cancelButtonText: null, + }); + }); + }); + + describe("addCredentialToCipher", () => { + beforeEach(() => { + component.session = mockSession; + }); + + it("should add passkey to cipher", async () => { + const cipher = createMockCiphers()[0]; + + await component.addCredentialToCipher(cipher); + + expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(true, cipher); + }); + + it("should not add passkey when password reprompt is cancelled", async () => { + const cipher = createMockCiphers()[0]; + cipher.reprompt = CipherRepromptType.Password; + mockPasswordRepromptService.showPasswordPrompt.mockResolvedValue(false); + + await component.addCredentialToCipher(cipher); + + expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(false, cipher); + }); + + it("should call openSimpleDialog when cipher already has a fido2 credential", async () => { + const cipher = createMockCiphers()[0]; + Object.defineProperty(cipher.login, "hasFido2Credentials", { + get: jest.fn().mockReturnValue(true), + }); + mockDialogService.openSimpleDialog.mockResolvedValue(true); + + await component.addCredentialToCipher(cipher); + + expect(mockDialogService.openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "overwritePasskey" }, + content: { key: "alreadyContainsPasskey" }, + type: "warning", + }); + expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(true, cipher); + }); + + it("should not add passkey when user cancels overwrite dialog", async () => { + const cipher = createMockCiphers()[0]; + Object.defineProperty(cipher.login, "hasFido2Credentials", { + get: jest.fn().mockReturnValue(true), + }); + mockDialogService.openSimpleDialog.mockResolvedValue(false); + + await component.addCredentialToCipher(cipher); + + expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(false, cipher); + }); + }); + + describe("confirmPasskey", () => { + beforeEach(() => { + component.session = mockSession; + }); + + it("should confirm passkey creation successfully", async () => { + await component.confirmPasskey(); + + expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(true); + }); + + it("should call openSimpleDialog when session is null", async () => { + component.session = null; + mockDialogService.openSimpleDialog.mockResolvedValue(false); + + await component.confirmPasskey(); + + expect(mockDialogService.openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "unableToSavePasskey" }, + content: { key: "closeThisBitwardenWindow" }, + type: "danger", + acceptButtonText: { key: "closeThisWindow" }, + acceptAction: expect.any(Function), + cancelButtonText: null, + }); + }); + }); + + describe("closeModal", () => { + it("should close modal and notify session", async () => { + component.session = mockSession; + + await component.closeModal(); + + expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(false); + expect(mockSession.confirmChosenCipher).toHaveBeenCalledWith(null); + }); + }); +}); diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts new file mode 100644 index 00000000000..67237bedccd --- /dev/null +++ b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts @@ -0,0 +1,219 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from "@angular/core"; +import { RouterModule, Router } from "@angular/router"; +import { combineLatest, map, Observable, Subject, switchMap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BitwardenShield, NoResults } from "@bitwarden/assets/svg"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + DialogService, + BadgeModule, + ButtonModule, + DialogModule, + IconModule, + ItemModule, + SectionComponent, + TableModule, + SectionHeaderComponent, + BitIconButtonComponent, + SimpleDialogOptions, +} from "@bitwarden/components"; +import { PasswordRepromptService } from "@bitwarden/vault"; + +import { DesktopAutofillService } from "../../../autofill/services/desktop-autofill.service"; +import { DesktopSettingsService } from "../../../platform/services/desktop-settings.service"; +import { + DesktopFido2UserInterfaceService, + DesktopFido2UserInterfaceSession, +} from "../../services/desktop-fido2-user-interface.service"; + +@Component({ + standalone: true, + imports: [ + CommonModule, + RouterModule, + SectionHeaderComponent, + BitIconButtonComponent, + TableModule, + JslibModule, + IconModule, + ButtonModule, + DialogModule, + SectionComponent, + ItemModule, + BadgeModule, + ], + templateUrl: "fido2-create.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Fido2CreateComponent implements OnInit, OnDestroy { + session?: DesktopFido2UserInterfaceSession = null; + ciphers$: Observable; + private destroy$ = new Subject(); + readonly Icons = { BitwardenShield, NoResults }; + + private get DIALOG_MESSAGES() { + return { + unexpectedErrorShort: { + title: { key: "unexpectedErrorShort" }, + content: { key: "closeThisBitwardenWindow" }, + type: "danger", + acceptButtonText: { key: "closeThisWindow" }, + cancelButtonText: null as null, + acceptAction: async () => this.dialogService.closeAll(), + }, + unableToSavePasskey: { + title: { key: "unableToSavePasskey" }, + content: { key: "closeThisBitwardenWindow" }, + type: "danger", + acceptButtonText: { key: "closeThisWindow" }, + cancelButtonText: null as null, + acceptAction: async () => this.dialogService.closeAll(), + }, + overwritePasskey: { + title: { key: "overwritePasskey" }, + content: { key: "alreadyContainsPasskey" }, + type: "warning", + }, + } as const satisfies Record; + } + + constructor( + private readonly desktopSettingsService: DesktopSettingsService, + private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService, + private readonly accountService: AccountService, + private readonly cipherService: CipherService, + private readonly desktopAutofillService: DesktopAutofillService, + private readonly dialogService: DialogService, + private readonly domainSettingsService: DomainSettingsService, + private readonly passwordRepromptService: PasswordRepromptService, + private readonly router: Router, + ) {} + + async ngOnInit(): Promise { + this.session = this.fido2UserInterfaceService.getCurrentSession(); + + if (this.session) { + const rpid = await this.session.getRpId(); + this.initializeCiphersObservable(rpid); + } else { + await this.showErrorDialog(this.DIALOG_MESSAGES.unableToSavePasskey); + } + } + + async ngOnDestroy(): Promise { + this.destroy$.next(); + this.destroy$.complete(); + await this.closeModal(); + } + + async addCredentialToCipher(cipher: CipherView): Promise { + const isConfirmed = await this.validateCipherAccess(cipher); + + try { + if (!this.session) { + throw new Error("Missing session"); + } + + this.session.notifyConfirmCreateCredential(isConfirmed, cipher); + } catch { + await this.showErrorDialog(this.DIALOG_MESSAGES.unableToSavePasskey); + return; + } + + await this.closeModal(); + } + + async confirmPasskey(): Promise { + try { + if (!this.session) { + throw new Error("Missing session"); + } + + this.session.notifyConfirmCreateCredential(true); + } catch { + await this.showErrorDialog(this.DIALOG_MESSAGES.unableToSavePasskey); + } + + await this.closeModal(); + } + + async closeModal(): Promise { + await this.desktopSettingsService.setModalMode(false); + await this.accountService.setShowHeader(true); + + if (this.session) { + this.session.notifyConfirmCreateCredential(false); + this.session.confirmChosenCipher(null); + } + + await this.router.navigate(["/"]); + } + + private initializeCiphersObservable(rpid: string): void { + const lastRegistrationRequest = this.desktopAutofillService.lastRegistrationRequest; + + if (!lastRegistrationRequest || !rpid) { + return; + } + + const userHandle = Fido2Utils.bufferToString( + new Uint8Array(lastRegistrationRequest.userHandle), + ); + + this.ciphers$ = combineLatest([ + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + this.domainSettingsService.getUrlEquivalentDomains(rpid), + ]).pipe( + switchMap(async ([activeUserId, equivalentDomains]) => { + if (!activeUserId) { + return []; + } + + try { + const allCiphers = await this.cipherService.getAllDecrypted(activeUserId); + return allCiphers.filter( + (cipher) => + cipher != null && + cipher.type == CipherType.Login && + cipher.login?.matchesUri(rpid, equivalentDomains) && + Fido2Utils.cipherHasNoOtherPasskeys(cipher, userHandle) && + !cipher.deletedDate, + ); + } catch { + await this.showErrorDialog(this.DIALOG_MESSAGES.unexpectedErrorShort); + return []; + } + }), + ); + } + + private async validateCipherAccess(cipher: CipherView): Promise { + if (cipher.login.hasFido2Credentials) { + const overwriteConfirmed = await this.dialogService.openSimpleDialog( + this.DIALOG_MESSAGES.overwritePasskey, + ); + + if (!overwriteConfirmed) { + return false; + } + } + + if (cipher.reprompt) { + return this.passwordRepromptService.showPasswordPrompt(); + } + + return true; + } + + private async showErrorDialog(config: SimpleDialogOptions): Promise { + await this.dialogService.openSimpleDialog(config); + await this.closeModal(); + } +} diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.html b/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.html new file mode 100644 index 00000000000..792934deedc --- /dev/null +++ b/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.html @@ -0,0 +1,44 @@ +
+ + +
+ + +

+ {{ "savePasskeyQuestion" | i18n }} +

+
+ + +
+
+ +
+ +
+ +
+ {{ "passkeyAlreadyExists" | i18n }} + {{ "applicationDoesNotSupportDuplicates" | i18n }} +
+ +
+
+
+
diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.spec.ts b/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.spec.ts new file mode 100644 index 00000000000..6a465136458 --- /dev/null +++ b/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.spec.ts @@ -0,0 +1,78 @@ +import { NO_ERRORS_SCHEMA } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { Router } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { DesktopSettingsService } from "../../../platform/services/desktop-settings.service"; +import { + DesktopFido2UserInterfaceService, + DesktopFido2UserInterfaceSession, +} from "../../services/desktop-fido2-user-interface.service"; + +import { Fido2ExcludedCiphersComponent } from "./fido2-excluded-ciphers.component"; + +describe("Fido2ExcludedCiphersComponent", () => { + let component: Fido2ExcludedCiphersComponent; + let fixture: ComponentFixture; + let mockDesktopSettingsService: MockProxy; + let mockFido2UserInterfaceService: MockProxy; + let mockAccountService: MockProxy; + let mockRouter: MockProxy; + let mockSession: MockProxy; + let mockI18nService: MockProxy; + + beforeEach(async () => { + mockDesktopSettingsService = mock(); + mockFido2UserInterfaceService = mock(); + mockAccountService = mock(); + mockRouter = mock(); + mockSession = mock(); + mockI18nService = mock(); + + mockFido2UserInterfaceService.getCurrentSession.mockReturnValue(mockSession); + + await TestBed.configureTestingModule({ + imports: [Fido2ExcludedCiphersComponent], + providers: [ + { provide: DesktopSettingsService, useValue: mockDesktopSettingsService }, + { provide: DesktopFido2UserInterfaceService, useValue: mockFido2UserInterfaceService }, + { provide: AccountService, useValue: mockAccountService }, + { provide: Router, useValue: mockRouter }, + { provide: I18nService, useValue: mockI18nService }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(Fido2ExcludedCiphersComponent); + component = fixture.componentInstance; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe("ngOnInit", () => { + it("should initialize session", async () => { + await component.ngOnInit(); + + expect(mockFido2UserInterfaceService.getCurrentSession).toHaveBeenCalled(); + expect(component.session).toBe(mockSession); + }); + }); + + describe("closeModal", () => { + it("should close modal and notify session when session exists", async () => { + component.session = mockSession; + + await component.closeModal(); + + expect(mockDesktopSettingsService.setModalMode).toHaveBeenCalledWith(false); + expect(mockAccountService.setShowHeader).toHaveBeenCalledWith(true); + expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(false); + expect(mockRouter.navigate).toHaveBeenCalledWith(["/"]); + }); + }); +}); diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.ts b/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.ts new file mode 100644 index 00000000000..049771c2252 --- /dev/null +++ b/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.ts @@ -0,0 +1,78 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from "@angular/core"; +import { RouterModule, Router } from "@angular/router"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BitwardenShield, NoResults } from "@bitwarden/assets/svg"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { + BadgeModule, + ButtonModule, + DialogModule, + IconModule, + ItemModule, + SectionComponent, + TableModule, + SectionHeaderComponent, + BitIconButtonComponent, +} from "@bitwarden/components"; + +import { DesktopSettingsService } from "../../../platform/services/desktop-settings.service"; +import { + DesktopFido2UserInterfaceService, + DesktopFido2UserInterfaceSession, +} from "../../services/desktop-fido2-user-interface.service"; + +@Component({ + standalone: true, + imports: [ + CommonModule, + RouterModule, + SectionHeaderComponent, + BitIconButtonComponent, + TableModule, + JslibModule, + IconModule, + ButtonModule, + DialogModule, + SectionComponent, + ItemModule, + BadgeModule, + ], + templateUrl: "fido2-excluded-ciphers.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Fido2ExcludedCiphersComponent implements OnInit, OnDestroy { + session?: DesktopFido2UserInterfaceSession = null; + readonly Icons = { BitwardenShield, NoResults }; + + constructor( + private readonly desktopSettingsService: DesktopSettingsService, + private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService, + private readonly accountService: AccountService, + private readonly router: Router, + ) {} + + async ngOnInit(): Promise { + this.session = this.fido2UserInterfaceService.getCurrentSession(); + } + + async ngOnDestroy(): Promise { + await this.closeModal(); + } + + async closeModal(): Promise { + // Clean up modal state + await this.desktopSettingsService.setModalMode(false); + await this.accountService.setShowHeader(true); + + // Clean up session state + if (this.session) { + this.session.notifyConfirmCreateCredential(false); + this.session.confirmChosenCipher(null); + } + + // Navigate away + await this.router.navigate(["/"]); + } +} diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.html b/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.html new file mode 100644 index 00000000000..ed04993d09f --- /dev/null +++ b/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.html @@ -0,0 +1,37 @@ +
+ + +
+ + +

{{ "passkeyLogin" | i18n }}

+
+ +
+
+ + + + + {{ c.subTitle }} + {{ "select" | i18n }} + + + +
diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.spec.ts b/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.spec.ts new file mode 100644 index 00000000000..70ef4461f6a --- /dev/null +++ b/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.spec.ts @@ -0,0 +1,196 @@ +import { NO_ERRORS_SCHEMA } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { Router } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { AccountService, Account } 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 { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { PasswordRepromptService } from "@bitwarden/vault"; + +import { DesktopSettingsService } from "../../../platform/services/desktop-settings.service"; +import { + DesktopFido2UserInterfaceService, + DesktopFido2UserInterfaceSession, +} from "../../services/desktop-fido2-user-interface.service"; + +import { Fido2VaultComponent } from "./fido2-vault.component"; + +describe("Fido2VaultComponent", () => { + let component: Fido2VaultComponent; + let fixture: ComponentFixture; + let mockDesktopSettingsService: MockProxy; + let mockFido2UserInterfaceService: MockProxy; + let mockCipherService: MockProxy; + let mockAccountService: MockProxy; + let mockLogService: MockProxy; + let mockPasswordRepromptService: MockProxy; + let mockRouter: MockProxy; + let mockSession: MockProxy; + let mockI18nService: MockProxy; + + const mockActiveAccount = { id: "test-user-id", email: "test@example.com" }; + const mockCipherIds = ["cipher-1", "cipher-2", "cipher-3"]; + + beforeEach(async () => { + mockDesktopSettingsService = mock(); + mockFido2UserInterfaceService = mock(); + mockCipherService = mock(); + mockAccountService = mock(); + mockLogService = mock(); + mockPasswordRepromptService = mock(); + mockRouter = mock(); + mockSession = mock(); + mockI18nService = mock(); + + mockAccountService.activeAccount$ = of(mockActiveAccount as Account); + mockFido2UserInterfaceService.getCurrentSession.mockReturnValue(mockSession); + mockSession.availableCipherIds$ = of(mockCipherIds); + mockCipherService.cipherListViews$ = jest.fn().mockReturnValue(of([])); + + await TestBed.configureTestingModule({ + imports: [Fido2VaultComponent], + providers: [ + { provide: DesktopSettingsService, useValue: mockDesktopSettingsService }, + { provide: DesktopFido2UserInterfaceService, useValue: mockFido2UserInterfaceService }, + { provide: CipherService, useValue: mockCipherService }, + { provide: AccountService, useValue: mockAccountService }, + { provide: LogService, useValue: mockLogService }, + { provide: PasswordRepromptService, useValue: mockPasswordRepromptService }, + { provide: Router, useValue: mockRouter }, + { provide: I18nService, useValue: mockI18nService }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(Fido2VaultComponent); + component = fixture.componentInstance; + }); + + const mockCiphers: any[] = [ + { + id: "cipher-1", + name: "Test Cipher 1", + type: CipherType.Login, + login: { + username: "test1@example.com", + }, + reprompt: CipherRepromptType.None, + deletedDate: null, + }, + { + id: "cipher-2", + name: "Test Cipher 2", + type: CipherType.Login, + login: { + username: "test2@example.com", + }, + reprompt: CipherRepromptType.None, + deletedDate: null, + }, + { + id: "cipher-3", + name: "Test Cipher 3", + type: CipherType.Login, + login: { + username: "test3@example.com", + }, + reprompt: CipherRepromptType.Password, + deletedDate: null, + }, + ]; + + describe("ngOnInit", () => { + it("should initialize session and load ciphers successfully", async () => { + mockCipherService.cipherListViews$ = jest.fn().mockReturnValue(of(mockCiphers)); + + await component.ngOnInit(); + + expect(mockFido2UserInterfaceService.getCurrentSession).toHaveBeenCalled(); + expect(component.session).toBe(mockSession); + expect(component.cipherIds$).toBe(mockSession.availableCipherIds$); + expect(mockCipherService.cipherListViews$).toHaveBeenCalledWith(mockActiveAccount.id); + }); + + it("should handle when no active session found", async () => { + mockFido2UserInterfaceService.getCurrentSession.mockReturnValue(null); + + await component.ngOnInit(); + + expect(component.session).toBeNull(); + }); + + it("should filter out deleted ciphers", async () => { + const ciphersWithDeleted = [ + ...mockCiphers.slice(0, 1), + { ...mockCiphers[1], deletedDate: new Date() }, + ...mockCiphers.slice(2), + ]; + mockCipherService.cipherListViews$ = jest.fn().mockReturnValue(of(ciphersWithDeleted)); + + await component.ngOnInit(); + await new Promise((resolve) => setTimeout(resolve, 0)); + + let ciphersResult: CipherView[] = []; + component.ciphers$.subscribe((ciphers) => { + ciphersResult = ciphers; + }); + + expect(ciphersResult).toHaveLength(2); + expect(ciphersResult.every((cipher) => !cipher.deletedDate)).toBe(true); + }); + }); + + describe("chooseCipher", () => { + const cipher = mockCiphers[0]; + + beforeEach(() => { + component.session = mockSession; + }); + + it("should choose cipher when access is validated", async () => { + cipher.reprompt = CipherRepromptType.None; + + await component.chooseCipher(cipher); + + expect(mockSession.confirmChosenCipher).toHaveBeenCalledWith(cipher.id, true); + expect(mockRouter.navigate).toHaveBeenCalledWith(["/"]); + }); + + it("should prompt for password when cipher requires reprompt", async () => { + cipher.reprompt = CipherRepromptType.Password; + mockPasswordRepromptService.showPasswordPrompt.mockResolvedValue(true); + + await component.chooseCipher(cipher); + + expect(mockPasswordRepromptService.showPasswordPrompt).toHaveBeenCalled(); + expect(mockSession.confirmChosenCipher).toHaveBeenCalledWith(cipher.id, true); + }); + + it("should not choose cipher when password reprompt is cancelled", async () => { + cipher.reprompt = CipherRepromptType.Password; + mockPasswordRepromptService.showPasswordPrompt.mockResolvedValue(false); + + await component.chooseCipher(cipher); + + expect(mockPasswordRepromptService.showPasswordPrompt).toHaveBeenCalled(); + expect(mockSession.confirmChosenCipher).toHaveBeenCalledWith(cipher.id, false); + }); + }); + + describe("closeModal", () => { + it("should close modal and notify session", async () => { + component.session = mockSession; + + await component.closeModal(); + + expect(mockRouter.navigate).toHaveBeenCalledWith(["/"]); + expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(false); + expect(mockSession.confirmChosenCipher).toHaveBeenCalledWith(null); + }); + }); +}); diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts b/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts new file mode 100644 index 00000000000..897e825c53e --- /dev/null +++ b/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts @@ -0,0 +1,161 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from "@angular/core"; +import { RouterModule, Router } from "@angular/router"; +import { + firstValueFrom, + map, + combineLatest, + of, + BehaviorSubject, + Observable, + Subject, + takeUntil, +} from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BitwardenShield } from "@bitwarden/assets/svg"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherRepromptType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + BadgeModule, + ButtonModule, + DialogModule, + DialogService, + IconModule, + ItemModule, + SectionComponent, + TableModule, + BitIconButtonComponent, + SectionHeaderComponent, +} from "@bitwarden/components"; +import { PasswordRepromptService } from "@bitwarden/vault"; + +import { DesktopSettingsService } from "../../../platform/services/desktop-settings.service"; +import { + DesktopFido2UserInterfaceService, + DesktopFido2UserInterfaceSession, +} from "../../services/desktop-fido2-user-interface.service"; + +@Component({ + standalone: true, + imports: [ + CommonModule, + RouterModule, + SectionHeaderComponent, + BitIconButtonComponent, + TableModule, + JslibModule, + IconModule, + ButtonModule, + DialogModule, + SectionComponent, + ItemModule, + BadgeModule, + ], + templateUrl: "fido2-vault.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Fido2VaultComponent implements OnInit, OnDestroy { + session?: DesktopFido2UserInterfaceSession = null; + private destroy$ = new Subject(); + private ciphersSubject = new BehaviorSubject([]); + ciphers$: Observable = this.ciphersSubject.asObservable(); + cipherIds$: Observable | undefined; + readonly Icons = { BitwardenShield }; + + constructor( + private readonly desktopSettingsService: DesktopSettingsService, + private readonly fido2UserInterfaceService: DesktopFido2UserInterfaceService, + private readonly cipherService: CipherService, + private readonly accountService: AccountService, + private readonly dialogService: DialogService, + private readonly logService: LogService, + private readonly passwordRepromptService: PasswordRepromptService, + private readonly router: Router, + ) {} + + async ngOnInit(): Promise { + this.session = this.fido2UserInterfaceService.getCurrentSession(); + this.cipherIds$ = this.session?.availableCipherIds$; + await this.loadCiphers(); + } + + async ngOnDestroy(): Promise { + this.destroy$.next(); + this.destroy$.complete(); + } + + async chooseCipher(cipher: CipherView): Promise { + if (!this.session) { + await this.dialogService.openSimpleDialog({ + title: { key: "unexpectedErrorShort" }, + content: { key: "closeThisBitwardenWindow" }, + type: "danger", + acceptButtonText: { key: "closeThisWindow" }, + cancelButtonText: null, + }); + await this.closeModal(); + + return; + } + + const isConfirmed = await this.validateCipherAccess(cipher); + this.session.confirmChosenCipher(cipher.id, isConfirmed); + + await this.closeModal(); + } + + async closeModal(): Promise { + await this.desktopSettingsService.setModalMode(false); + await this.accountService.setShowHeader(true); + + if (this.session) { + this.session.notifyConfirmCreateCredential(false); + this.session.confirmChosenCipher(null); + } + + await this.router.navigate(["/"]); + } + + private async loadCiphers(): Promise { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + if (!activeUserId) { + return; + } + + // Combine cipher list with optional cipher IDs filter + combineLatest([this.cipherService.cipherListViews$(activeUserId), this.cipherIds$ || of(null)]) + .pipe( + map(([ciphers, cipherIds]) => { + // Filter out deleted ciphers + const activeCiphers = ciphers.filter((cipher) => !cipher.deletedDate); + + // If specific IDs provided, filter by them + if (cipherIds?.length > 0) { + return activeCiphers.filter((cipher) => cipherIds.includes(cipher.id as string)); + } + + return activeCiphers; + }), + takeUntil(this.destroy$), + ) + .subscribe({ + next: (ciphers) => this.ciphersSubject.next(ciphers as CipherView[]), + error: (error: unknown) => this.logService.error("Failed to load ciphers", error), + }); + } + + private async validateCipherAccess(cipher: CipherView): Promise { + if (cipher.reprompt !== CipherRepromptType.None) { + return this.passwordRepromptService.showPasswordPrompt(); + } + + return true; + } +} diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts index e839ac223b7..6a7a8459ea9 100644 --- a/apps/desktop/src/autofill/preload.ts +++ b/apps/desktop/src/autofill/preload.ts @@ -12,6 +12,8 @@ export default { runCommand: (params: RunCommandParams): Promise> => ipcRenderer.invoke("autofill.runCommand", params), + listenerReady: () => ipcRenderer.send("autofill.listenerReady"), + listenPasskeyRegistration: ( fn: ( clientId: number, @@ -130,6 +132,25 @@ export default { }, ); }, + + listenNativeStatus: ( + fn: (clientId: number, sequenceNumber: number, status: { key: string; value: string }) => void, + ) => { + ipcRenderer.on( + "autofill.nativeStatus", + ( + event, + data: { + clientId: number; + sequenceNumber: number; + status: { key: string; value: string }; + }, + ) => { + const { clientId, sequenceNumber, status } = data; + fn(clientId, sequenceNumber, status); + }, + ); + }, configureAutotype: (enabled: boolean, keyboardShortcut: string[]) => { ipcRenderer.send("autofill.configureAutotype", { enabled, keyboardShortcut }); }, diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index 18f4652d72a..c50964e31e3 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -1,6 +1,8 @@ import { Injectable, OnDestroy } from "@angular/core"; import { Subject, + combineLatest, + debounceTime, distinctUntilChanged, filter, firstValueFrom, @@ -8,10 +10,11 @@ import { mergeMap, switchMap, takeUntil, - EMPTY, } 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 { DeviceType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -48,6 +51,7 @@ import type { NativeWindowObject } from "./desktop-fido2-user-interface.service" @Injectable() export class DesktopAutofillService implements OnDestroy { private destroy$ = new Subject(); + private registrationRequest: autofill.PasskeyRegistrationRequest; constructor( private logService: LogService, @@ -55,6 +59,7 @@ export class DesktopAutofillService implements OnDestroy { private configService: ConfigService, private fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction, private accountService: AccountService, + private authService: AuthService, private platformUtilsService: PlatformUtilsService, ) {} @@ -68,28 +73,56 @@ export class DesktopAutofillService implements OnDestroy { .getFeatureFlag$(FeatureFlag.MacOsNativeCredentialSync) .pipe( distinctUntilChanged(), - switchMap((enabled) => { - if (!enabled) { - return EMPTY; - } - - return this.accountService.activeAccount$.pipe( - map((account) => account?.id), - filter((userId): userId is UserId => userId != null), - switchMap((userId) => this.cipherService.cipherViews$(userId)), + filter((enabled) => enabled === true), // Only proceed if feature is enabled + switchMap(() => { + return combineLatest([ + this.accountService.activeAccount$.pipe( + map((account) => account?.id), + filter((userId): userId is UserId => userId != null), + ), + this.authService.activeAccountStatus$, + ]).pipe( + // Only proceed when the vault is unlocked + filter(([, status]) => status === AuthenticationStatus.Unlocked), + // Then get cipher views + 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 - // when the user logs out. Maybe by subscribing to the encrypted ciphers observable instead. + debounceTime(100), // just a precaution to not spam the sync if there are multiple changes (we typically observe a null change) + // No filter for empty arrays here - we want to sync even if there are 0 items + filter((cipherViewMap) => cipherViewMap !== null), + mergeMap((cipherViewMap) => this.sync(Object.values(cipherViewMap ?? []))), takeUntil(this.destroy$), ) .subscribe(); + // Listen for sign out to clear credentials + this.authService.activeAccountStatus$ + .pipe( + filter((status) => status === AuthenticationStatus.LoggedOut), + mergeMap(() => this.sync([])), // sync an empty array + takeUntil(this.destroy$), + ) + .subscribe(); + this.listenIpc(); } + async adHocSync(): Promise { + this.logService.debug("Performing AdHoc sync"); + const account = await firstValueFrom(this.accountService.activeAccount$); + const userId = account?.id; + + if (!userId) { + throw new Error("No active user found"); + } + + const cipherViewMap = await firstValueFrom(this.cipherService.cipherViews$(userId)); + this.logService.info("Performing AdHoc sync", Object.values(cipherViewMap ?? [])); + await this.sync(Object.values(cipherViewMap ?? [])); + } + /** Give metadata about all available credentials in the users vault */ async sync(cipherViews: CipherView[]) { const status = await this.status(); @@ -130,6 +163,11 @@ export class DesktopAutofillService implements OnDestroy { })); } + this.logService.info("Syncing autofill credentials", { + fido2Credentials, + passwordCredentials, + }); + const syncResult = await ipc.autofill.runCommand({ namespace: "autofill", command: "sync", @@ -155,107 +193,152 @@ export class DesktopAutofillService implements OnDestroy { }); } + get lastRegistrationRequest() { + return this.registrationRequest; + } + listenIpc() { - ipc.autofill.listenPasskeyRegistration((clientId, sequenceNumber, request, callback) => { - this.logService.warning("listenPasskeyRegistration", clientId, sequenceNumber, request); - this.logService.warning( - "listenPasskeyRegistration2", - this.convertRegistrationRequest(request), - ); + ipc.autofill.listenPasskeyRegistration(async (clientId, sequenceNumber, request, callback) => { + if (!(await this.configService.getFeatureFlag(FeatureFlag.MacOsNativeCredentialSync))) { + this.logService.debug( + "listenPasskeyRegistration: MacOsNativeCredentialSync feature flag is disabled", + ); + callback(new Error("MacOsNativeCredentialSync feature flag is disabled"), null); + return; + } + + this.registrationRequest = request; + + this.logService.debug("listenPasskeyRegistration", clientId, sequenceNumber, request); + this.logService.debug("listenPasskeyRegistration2", this.convertRegistrationRequest(request)); const controller = new AbortController(); - void this.fido2AuthenticatorService - .makeCredential( + + try { + const response = await this.fido2AuthenticatorService.makeCredential( this.convertRegistrationRequest(request), - { windowXy: request.windowXy }, + { windowXy: normalizePosition(request.windowXy) }, controller, - ) - .then((response) => { - callback(null, this.convertRegistrationResponse(request, response)); - }) - .catch((error) => { - this.logService.error("listenPasskeyRegistration error", error); - callback(error, null); - }); + ); + + callback(null, this.convertRegistrationResponse(request, response)); + } catch (error) { + this.logService.error("listenPasskeyRegistration error", error); + callback(error, null); + } }); ipc.autofill.listenPasskeyAssertionWithoutUserInterface( async (clientId, sequenceNumber, request, callback) => { - this.logService.warning( + if (!(await this.configService.getFeatureFlag(FeatureFlag.MacOsNativeCredentialSync))) { + this.logService.debug( + "listenPasskeyAssertionWithoutUserInterface: MacOsNativeCredentialSync feature flag is disabled", + ); + callback(new Error("MacOsNativeCredentialSync feature flag is disabled"), null); + return; + } + + this.logService.debug( "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 this.cipherService.decrypt(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( - new Uint8Array(parseCredentialId(decrypted.login.fido2Credentials?.[0].credentialId)), - ); - } - const controller = new AbortController(); - void this.fido2AuthenticatorService - .getAssertion( - this.convertAssertionRequest(request), - { windowXy: request.windowXy }, + + try { + // 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 this.cipherService.decrypt(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( + new Uint8Array(parseCredentialId(decrypted.login.fido2Credentials?.[0].credentialId)), + ); + } + + const response = await this.fido2AuthenticatorService.getAssertion( + this.convertAssertionRequest(request, true), + { windowXy: normalizePosition(request.windowXy) }, controller, - ) - .then((response) => { - callback(null, this.convertAssertionResponse(request, response)); - }) - .catch((error) => { - this.logService.error("listenPasskeyAssertion error", error); - callback(error, null); - }); + ); + + callback(null, this.convertAssertionResponse(request, response)); + } catch (error) { + this.logService.error("listenPasskeyAssertion error", error); + callback(error, null); + return; + } }, ); ipc.autofill.listenPasskeyAssertion(async (clientId, sequenceNumber, request, callback) => { - this.logService.warning("listenPasskeyAssertion", clientId, sequenceNumber, request); + if (!(await this.configService.getFeatureFlag(FeatureFlag.MacOsNativeCredentialSync))) { + this.logService.debug( + "listenPasskeyAssertion: MacOsNativeCredentialSync feature flag is disabled", + ); + callback(new Error("MacOsNativeCredentialSync feature flag is disabled"), null); + return; + } + + this.logService.debug("listenPasskeyAssertion", clientId, sequenceNumber, request); const controller = new AbortController(); - void this.fido2AuthenticatorService - .getAssertion( + try { + const response = await this.fido2AuthenticatorService.getAssertion( this.convertAssertionRequest(request), - { windowXy: request.windowXy }, + { windowXy: normalizePosition(request.windowXy) }, controller, - ) - .then((response) => { - callback(null, this.convertAssertionResponse(request, response)); - }) - .catch((error) => { - this.logService.error("listenPasskeyAssertion error", error); - callback(error, null); - }); + ); + + callback(null, this.convertAssertionResponse(request, response)); + } catch (error) { + this.logService.error("listenPasskeyAssertion error", error); + callback(error, null); + } }); + + // Listen for native status messages + ipc.autofill.listenNativeStatus(async (clientId, sequenceNumber, status) => { + if (!(await this.configService.getFeatureFlag(FeatureFlag.MacOsNativeCredentialSync))) { + this.logService.debug( + "listenNativeStatus: MacOsNativeCredentialSync feature flag is disabled", + ); + return; + } + + this.logService.info("Received native status", status.key, status.value); + if (status.key === "request-sync") { + // perform ad-hoc sync + await this.adHocSync(); + } + }); + + ipc.autofill.listenerReady(); } private convertRegistrationRequest( @@ -277,7 +360,10 @@ export class DesktopAutofillService implements OnDestroy { alg, type: "public-key", })), - excludeCredentialDescriptorList: [], + excludeCredentialDescriptorList: request.excludedCredentials.map((credentialId) => ({ + id: new Uint8Array(credentialId), + type: "public-key" as const, + })), requireResidentKey: true, requireUserVerification: request.userVerification === "required" || request.userVerification === "preferred", @@ -309,18 +395,19 @@ export class DesktopAutofillService implements OnDestroy { request: | autofill.PasskeyAssertionRequest | autofill.PasskeyAssertionWithoutUserInterfaceRequest, + assumeUserPresence: boolean = false, ): Fido2AuthenticatorGetAssertionParams { let allowedCredentials; if ("credentialId" in request) { allowedCredentials = [ { - id: new Uint8Array(request.credentialId), + id: new Uint8Array(request.credentialId).buffer, type: "public-key" as const, }, ]; } else { allowedCredentials = request.allowedCredentials.map((credentialId) => ({ - id: new Uint8Array(credentialId), + id: new Uint8Array(credentialId).buffer, type: "public-key" as const, })); } @@ -333,7 +420,7 @@ export class DesktopAutofillService implements OnDestroy { 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 + assumeUserPresence, }; } @@ -358,3 +445,13 @@ export class DesktopAutofillService implements OnDestroy { this.destroy$.complete(); } } + +function normalizePosition(position: { x: number; y: number }): { x: number; y: number } { + // Add 100 pixels to the x-coordinate to offset the native OS dialog positioning. + const xPositionOffset = 100; + + return { + x: Math.round(position.x + xPositionOffset), + y: Math.round(position.y), + }; +} 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 3caf13fa5b7..cf29370840d 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 @@ -43,9 +43,7 @@ export type NativeWindowObject = { windowXy?: { x: number; y: number }; }; -export class DesktopFido2UserInterfaceService - implements Fido2UserInterfaceServiceAbstraction -{ +export class DesktopFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction { constructor( private authService: AuthService, private cipherService: CipherService, @@ -66,7 +64,7 @@ export class DesktopFido2UserInterfaceService nativeWindowObject: NativeWindowObject, abortController?: AbortController, ): Promise { - this.logService.warning("newSession", fallbackSupported, abortController, nativeWindowObject); + this.logService.debug("newSession", fallbackSupported, abortController, nativeWindowObject); const session = new DesktopFido2UserInterfaceSession( this.authService, this.cipherService, @@ -94,9 +92,11 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi ) {} private confirmCredentialSubject = new Subject(); - private createdCipher: Cipher; - private availableCipherIdsSubject = new BehaviorSubject(null); + private updatedCipher: CipherView; + + private rpId = new BehaviorSubject(null); + private availableCipherIdsSubject = new BehaviorSubject([""]); /** * Observable that emits available cipher IDs once they're confirmed by the UI */ @@ -114,7 +114,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi assumeUserPresence, masterPasswordRepromptRequired, }: PickCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> { - this.logService.warning("pickCredential desktop function", { + this.logService.debug("pickCredential desktop function", { cipherIds, userVerification, assumeUserPresence, @@ -123,6 +123,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi try { // Check if we can return the credential without user interaction + await this.accountService.setShowHeader(false); if (assumeUserPresence && cipherIds.length === 1 && !masterPasswordRepromptRequired) { this.logService.debug( "shortcut - Assuming user presence and returning cipherId", @@ -136,22 +137,27 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi // make the cipherIds available to the UI. this.availableCipherIdsSubject.next(cipherIds); - await this.showUi("/passkeys", this.windowObject.windowXy); + await this.showUi("/fido2-assertion", this.windowObject.windowXy, false); const chosenCipherResponse = await this.waitForUiChosenCipher(); this.logService.debug("Received chosen cipher", chosenCipherResponse); return { - cipherId: chosenCipherResponse.cipherId, - userVerified: chosenCipherResponse.userVerified, + 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); + await this.accountService.setShowHeader(true); } } + async getRpId(): Promise { + return firstValueFrom(this.rpId.pipe(filter((id) => id != null))); + } + confirmChosenCipher(cipherId: string, userVerified: boolean = false): void { this.chosenCipherSubject.next({ cipherId, userVerified }); this.chosenCipherSubject.complete(); @@ -159,7 +165,7 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi private async waitForUiChosenCipher( timeoutMs: number = 60000, - ): Promise<{ cipherId: string; userVerified: boolean } | undefined> { + ): Promise<{ cipherId?: string; userVerified: boolean } | undefined> { try { return await lastValueFrom(this.chosenCipherSubject.pipe(timeout(timeoutMs))); } catch { @@ -174,7 +180,10 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi /** * Notifies the Fido2UserInterfaceSession that the UI operations has completed and it can return to the OS. */ - notifyConfirmNewCredential(confirmed: boolean): void { + notifyConfirmCreateCredential(confirmed: boolean, updatedCipher?: CipherView): void { + if (updatedCipher) { + this.updatedCipher = updatedCipher; + } this.confirmCredentialSubject.next(confirmed); this.confirmCredentialSubject.complete(); } @@ -195,60 +204,79 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi async confirmNewCredential({ credentialName, userName, + userHandle, userVerification, rpId, }: NewCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> { - this.logService.warning( + this.logService.debug( "confirmNewCredential", credentialName, userName, + userHandle, userVerification, rpId, ); + this.rpId.next(rpId); try { - await this.showUi("/passkeys", this.windowObject.windowXy); + await this.showUi("/fido2-creation", this.windowObject.windowXy, false); // 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 }; + if (this.updatedCipher) { + await this.updateCredential(this.updatedCipher); + return { cipherId: this.updatedCipher.id, userVerified: userVerification }; + } else { + // Create the cipher + const createdCipher = await this.createCipher({ + credentialName, + userName, + rpId, + userHandle, + userVerification, + }); + return { cipherId: createdCipher.id, userVerified: userVerification }; + } } finally { // Make sure to clean up so the app is never stuck in modal mode? await this.desktopSettingsService.setModalMode(false); + await this.accountService.setShowHeader(true); } } - private async showUi(route: string, position?: { x: number; y: number }): Promise { + private async hideUi(): Promise { + await this.desktopSettingsService.setModalMode(false); + await this.router.navigate(["/"]); + } + + private async showUi( + route: string, + position?: { x: number; y: number }, + showTrafficButtons: boolean = false, + disableRedirect?: boolean, + ): Promise { // Load the UI: - await this.desktopSettingsService.setModalMode(true, position); - await this.router.navigate(["/passkeys"]); + await this.desktopSettingsService.setModalMode(true, showTrafficButtons, position); + await this.accountService.setShowHeader(showTrafficButtons); + await this.router.navigate([ + route, + { + "disable-redirect": disableRedirect || null, + }, + ]); } /** - * Can be called by the UI to create a new credential with user input etc. + * Can be called by the UI to create a new cipher with user input etc. * @param param0 */ - async createCredential({ credentialName, userName, rpId }: NewCredentialParams): Promise { + async createCipher({ credentialName, userName, rpId }: NewCredentialParams): Promise { // Store the passkey on a new cipher to avoid replacing something important + const cipher = new CipherView(); cipher.name = credentialName; @@ -267,32 +295,81 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + if (!activeUserId) { + throw new Error("No active user ID found!"); + } + const encCipher = await this.cipherService.encrypt(cipher, activeUserId); - const createdCipher = await this.cipherService.createWithServer(encCipher); - this.createdCipher = createdCipher; + try { + const createdCipher = await this.cipherService.createWithServer(encCipher); - return createdCipher; + return createdCipher; + } catch { + throw new Error("Unable to create cipher"); + } + } + + async updateCredential(cipher: CipherView): Promise { + this.logService.info("updateCredential"); + await firstValueFrom( + this.accountService.activeAccount$.pipe( + map(async (a) => { + if (a) { + const encCipher = await this.cipherService.encrypt(cipher, a.id); + await this.cipherService.updateWithServer(encCipher); + } + }), + ), + ); } async informExcludedCredential(existingCipherIds: string[]): Promise { - this.logService.warning("informExcludedCredential", existingCipherIds); + this.logService.debug("informExcludedCredential", existingCipherIds); + + // make the cipherIds available to the UI. + this.availableCipherIdsSubject.next(existingCipherIds); + + await this.accountService.setShowHeader(false); + await this.showUi("/fido2-excluded", this.windowObject.windowXy, false); } async ensureUnlockedVault(): Promise { - this.logService.warning("ensureUnlockedVault"); + this.logService.debug("ensureUnlockedVault"); const status = await firstValueFrom(this.authService.activeAccountStatus$); if (status !== AuthenticationStatus.Unlocked) { - throw new Error("Vault is not unlocked"); + await this.showUi("/lock", this.windowObject.windowXy, true, true); + + let status2: AuthenticationStatus; + try { + status2 = await lastValueFrom( + this.authService.activeAccountStatus$.pipe( + filter((s) => s === AuthenticationStatus.Unlocked), + take(1), + timeout(1000 * 60 * 5), // 5 minutes + ), + ); + } catch (error) { + this.logService.warning("Error while waiting for vault to unlock", error); + } + + if (status2 === AuthenticationStatus.Unlocked) { + await this.router.navigate(["/"]); + } + + if (status2 !== AuthenticationStatus.Unlocked) { + await this.hideUi(); + throw new Error("Vault is not unlocked"); + } } } async informCredentialNotFound(): Promise { - this.logService.warning("informCredentialNotFound"); + this.logService.debug("informCredentialNotFound"); } async close() { - this.logService.warning("close"); + this.logService.debug("close"); } } diff --git a/apps/desktop/src/billing/app/accounts/premium.component.html b/apps/desktop/src/billing/app/accounts/premium.component.html index d88602bed1e..c5f9722f133 100644 --- a/apps/desktop/src/billing/app/accounts/premium.component.html +++ b/apps/desktop/src/billing/app/accounts/premium.component.html @@ -13,7 +13,7 @@
  • - {{ "premiumSignUpStorage" | i18n }} + {{ "premiumSignUpStorageV2" | i18n: `${storageProvidedGb} GB` }}
  • diff --git a/apps/desktop/src/billing/app/accounts/premium.component.ts b/apps/desktop/src/billing/app/accounts/premium.component.ts index 637969c1a21..4aff0cc03e1 100644 --- a/apps/desktop/src/billing/app/accounts/premium.component.ts +++ b/apps/desktop/src/billing/app/accounts/premium.component.ts @@ -3,6 +3,7 @@ import { Component } from "@angular/core"; 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 { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; 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"; @@ -28,6 +29,7 @@ export class PremiumComponent extends BasePremiumComponent { billingAccountProfileStateService: BillingAccountProfileStateService, toastService: ToastService, accountService: AccountService, + billingApiService: BillingApiServiceAbstraction, ) { super( i18nService, @@ -39,6 +41,7 @@ export class PremiumComponent extends BasePremiumComponent { billingAccountProfileStateService, toastService, accountService, + billingApiService, ); } } 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 04a2f389781..59d9dcce7fe 100644 --- a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts @@ -16,9 +16,6 @@ export abstract class DesktopBiometricsService extends BiometricsService { abstract enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise; abstract hasPersistentKey(userId: UserId): Promise; /* Enables the v2 biometrics re-write. This will stay enabled until the application is restarted. */ - abstract enableWindowsV2Biometrics(): Promise; - abstract isWindowsV2BiometricsEnabled(): Promise; - /* Enables the v2 biometrics re-write. This will stay enabled until the application is restarted. */ abstract enableLinuxV2Biometrics(): Promise; abstract isLinuxV2BiometricsEnabled(): Promise; } diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts index db7c7c8f7fa..e30e4af3bac 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 @@ -58,10 +58,6 @@ export class MainBiometricsIPCListener { message.userId as UserId, SymmetricCryptoKey.fromString(message.key as string), ); - case BiometricAction.EnableWindowsV2: - return await this.biometricService.enableWindowsV2Biometrics(); - case BiometricAction.IsWindowsV2Enabled: - return await this.biometricService.isWindowsV2BiometricsEnabled(); case BiometricAction.EnableLinuxV2: return await this.biometricService.enableLinuxV2Biometrics(); case BiometricAction.IsLinuxV2Enabled: 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 index be9e1f841e1..0cbe032aa76 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts @@ -20,7 +20,6 @@ import { MainBiometricsService } from "./main-biometrics.service"; import { WindowsBiometricsSystem } from "./native-v2"; 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", () => { @@ -61,7 +60,7 @@ describe("MainBiometricsService", function () { const internalService = (sut as any).osBiometricsService; expect(internalService).not.toBeNull(); - expect(internalService).toBeInstanceOf(OsBiometricsServiceWindows); + expect(internalService).toBeInstanceOf(WindowsBiometricsSystem); }); it("Should create a biometrics service specific for MacOs", () => { @@ -289,78 +288,6 @@ describe("MainBiometricsService", function () { }); }); - describe("enableWindowsV2Biometrics", () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it("enables Windows V2 biometrics when platform is win32 and not already enabled", async () => { - const sut = new MainBiometricsService( - i18nService, - windowMain, - logService, - "win32", - biometricStateService, - encryptService, - cryptoFunctionService, - ); - - await sut.enableWindowsV2Biometrics(); - - expect(logService.info).toHaveBeenCalledWith( - "[BiometricsMain] Loading native biometrics module v2 for windows", - ); - expect(await sut.isWindowsV2BiometricsEnabled()).toBe(true); - const internalService = (sut as any).osBiometricsService; - expect(internalService).not.toBeNull(); - expect(internalService).toBeInstanceOf(WindowsBiometricsSystem); - }); - - it("should not enable Windows V2 biometrics when platform is not win32", async () => { - const sut = new MainBiometricsService( - i18nService, - windowMain, - logService, - "darwin", - biometricStateService, - encryptService, - cryptoFunctionService, - ); - - await sut.enableWindowsV2Biometrics(); - - expect(logService.info).not.toHaveBeenCalled(); - expect(await sut.isWindowsV2BiometricsEnabled()).toBe(false); - }); - - it("should not enable Windows V2 biometrics when already enabled", async () => { - const sut = new MainBiometricsService( - i18nService, - windowMain, - logService, - "win32", - biometricStateService, - encryptService, - cryptoFunctionService, - ); - - // Enable it first - await sut.enableWindowsV2Biometrics(); - - // Enable it again - await sut.enableWindowsV2Biometrics(); - - expect(logService.info).toHaveBeenCalledWith( - "[BiometricsMain] Loading native biometrics module v2 for windows", - ); - expect(logService.info).toHaveBeenCalledTimes(1); - expect(await sut.isWindowsV2BiometricsEnabled()).toBe(true); - const internalService = (sut as any).osBiometricsService; - expect(internalService).not.toBeNull(); - expect(internalService).toBeInstanceOf(WindowsBiometricsSystem); - }); - }); - describe("pass through methods that call platform specific osBiometricsService methods", () => { const userId = newGuid() as UserId; let sut: MainBiometricsService; 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 da532828314..ad9ec62afcc 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts @@ -16,7 +16,6 @@ import { OsBiometricService } from "./os-biometrics.service"; export class MainBiometricsService extends DesktopBiometricsService { private osBiometricsService: OsBiometricService; private shouldAutoPrompt = true; - private windowsV2BiometricsEnabled = false; private linuxV2BiometricsEnabled = false; constructor( @@ -30,15 +29,10 @@ export class MainBiometricsService extends DesktopBiometricsService { ) { super(); if (platform === "win32") { - // eslint-disable-next-line - const OsBiometricsServiceWindows = require("./os-biometrics-windows.service").default; - this.osBiometricsService = new OsBiometricsServiceWindows( + this.osBiometricsService = new WindowsBiometricsSystem( this.i18nService, this.windowMain, this.logService, - this.biometricStateService, - this.encryptService, - this.cryptoFunctionService, ); } else if (platform === "darwin") { // eslint-disable-next-line @@ -156,22 +150,6 @@ export class MainBiometricsService extends DesktopBiometricsService { return await this.osBiometricsService.hasPersistentKey(userId); } - async enableWindowsV2Biometrics(): Promise { - if (this.platform === "win32" && !this.windowsV2BiometricsEnabled) { - this.logService.info("[BiometricsMain] Loading native biometrics module v2 for windows"); - this.osBiometricsService = new WindowsBiometricsSystem( - this.i18nService, - this.windowMain, - this.logService, - ); - this.windowsV2BiometricsEnabled = true; - } - } - - async isWindowsV2BiometricsEnabled(): Promise { - return this.windowsV2BiometricsEnabled; - } - async enableLinuxV2Biometrics(): Promise { if (this.platform === "linux" && !this.linuxV2BiometricsEnabled) { this.logService.info("[BiometricsMain] Loading native biometrics module v2 for linux"); diff --git a/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.spec.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.spec.ts deleted file mode 100644 index f301efc70e7..00000000000 --- a/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.spec.ts +++ /dev/null @@ -1,378 +0,0 @@ -import { randomBytes } from "node:crypto"; - -import { BrowserWindow } from "electron"; -import { mock } from "jest-mock-extended"; - -import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.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"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { UserId } from "@bitwarden/common/types/guid"; -import { biometrics, passwords } from "@bitwarden/desktop-napi"; -import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-management"; - -import { WindowMain } from "../../main/window.main"; - -import OsBiometricsServiceWindows from "./os-biometrics-windows.service"; - -import OsDerivedKey = biometrics.OsDerivedKey; - -jest.mock("@bitwarden/desktop-napi", () => { - return { - biometrics: { - available: jest.fn().mockResolvedValue(true), - getBiometricSecret: jest.fn().mockResolvedValue(""), - setBiometricSecret: jest.fn().mockResolvedValue(""), - deleteBiometricSecret: jest.fn(), - deriveKeyMaterial: jest.fn().mockResolvedValue({ - keyB64: "", - ivB64: "", - }), - prompt: jest.fn().mockResolvedValue(true), - }, - passwords: { - getPassword: jest.fn().mockResolvedValue(null), - deletePassword: jest.fn().mockImplementation(() => {}), - isAvailable: jest.fn(), - PASSWORD_NOT_FOUND: "Password not found", - }, - }; -}); - -describe("OsBiometricsServiceWindows", function () { - const i18nService = mock(); - const windowMain = mock(); - const browserWindow = mock(); - const encryptionService: EncryptService = mock(); - const cryptoFunctionService: CryptoFunctionService = mock(); - const biometricStateService: BiometricStateService = mock(); - const logService = mock(); - - let service: OsBiometricsServiceWindows; - - const key = new SymmetricCryptoKey(new Uint8Array(64)); - const userId = "test-user-id" as UserId; - const serviceKey = "Bitwarden_biometric"; - const storageKey = `${userId}_user_biometric`; - - beforeEach(() => { - windowMain.win = browserWindow; - - service = new OsBiometricsServiceWindows( - i18nService, - windowMain, - logService, - biometricStateService, - encryptionService, - cryptoFunctionService, - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("getBiometricsFirstUnlockStatusForUser", () => { - const userId = "test-user-id" as UserId; - it("should return Available when client key half is set", async () => { - (service as any).clientKeyHalves = new Map(); - (service as any).clientKeyHalves.set(userId, new Uint8Array([1, 2, 3, 4])); - const result = await service.getBiometricsFirstUnlockStatusForUser(userId); - expect(result).toBe(BiometricsStatus.Available); - }); - it("should return UnlockNeeded when client key half is not set", async () => { - (service as any).clientKeyHalves = new Map(); - const result = await service.getBiometricsFirstUnlockStatusForUser(userId); - expect(result).toBe(BiometricsStatus.UnlockNeeded); - }); - }); - - describe("getOrCreateBiometricEncryptionClientKeyHalf", () => { - it("should return cached key half if already present", async () => { - const cachedKeyHalf = new Uint8Array([10, 20, 30]); - (service as any).clientKeyHalves.set(userId.toString(), cachedKeyHalf); - const result = await service.getOrCreateBiometricEncryptionClientKeyHalf(userId, key); - expect(result).toBe(cachedKeyHalf); - }); - - it("should decrypt and return existing encrypted client key half", async () => { - biometricStateService.getEncryptedClientKeyHalf = jest - .fn() - .mockResolvedValue(new Uint8Array([1, 2, 3])); - const decrypted = new Uint8Array([4, 5, 6]); - encryptionService.decryptBytes = jest.fn().mockResolvedValue(decrypted); - - const result = await service.getOrCreateBiometricEncryptionClientKeyHalf(userId, key); - - expect(biometricStateService.getEncryptedClientKeyHalf).toHaveBeenCalledWith(userId); - expect(encryptionService.decryptBytes).toHaveBeenCalledWith(new Uint8Array([1, 2, 3]), key); - expect(result).toEqual(decrypted); - expect((service as any).clientKeyHalves.get(userId.toString())).toEqual(decrypted); - }); - - it("should generate, encrypt, store, and cache a new key half if none exists", async () => { - biometricStateService.getEncryptedClientKeyHalf = jest.fn().mockResolvedValue(null); - const randomBytes = new Uint8Array([7, 8, 9]); - cryptoFunctionService.randomBytes = jest.fn().mockResolvedValue(randomBytes); - const encrypted = new Uint8Array([10, 11, 12]); - encryptionService.encryptBytes = jest.fn().mockResolvedValue(encrypted); - biometricStateService.setEncryptedClientKeyHalf = jest.fn().mockResolvedValue(undefined); - - const result = await service.getOrCreateBiometricEncryptionClientKeyHalf(userId, key); - - expect(cryptoFunctionService.randomBytes).toHaveBeenCalledWith(32); - expect(encryptionService.encryptBytes).toHaveBeenCalledWith(randomBytes, key); - expect(biometricStateService.setEncryptedClientKeyHalf).toHaveBeenCalledWith( - encrypted, - userId, - ); - expect(result).toEqual(randomBytes); - expect((service as any).clientKeyHalves.get(userId.toString())).toEqual(randomBytes); - }); - }); - - describe("supportsBiometrics", () => { - it("should return true if biometrics are available", async () => { - biometrics.available = jest.fn().mockResolvedValue(true); - - const result = await service.supportsBiometrics(); - - expect(result).toBe(true); - }); - - it("should return false if biometrics are not available", async () => { - biometrics.available = jest.fn().mockResolvedValue(false); - - const result = await service.supportsBiometrics(); - - expect(result).toBe(false); - }); - }); - - describe("getBiometricKey", () => { - beforeEach(() => { - biometrics.prompt = jest.fn().mockResolvedValue(true); - }); - - it("should return null when unsuccessfully authenticated biometrics", async () => { - biometrics.prompt = jest.fn().mockResolvedValue(false); - - const result = await service.getBiometricKey(userId); - - expect(result).toBeNull(); - }); - - it.each([null, undefined, ""])( - "should throw error when no biometric key is found '%s'", - async (password) => { - passwords.getPassword = jest.fn().mockResolvedValue(password); - - await expect(service.getBiometricKey(userId)).rejects.toThrow( - "Biometric key not found for user", - ); - - expect(passwords.getPassword).toHaveBeenCalledWith(serviceKey, storageKey); - }, - ); - - it.each([[false], [true]])( - "should return the biometricKey and setBiometricSecret called if password is not encrypted and cached clientKeyHalves is %s", - async (haveClientKeyHalves) => { - const clientKeyHalveBytes = new Uint8Array([1, 2, 3]); - if (haveClientKeyHalves) { - service["clientKeyHalves"].set(userId, clientKeyHalveBytes); - } - const biometricKey = key.toBase64(); - passwords.getPassword = jest.fn().mockResolvedValue(biometricKey); - biometrics.deriveKeyMaterial = jest.fn().mockResolvedValue({ - keyB64: "testKeyB64", - ivB64: "testIvB64", - } satisfies OsDerivedKey); - - const result = await service.getBiometricKey(userId); - - expect(result.toBase64()).toBe(biometricKey); - expect(passwords.getPassword).toHaveBeenCalledWith(serviceKey, storageKey); - expect(biometrics.setBiometricSecret).toHaveBeenCalledWith( - serviceKey, - storageKey, - biometricKey, - { - osKeyPartB64: "testKeyB64", - clientKeyPartB64: haveClientKeyHalves - ? Utils.fromBufferToB64(clientKeyHalveBytes) - : undefined, - }, - "testIvB64", - ); - }, - ); - - it.each([[false], [true]])( - "should return the biometricKey if password is encrypted and cached clientKeyHalves is %s", - async (haveClientKeyHalves) => { - const clientKeyHalveBytes = new Uint8Array([1, 2, 3]); - if (haveClientKeyHalves) { - service["clientKeyHalves"].set(userId, clientKeyHalveBytes); - } - const biometricKey = key.toBase64(); - const biometricKeyEncrypted = "2.testId|data|mac"; - passwords.getPassword = jest.fn().mockResolvedValue(biometricKeyEncrypted); - biometrics.getBiometricSecret = jest.fn().mockResolvedValue(biometricKey); - biometrics.deriveKeyMaterial = jest.fn().mockResolvedValue({ - keyB64: "testKeyB64", - ivB64: "testIvB64", - } satisfies OsDerivedKey); - - const result = await service.getBiometricKey(userId); - - expect(result.toBase64()).toBe(biometricKey); - expect(passwords.getPassword).toHaveBeenCalledWith(serviceKey, storageKey); - expect(biometrics.setBiometricSecret).not.toHaveBeenCalled(); - expect(biometrics.getBiometricSecret).toHaveBeenCalledWith(serviceKey, storageKey, { - osKeyPartB64: "testKeyB64", - clientKeyPartB64: haveClientKeyHalves - ? Utils.fromBufferToB64(clientKeyHalveBytes) - : undefined, - }); - }, - ); - }); - - describe("deleteBiometricKey", () => { - const serviceName = "Bitwarden_biometric"; - const keyName = "test-user-id_user_biometric"; - - it("should delete biometric key successfully", async () => { - await service.deleteBiometricKey(userId); - - expect(passwords.deletePassword).toHaveBeenCalledWith(serviceName, keyName); - }); - - it.each([[false], [true]])("should not throw error if key found: %s", async (keyFound) => { - if (!keyFound) { - passwords.deletePassword = jest - .fn() - .mockRejectedValue(new Error(passwords.PASSWORD_NOT_FOUND)); - } - - await service.deleteBiometricKey(userId); - - expect(passwords.deletePassword).toHaveBeenCalledWith(serviceName, keyName); - if (!keyFound) { - expect(logService.debug).toHaveBeenCalledWith( - "[OsBiometricService] Biometric key %s not found for service %s.", - keyName, - serviceName, - ); - } - }); - - it("should throw error when deletePassword for key throws unexpected errors", async () => { - const error = new Error("Unexpected error"); - passwords.deletePassword = jest.fn().mockRejectedValue(error); - - await expect(service.deleteBiometricKey(userId)).rejects.toThrow(error); - - expect(passwords.deletePassword).toHaveBeenCalledWith(serviceName, keyName); - }); - }); - - describe("authenticateBiometric", () => { - const hwnd = randomBytes(32).buffer; - const consentMessage = "Test Windows Hello Consent Message"; - - beforeEach(() => { - windowMain.win.getNativeWindowHandle = jest.fn().mockReturnValue(hwnd); - i18nService.t.mockReturnValue(consentMessage); - }); - - it("should return true when biometric authentication is successful", async () => { - const result = await service.authenticateBiometric(); - - expect(result).toBe(true); - expect(biometrics.prompt).toHaveBeenCalledWith(hwnd, consentMessage); - }); - - it("should return false when biometric authentication fails", async () => { - biometrics.prompt = jest.fn().mockResolvedValue(false); - - const result = await service.authenticateBiometric(); - - expect(result).toBe(false); - expect(biometrics.prompt).toHaveBeenCalledWith(hwnd, consentMessage); - }); - }); - - describe("getStorageDetails", () => { - it.each([ - ["testClientKeyHalfB64", "testIvB64"], - [undefined, "testIvB64"], - ["testClientKeyHalfB64", null], - [undefined, null], - ])( - "should derive key material and ivB64 and return it when os key half not saved yet", - async (clientKeyHalfB64, ivB64) => { - service["setIv"](ivB64); - - const derivedKeyMaterial = { - keyB64: "derivedKeyB64", - ivB64: "derivedIvB64", - }; - biometrics.deriveKeyMaterial = jest.fn().mockResolvedValue(derivedKeyMaterial); - - const result = await service["getStorageDetails"]({ clientKeyHalfB64 }); - - expect(result).toEqual({ - key_material: { - osKeyPartB64: derivedKeyMaterial.keyB64, - clientKeyPartB64: clientKeyHalfB64, - }, - ivB64: derivedKeyMaterial.ivB64, - }); - expect(biometrics.deriveKeyMaterial).toHaveBeenCalledWith(ivB64); - expect(service["_osKeyHalf"]).toEqual(derivedKeyMaterial.keyB64); - expect(service["_iv"]).toEqual(derivedKeyMaterial.ivB64); - }, - ); - - it("should throw an error when deriving key material and returned iv is null", async () => { - service["setIv"]("testIvB64"); - - const derivedKeyMaterial = { - keyB64: "derivedKeyB64", - ivB64: null as string | undefined | null, - }; - biometrics.deriveKeyMaterial = jest.fn().mockResolvedValue(derivedKeyMaterial); - - await expect( - service["getStorageDetails"]({ clientKeyHalfB64: "testClientKeyHalfB64" }), - ).rejects.toThrow("Initialization Vector is null"); - - expect(biometrics.deriveKeyMaterial).toHaveBeenCalledWith("testIvB64"); - }); - }); - - describe("setIv", () => { - it("should set the iv and reset the osKeyHalf", () => { - const iv = "testIv"; - service["_osKeyHalf"] = "testOsKeyHalf"; - - service["setIv"](iv); - - expect(service["_iv"]).toBe(iv); - expect(service["_osKeyHalf"]).toBeNull(); - }); - - it("should set the iv to null when iv is undefined and reset the osKeyHalf", () => { - service["_osKeyHalf"] = "testOsKeyHalf"; - - service["setIv"](undefined); - - expect(service["_iv"]).toBeNull(); - expect(service["_osKeyHalf"]).toBeNull(); - }); - }); -}); 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 deleted file mode 100644 index a32d4678427..00000000000 --- a/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -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 { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { UserId } from "@bitwarden/common/types/guid"; -import { biometrics, passwords } from "@bitwarden/desktop-napi"; -import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-management"; - -import { WindowMain } from "../../main/window.main"; - -import { OsBiometricService } from "./os-biometrics.service"; - -const SERVICE = "Bitwarden_biometric"; - -function getLookupKeyForUser(userId: UserId): string { - return `${userId}_user_biometric`; -} - -export default class OsBiometricsServiceWindows implements OsBiometricService { - // Use set helper method instead of direct access - private _iv: string | null = null; - // Use getKeyMaterial helper instead of direct access - private _osKeyHalf: string | null = null; - private clientKeyHalves = new Map(); - - constructor( - private i18nService: I18nService, - private windowMain: WindowMain, - private logService: LogService, - private biometricStateService: BiometricStateService, - private encryptService: EncryptService, - private cryptoFunctionService: CryptoFunctionService, - ) {} - - async enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise {} - - async hasPersistentKey(userId: UserId): Promise { - return false; - } - - async supportsBiometrics(): Promise { - return await biometrics.available(); - } - - async getBiometricKey(userId: UserId): Promise { - const success = await this.authenticateBiometric(); - if (!success) { - return null; - } - - const value = await passwords.getPassword(SERVICE, getLookupKeyForUser(userId)); - if (value == null || value == "") { - throw new Error("Biometric key not found for user"); - } - - let clientKeyHalfB64: string | null = null; - if (this.clientKeyHalves.has(userId)) { - clientKeyHalfB64 = Utils.fromBufferToB64(this.clientKeyHalves.get(userId)!); - } - - if (!EncString.isSerializedEncString(value)) { - // Update to format encrypted with client key half - const storageDetails = await this.getStorageDetails({ - clientKeyHalfB64: clientKeyHalfB64 ?? undefined, - }); - - await biometrics.setBiometricSecret( - SERVICE, - getLookupKeyForUser(userId), - value, - storageDetails.key_material, - storageDetails.ivB64, - ); - return SymmetricCryptoKey.fromString(value); - } else { - const encValue = new EncString(value); - this.setIv(encValue.iv); - const storageDetails = await this.getStorageDetails({ - clientKeyHalfB64: clientKeyHalfB64 ?? undefined, - }); - return SymmetricCryptoKey.fromString( - await biometrics.getBiometricSecret( - SERVICE, - getLookupKeyForUser(userId), - storageDetails.key_material, - ), - ); - } - } - - async setBiometricKey(userId: UserId, key: SymmetricCryptoKey): Promise { - const clientKeyHalf = await this.getOrCreateBiometricEncryptionClientKeyHalf(userId, key); - - const storageDetails = await this.getStorageDetails({ - clientKeyHalfB64: Utils.fromBufferToB64(clientKeyHalf), - }); - await biometrics.setBiometricSecret( - SERVICE, - getLookupKeyForUser(userId), - key.toBase64(), - storageDetails.key_material, - storageDetails.ivB64, - ); - } - - async deleteBiometricKey(userId: UserId): Promise { - try { - await passwords.deletePassword(SERVICE, getLookupKeyForUser(userId)); - } catch (e) { - if (e instanceof Error && e.message === passwords.PASSWORD_NOT_FOUND) { - this.logService.debug( - "[OsBiometricService] Biometric key %s not found for service %s.", - getLookupKeyForUser(userId), - SERVICE, - ); - } else { - throw e; - } - } - } - - /** - * Prompts Windows Hello - */ - async authenticateBiometric(): Promise { - const hwnd = this.windowMain.win.getNativeWindowHandle(); - return await biometrics.prompt(hwnd, this.i18nService.t("windowsHelloConsentMessage")); - } - - private async getStorageDetails({ - clientKeyHalfB64, - }: { - clientKeyHalfB64: string | undefined; - }): Promise<{ key_material: biometrics.KeyMaterial; ivB64: string }> { - if (this._osKeyHalf == null) { - const keyMaterial = await biometrics.deriveKeyMaterial(this._iv); - this._osKeyHalf = keyMaterial.keyB64; - this._iv = keyMaterial.ivB64; - } - - if (this._iv == null) { - throw new Error("Initialization Vector is null"); - } - - const result = { - key_material: { - osKeyPartB64: this._osKeyHalf, - clientKeyPartB64: clientKeyHalfB64, - }, - ivB64: this._iv, - }; - - // napi-rs fails to convert null values - if (result.key_material.clientKeyPartB64 == null) { - delete result.key_material.clientKeyPartB64; - } - return result; - } - - // Nulls out key material in order to force a re-derive. This should only be used in getBiometricKey - // when we want to force a re-derive of the key material. - private setIv(iv?: string) { - this._iv = iv ?? null; - this._osKeyHalf = null; - } - - async needsSetup() { - return false; - } - - async canAutoSetup(): Promise { - return false; - } - - async runSetup(): Promise {} - - async getOrCreateBiometricEncryptionClientKeyHalf( - userId: UserId, - key: SymmetricCryptoKey, - ): Promise { - if (this.clientKeyHalves.has(userId)) { - return this.clientKeyHalves.get(userId)!; - } - - // Retrieve existing key half if it exists - let clientKeyHalf: Uint8Array | null = null; - const encryptedClientKeyHalf = - await this.biometricStateService.getEncryptedClientKeyHalf(userId); - if (encryptedClientKeyHalf != null) { - clientKeyHalf = await this.encryptService.decryptBytes(encryptedClientKeyHalf, key); - } - if (clientKeyHalf == null) { - // Set a key half if it doesn't exist - clientKeyHalf = await this.cryptoFunctionService.randomBytes(32); - const encKey = await this.encryptService.encryptBytes(clientKeyHalf, key); - await this.biometricStateService.setEncryptedClientKeyHalf(encKey, userId); - } - - this.clientKeyHalves.set(userId, clientKeyHalf); - - return clientKeyHalf; - } - - async getBiometricsFirstUnlockStatusForUser(userId: UserId): Promise { - if (this.clientKeyHalves.has(userId)) { - return BiometricsStatus.Available; - } else { - return BiometricsStatus.UnlockNeeded; - } - } -} 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 63d2225b7c6..3a47086b1aa 100644 --- a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts @@ -1,5 +1,7 @@ import { Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; +import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; @@ -13,6 +15,10 @@ import { DesktopBiometricsService } from "./desktop.biometrics.service"; */ @Injectable() export class RendererBiometricsService extends DesktopBiometricsService { + constructor(private tokenService: TokenService) { + super(); + } + async authenticateWithBiometrics(): Promise { return await ipc.keyManagement.biometric.authenticateWithBiometrics(); } @@ -31,6 +37,10 @@ export class RendererBiometricsService extends DesktopBiometricsService { } async getBiometricsStatusForUser(id: UserId): Promise { + if ((await firstValueFrom(this.tokenService.hasAccessToken$(id))) === false) { + return BiometricsStatus.NotEnabledInConnectedDesktopApp; + } + return await ipc.keyManagement.biometric.getBiometricsStatusForUser(id); } @@ -77,14 +87,6 @@ export class RendererBiometricsService extends DesktopBiometricsService { return await ipc.keyManagement.biometric.hasPersistentKey(userId); } - async enableWindowsV2Biometrics(): Promise { - return await ipc.keyManagement.biometric.enableWindowsV2Biometrics(); - } - - async isWindowsV2BiometricsEnabled(): Promise { - return await ipc.keyManagement.biometric.isWindowsV2BiometricsEnabled(); - } - async enableLinuxV2Biometrics(): Promise { return await ipc.keyManagement.biometric.enableLinuxV2Biometrics(); } diff --git a/apps/desktop/src/key-management/key-connector/remove-password.component.html b/apps/desktop/src/key-management/key-connector/remove-password.component.html deleted file mode 100644 index 5276e00c531..00000000000 --- a/apps/desktop/src/key-management/key-connector/remove-password.component.html +++ /dev/null @@ -1,20 +0,0 @@ -
    -
    -

    {{ "removeMasterPassword" | i18n }}

    -

    {{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}

    -

    {{ "organizationName" | i18n }}:

    -

    {{ organization.name }}

    -

    {{ "keyConnectorDomain" | i18n }}:

    -

    {{ organization.keyConnectorUrl }}

    -
    - - -
    -
    -
    diff --git a/apps/desktop/src/key-management/key-connector/remove-password.component.ts b/apps/desktop/src/key-management/key-connector/remove-password.component.ts deleted file mode 100644 index d9fea9409f8..00000000000 --- a/apps/desktop/src/key-management/key-connector/remove-password.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from "@angular/core"; - -import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitwarden/key-management-ui"; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "app-remove-password", - templateUrl: "remove-password.component.html", - standalone: false, -}) -export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/desktop/src/key-management/preload.ts b/apps/desktop/src/key-management/preload.ts index d317b1f6ce0..844a81ff8e3 100644 --- a/apps/desktop/src/key-management/preload.ts +++ b/apps/desktop/src/key-management/preload.ts @@ -61,14 +61,6 @@ const biometric = { action: BiometricAction.HasPersistentKey, userId: userId, } satisfies BiometricMessage), - enableWindowsV2Biometrics: (): Promise => - ipcRenderer.invoke("biometric", { - action: BiometricAction.EnableWindowsV2, - } satisfies BiometricMessage), - isWindowsV2BiometricsEnabled: (): Promise => - ipcRenderer.invoke("biometric", { - action: BiometricAction.IsWindowsV2Enabled, - } satisfies BiometricMessage), enableLinuxV2Biometrics: (): Promise => ipcRenderer.invoke("biometric", { action: BiometricAction.EnableLinuxV2, diff --git a/apps/desktop/src/key-management/session-timeout/services/desktop-session-timeout-settings-component.service.ts b/apps/desktop/src/key-management/session-timeout/services/desktop-session-timeout-settings-component.service.ts deleted file mode 100644 index 91c8126cdd7..00000000000 --- a/apps/desktop/src/key-management/session-timeout/services/desktop-session-timeout-settings-component.service.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { defer, from, map, Observable } from "rxjs"; - -import { - VaultTimeout, - VaultTimeoutOption, - VaultTimeoutStringType, -} from "@bitwarden/common/key-management/vault-timeout"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { SessionTimeoutSettingsComponentService } from "@bitwarden/key-management-ui"; - -export class DesktopSessionTimeoutSettingsComponentService - implements SessionTimeoutSettingsComponentService -{ - availableTimeoutOptions$: Observable = defer(() => - from(ipc.platform.powermonitor.isLockMonitorAvailable()).pipe( - map((isLockMonitorAvailable) => { - const options: VaultTimeoutOption[] = [ - { name: this.i18nService.t("oneMinute"), value: 1 }, - { name: this.i18nService.t("fiveMinutes"), value: 5 }, - { name: this.i18nService.t("fifteenMinutes"), value: 15 }, - { name: this.i18nService.t("thirtyMinutes"), value: 30 }, - { name: this.i18nService.t("oneHour"), value: 60 }, - { name: this.i18nService.t("fourHours"), value: 240 }, - { name: this.i18nService.t("onIdle"), value: VaultTimeoutStringType.OnIdle }, - { name: this.i18nService.t("onSleep"), value: VaultTimeoutStringType.OnSleep }, - ]; - - if (isLockMonitorAvailable) { - options.push({ - name: this.i18nService.t("onLocked"), - value: VaultTimeoutStringType.OnLocked, - }); - } - - options.push( - { name: this.i18nService.t("onRestart"), value: VaultTimeoutStringType.OnRestart }, - { name: this.i18nService.t("never"), value: VaultTimeoutStringType.Never }, - ); - - return options; - }), - ), - ); - - constructor(private readonly i18nService: I18nService) {} - - onTimeoutSave(_: VaultTimeout): void {} -} diff --git a/apps/desktop/src/key-management/session-timeout/services/desktop-session-timeout-type.service.spec.ts b/apps/desktop/src/key-management/session-timeout/services/desktop-session-timeout-type.service.spec.ts new file mode 100644 index 00000000000..d3ece8842b2 --- /dev/null +++ b/apps/desktop/src/key-management/session-timeout/services/desktop-session-timeout-type.service.spec.ts @@ -0,0 +1,125 @@ +import { + VaultTimeoutNumberType, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; + +import { DesktopSessionTimeoutTypeService } from "./desktop-session-timeout-type.service"; + +describe("DesktopSessionTimeoutTypeService", () => { + let service: DesktopSessionTimeoutTypeService; + let mockIsLockMonitorAvailable: jest.Mock; + + beforeEach(() => { + mockIsLockMonitorAvailable = jest.fn(); + + (global as any).ipc = { + platform: { + powermonitor: { + isLockMonitorAvailable: mockIsLockMonitorAvailable, + }, + }, + }; + + service = new DesktopSessionTimeoutTypeService(); + }); + + describe("isAvailable", () => { + it("should return false for Immediately", async () => { + const result = await service.isAvailable(VaultTimeoutNumberType.Immediately); + + expect(result).toBe(false); + }); + + it.each([ + VaultTimeoutStringType.OnIdle, + VaultTimeoutStringType.OnSleep, + VaultTimeoutStringType.OnRestart, + VaultTimeoutStringType.Never, + VaultTimeoutStringType.Custom, + ])("should return true for always available type: %s", async (timeoutType) => { + const result = await service.isAvailable(timeoutType); + + expect(result).toBe(true); + }); + + it.each([VaultTimeoutNumberType.OnMinute, VaultTimeoutNumberType.EightHours])( + "should return true for numeric timeout type: %s", + async (timeoutType) => { + const result = await service.isAvailable(timeoutType); + + expect(result).toBe(true); + }, + ); + + describe("OnLocked availability", () => { + it("should return true when lock monitor is available", async () => { + mockIsLockMonitorAvailable.mockResolvedValue(true); + + const result = await service.isAvailable(VaultTimeoutStringType.OnLocked); + + expect(result).toBe(true); + expect(mockIsLockMonitorAvailable).toHaveBeenCalled(); + }); + + it("should return false when lock monitor is not available", async () => { + mockIsLockMonitorAvailable.mockResolvedValue(false); + + const result = await service.isAvailable(VaultTimeoutStringType.OnLocked); + + expect(result).toBe(false); + expect(mockIsLockMonitorAvailable).toHaveBeenCalled(); + }); + }); + }); + + describe("getOrPromoteToAvailable", () => { + it.each([ + VaultTimeoutNumberType.OnMinute, + VaultTimeoutStringType.OnIdle, + VaultTimeoutStringType.OnSleep, + VaultTimeoutStringType.OnRestart, + VaultTimeoutStringType.OnLocked, + VaultTimeoutStringType.Never, + VaultTimeoutStringType.Custom, + ])("should return the original type when it is available: %s", async (timeoutType) => { + jest.spyOn(service, "isAvailable").mockResolvedValue(true); + + const result = await service.getOrPromoteToAvailable(timeoutType); + + expect(result).toBe(timeoutType); + expect(service.isAvailable).toHaveBeenCalledWith(timeoutType); + }); + + it("should return OnMinute when Immediately is not available", async () => { + jest.spyOn(service, "isAvailable").mockResolvedValue(false); + + const result = await service.getOrPromoteToAvailable(VaultTimeoutNumberType.Immediately); + + expect(result).toBe(VaultTimeoutNumberType.OnMinute); + expect(service.isAvailable).toHaveBeenCalledWith(VaultTimeoutNumberType.Immediately); + }); + + it("should return OnSleep when OnLocked is not available", async () => { + jest.spyOn(service, "isAvailable").mockResolvedValue(false); + + const result = await service.getOrPromoteToAvailable(VaultTimeoutStringType.OnLocked); + + expect(result).toBe(VaultTimeoutStringType.OnSleep); + expect(service.isAvailable).toHaveBeenCalledWith(VaultTimeoutStringType.OnLocked); + }); + + it.each([ + VaultTimeoutStringType.OnIdle, + VaultTimeoutStringType.OnSleep, + VaultTimeoutNumberType.OnMinute, + 5, + ])("should return OnRestart when type is not available: %s", async (timeoutType) => { + jest.spyOn(service, "isAvailable").mockResolvedValue(false); + + const result = await service.getOrPromoteToAvailable(timeoutType); + + expect(result).toBe(VaultTimeoutStringType.OnRestart); + expect(service.isAvailable).toHaveBeenCalledWith(timeoutType); + }); + }); +}); diff --git a/apps/desktop/src/key-management/session-timeout/services/desktop-session-timeout-type.service.ts b/apps/desktop/src/key-management/session-timeout/services/desktop-session-timeout-type.service.ts new file mode 100644 index 00000000000..1f09e83b0f1 --- /dev/null +++ b/apps/desktop/src/key-management/session-timeout/services/desktop-session-timeout-type.service.ts @@ -0,0 +1,46 @@ +import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout"; +import { + isVaultTimeoutTypeNumeric, + VaultTimeout, + VaultTimeoutNumberType, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; + +export class DesktopSessionTimeoutTypeService implements SessionTimeoutTypeService { + async isAvailable(type: VaultTimeout): Promise { + switch (type) { + case VaultTimeoutNumberType.Immediately: + return false; + case VaultTimeoutStringType.OnIdle: + case VaultTimeoutStringType.OnSleep: + case VaultTimeoutStringType.OnRestart: + case VaultTimeoutStringType.Never: + case VaultTimeoutStringType.Custom: + return true; + case VaultTimeoutStringType.OnLocked: + return await ipc.platform.powermonitor.isLockMonitorAvailable(); + default: + if (isVaultTimeoutTypeNumeric(type)) { + return true; + } + break; + } + + return false; + } + + async getOrPromoteToAvailable(type: VaultTimeout): Promise { + const available = await this.isAvailable(type); + if (!available) { + switch (type) { + case VaultTimeoutNumberType.Immediately: + return VaultTimeoutNumberType.OnMinute; + case VaultTimeoutStringType.OnLocked: + return VaultTimeoutStringType.OnSleep; + default: + return VaultTimeoutStringType.OnRestart; + } + } + return type; + } +} diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 1c6a2bc49c9..a4230a128c5 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Leer meer" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funksie Onbeskikbaar" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GG geënkripteerde berging vir lêeraanhegsels." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Alle Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Is u seker u wil die “Nooit”-opsie gebruik? Deur u vergrendelopsies op “Nooit” te stel word u kluis se enkripsie op u toestel bewaar. Indien u hierdie opsie gebruik moet u verseker dat u toestel behoorlik beskerm is." }, "vault": { - "message": "Kluis" + "message": "Kluis", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Meld aan met meesterwagwoord" diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index ca404f4e179..54a9425c901 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "اعرف المزيد" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "الميزة غير متوفرة" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 جيغابايت وحدة تخزين مشفرة لمرفقات الملفات." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "خيارات تسجيل الدخول بخطوتين المملوكة مثل YubiKey و Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "كل الإرسالات", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "هل أنت متأكد من أنك تريد استخدام خيار \"مطلقا\"؟ إعداد خيارات القفل إلى \"مطلقا\" يخزن مفتاح تشفير المستودع الخاص بك على جهازك. إذا كنت تستخدم هذا الخيار، يجب أن تتأكد من الحفاظ على حماية جهازك بشكل صحيح." }, "vault": { - "message": "الخزانة" + "message": "الخزانة", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "تسجيل الدخول باستخدام كلمة المرور الرئيسية" diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 55c2bdcd677..9e8c77aa76a 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Daha ətraflı" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Özəllik əlçatmazdır" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş saxlama sahəsi." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "YubiKey və Duo kimi mülkiyyətçi iki addımlı giriş seçimləri." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Əlaqə məlumatları" }, + "send": { + "message": "Send", + "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", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "\"Heç vaxt\"i seçmək istədiyinizə əminsiniz? Kilid seçimini \"Heç vaxt\" olaraq ayarlasanız, seyfinizin şifrələmə açarı cihazınızda saxlanılacaq. Bu seçimi istifadə etsəniz, cihazınızı daha yaxşı mühafizə etdiyinizə əmin olmalısınız." }, "vault": { - "message": "Seyf" + "message": "Seyf", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Ana parolla giriş et" diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index b2e4db47b32..e9b3834a9f7 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Даведацца больш" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Функцыя недаступна" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 ГБ зашыфраванага сховішча для далучаных файлаў." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Прапрыетарныя варыянты двухэтапнага ўваходу, такія як YubiKey, FIDO U2F і Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Усе Send'ы", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Вы сапраўды хочаце адключыць блакіроўку сховішча? Прызначыўшы параметр блакіравання \"Ніколі\", ключ шыфравання будзе захоўвацца на вашай прыладзе. Калі вы выкарыстоўваеце гэты параметр, вы павінны быць упэўнены ў тым, што ваша прылада надзейна абаронена." }, "vault": { - "message": "Сховішча" + "message": "Сховішча", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Увайсці з асноўным паролем" diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index ad03c2cc023..f27fb467c0c 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Научете повече" }, + "migrationsFailed": { + "message": "Възникна грешка при обновяването на настройките за шифроване." + }, + "updateEncryptionSettingsTitle": { + "message": "Обновете настройките си за шифроване" + }, + "updateEncryptionSettingsDesc": { + "message": "Новите препоръчани настройки за шифроване ще подобрят сигурността на акаунта Ви. Въведете главната си парола, за да ги обновите сега." + }, + "confirmIdentityToContinue": { + "message": "Потвърдете самоличността си, за да продължите" + }, + "enterYourMasterPassword": { + "message": "Въведете главната си парола" + }, + "updateSettings": { + "message": "Обновяване на настройките" + }, "featureUnavailable": { "message": "Функцията е недостъпна" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 ГБ пространство за файлове, които се шифроват." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ пространство за файлове, които се шифрират.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Частно двустепенно удостоверяване чрез YubiKey и Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Информация за контакт" }, + "send": { + "message": "Изпращане", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Всички изпращания", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Уверени ли сте, че искате да зададете стойност „Никога“? Това води до съхранение на шифриращия ключ за трезора във устройството ви. Ако използвате тази възможност, е много важно да имате надлежна защита на устройството си." }, "vault": { - "message": "Трезор" + "message": "Трезор", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Вписване с главната парола" diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index d6c61c1ab51..f9f072f290e 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "আরও জানুন" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "বৈশিষ্ট্য অনুপলব্ধ" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "ফাইল সংযুক্তির জন্য ১ জিবি এনক্রিপ্টেড স্থান।" }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 569f1072c4b..b8798320a98 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Saznajte više" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funkcija nije dostupna" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB šifriranog prostora za pohranu podataka." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Svi Send-ovi", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index de468f1e8b3..a7674cbc753 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Més informació" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Característica no disponible" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB d'emmagatzematge xifrat per als fitxers adjunts." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Opcions propietàries de doble factor com ara YubiKey i Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Informació de contacte" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Tots els Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Esteu segur que voleu utilitzar l'opció \"Mai\"? En configurar les opcions de bloqueig a \"Mai\" s'emmagatzema la clau de xifratge de la vostra caixa forta al vostre dispositiu. Si utilitzeu aquesta opció, heu d'assegurar-vos que conserveu el dispositiu degudament protegit." }, "vault": { - "message": "Caixa forta" + "message": "Caixa forta", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Inici de sessió amb contrasenya mestra" diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index c02dbabbc93..b93e5d0f513 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Dozvědět se více" }, + "migrationsFailed": { + "message": "Došlo k chybě při aktualizaci nastavení šifrování." + }, + "updateEncryptionSettingsTitle": { + "message": "Aktualizovat nastavení šifrování" + }, + "updateEncryptionSettingsDesc": { + "message": "Nové doporučené nastavení šifrování zlepší bezpečnost Vašeho účtu. Pokud chcete aktualizovat nyní, zadejte hlavní heslo." + }, + "confirmIdentityToContinue": { + "message": "Pro pokračování potvrďte svou identitu" + }, + "enterYourMasterPassword": { + "message": "Zadejte své hlavní heslo" + }, + "updateSettings": { + "message": "Aktualizovat nastavení" + }, "featureUnavailable": { "message": "Funkce není dostupná" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB šifrovaného uložiště pro přílohy." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ šifrovaného úložiště pro přílohy.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Volby proprietálních dvoufázových přihlášení jako je YubiKey a Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Všechny Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Opravdu chcete použít volbu \"Nikdy\"? Nastavením volby uzamčení na \"Nikdy\" bude šifrovací klíč k trezoru uložen přímo ve Vašem zařízení. Pokud tuto možnost použijete, měli byste Vaše zařízení řádně zabezpečit a chránit." }, "vault": { - "message": "Trezor" + "message": "Trezor", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Přihlásit se pomocí hlavního hesla" diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 25b52fcc101..5a00ad90bbd 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index 1d135a533f2..622c9e9187d 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Læs mere" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funktion utilgængelig" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB krypteret lagerplads til filvedhæftninger." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietære totrins-login muligheder, såsom YubiKey og Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Alle Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Sikker på, at indstillingen \"Aldrig\" skal bruges? Sættes låseindstillinger til \"Aldrig\", gemmes din bokskrypteringsnøgle på enheden. Bruges denne indstilling, så sørg for at holde din enhed ordentligt beskyttet." }, "vault": { - "message": "Boks" + "message": "Boks", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log ind med hovedadgangskoden" diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 2f8daec5b68..2743ec21c8a 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" + "message": "Keine Berechtigung zum Bearbeiten dieses Eintrags" }, "welcomeBack": { "message": "Willkommen zurück" @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Mehr erfahren" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funktion nicht verfügbar" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB verschlüsselter Speicherplatz für Dateianhänge." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ verschlüsselter Speicher für Dateianhänge.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietäre Optionen für die Zwei-Faktor Authentifizierung wie YubiKey und Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Kontaktinformationen" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Alle Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2562,7 +2593,7 @@ } }, "vaultCustomTimeoutMinimum": { - "message": "Das minimal benutzerdefinierte Timeout beträgt 1 Minute." + "message": "Minimale benutzerdefinierte Timeout-Zeit beträgt 1 Minute." }, "inviteAccepted": { "message": "Einladung angenommen" @@ -2991,7 +3022,8 @@ "message": "Bist du sicher, dass du die Option \"Nie\" verwenden möchtest? Durch das Setzen der Sperroptionen zu \"Nie\" wird der Verschlüsselungscode deines Tresors auf deinem Gerät gespeichert. Wenn du diese Option verwendest, solltest du sicherstellen, dass dein Gerät ausreichend geschützt ist." }, "vault": { - "message": "Tresor" + "message": "Tresor", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Mit Master-Passwort anmelden" @@ -4165,7 +4197,7 @@ "description": "Verb" }, "unArchive": { - "message": "Nicht mehr archivieren" + "message": "Wiederherstellen" }, "itemsInArchive": { "message": "Einträge im Archiv" @@ -4177,10 +4209,10 @@ "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Auto-Ausfüllen-Vorschlägen ausgeschlossen." }, "itemWasSentToArchive": { - "message": "Eintrag wurde ins Archiv verschoben" + "message": "Eintrag wurde archiviert" }, "itemWasUnarchived": { - "message": "Eintrag wird nicht mehr archiviert" + "message": "Eintrag wurde wiederhergestellt" }, "archiveItem": { "message": "Eintrag archivieren" @@ -4201,22 +4233,22 @@ "message": "Integrierter Authenticator" }, "secureFileStorage": { - "message": "Sicherer Dateispeicher" + "message": "Sichere Dateispeicherung" }, "emergencyAccess": { "message": "Notfallzugriff" }, "breachMonitoring": { - "message": "Datendiebstahl-Überwachung" + "message": "Datenleck-Überwachung" }, "andMoreFeatures": { - "message": "Und mehr!" + "message": "Und vieles mehr!" }, "planDescPremium": { - "message": "Umfassende Online-Sicherheit" + "message": "Kompletter Online-Sicherheitsplan" }, "upgradeToPremium": { - "message": "Upgrade auf Premium" + "message": "Auf Premium upgraden" }, "sessionTimeoutSettingsAction": { "message": "Timeout-Aktion" diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 0b869c1e02f..bd8269db4ea 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Μάθετε περισσότερα" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Μη διαθέσιμη λειτουργία" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB κρυπτογραφημένο αποθηκευτικό χώρο για συνημμένα αρχεία." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Ιδιόκτητες επιλογές σύνδεσης δύο βημάτων, όπως το YubiKey και το Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Στοιχεία επικοινωνίας" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Όλα τα Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε την επιλογή \"Ποτέ\"; Ο ορισμός των επιλογών κλειδώματος σε \"Ποτέ\" αποθηκεύει το κλειδί κρυπτογράφησης του θησαυ/κίου σας στη συσκευή σας. Εάν χρησιμοποιήσετε αυτήν την επιλογή, θα πρέπει να διασφαλίσετε ότι θα διατηρείτε τη συσκευή σας κατάλληλα προστατευμένη." }, "vault": { - "message": "Κρύπτη" + "message": "Κρύπτη", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Συνδεθείτε με τον κύριο κωδικό πρόσβασης" diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 5c7f3034597..48e346d9c68 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -708,6 +708,18 @@ "addAttachment": { "message": "Add attachment" }, + "itemsTransferred": { + "message": "Items transferred" + }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" }, @@ -908,6 +920,12 @@ "unexpectedError": { "message": "An unexpected error has occurred." }, + "unexpectedErrorShort": { + "message": "Unexpected error" + }, + "closeThisBitwardenWindow": { + "message": "Close this Bitwarden window and try again." + }, "itemInformation": { "message": "Item information" }, @@ -1508,6 +1526,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2246,6 +2273,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2606,9 +2637,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -3009,7 +3037,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" @@ -3342,7 +3371,7 @@ "orgTrustWarning1": { "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." }, - "trustUser":{ + "trustUser": { "message": "Trust user" }, "inputRequired": { @@ -3872,6 +3901,75 @@ "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, + "importantNotice": { + "message": "Important notice" + }, + "setupTwoStepLogin": { + "message": "Set up two-step login" + }, + "newDeviceVerificationNoticeContentPage1": { + "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + }, + "newDeviceVerificationNoticeContentPage2": { + "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + }, + "remindMeLater": { + "message": "Remind me later" + }, + "newDeviceVerificationNoticePageOneFormContent": { + "message": "Do you have reliable access to your email, $EMAIL$?", + "placeholders": { + "email": { + "content": "$1", + "example": "your_name@email.com" + } + } + }, + "newDeviceVerificationNoticePageOneEmailAccessNo": { + "message": "No, I do not" + }, + "newDeviceVerificationNoticePageOneEmailAccessYes": { + "message": "Yes, I can reliably access my email" + }, + "turnOnTwoStepLogin": { + "message": "Turn on two-step login" + }, + "changeAcctEmail": { + "message": "Change account email" + }, + "passkeyLogin": { + "message": "Log in with passkey?" + }, + "savePasskeyQuestion": { + "message": "Save passkey?" + }, + "saveNewPasskey": { + "message": "Save as new login" + }, + "savePasskeyNewLogin": { + "message": "Save passkey as new login" + }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, + "overwritePasskey": { + "message": "Overwrite passkey?" + }, + "unableToSavePasskey": { + "message": "Unable to save passkey" + }, + "alreadyContainsPasskey": { + "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + }, + "passkeyAlreadyExists": { + "message": "A passkey already exists for this application." + }, + "applicationDoesNotSupportDuplicates": { + "message": "This application does not support duplicates." + }, + "closeThisWindow": { + "message": "Close this window" + }, "allowScreenshots": { "message": "Allow screen capture" }, @@ -4230,16 +4328,147 @@ "andMoreFeatures": { "message": "And more!" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector":{ + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified":{ + "message": "Organization verified" + }, + "domainVerified":{ + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, "sessionTimeoutHeader": { "message": "Session timeout" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": { + "message": "Your organization has set the default session timeout to On system lock." + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On restart." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On restart" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "upgrade": { + "message": "Upgrade" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 16af69361c6..4e1f8569caf 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index c6f1253bb59..1413debe49c 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 28a9f3b8bce..5cb6995eeb6 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Lerni pli" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "La funkcio nedisponeblas" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Ĉiuj Send'oj", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Trezorejo" + "message": "Trezorejo", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Saluti per la ĉefa pasvorto" diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 9966fa1064c..af90b40af65 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Más información" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Característica no disponible" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1GB de espacio en disco cifrado." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Opciones de inicio de sesión con autenticación de dos pasos propietarios como YubiKey y Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Información de contacto" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Todos los Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "¿Está seguro de que quieres usar la opción \"Nunca\"? Al ajustar las opciones de bloqueo a \"Nunca\", la clave de cifrado de su caja fuerte se guardará en tu dispositivo. Si usas esta opción, asegúrate de mantener tu dispositivo debidamente protegido." }, "vault": { - "message": "Caja fuerte" + "message": "Caja fuerte", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Iniciar sesión con contraseña maestra" diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index d85c52bb763..8ab2818f7ea 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Loe edasi" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funktsioon pole saadaval" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB ulatuses krüpteeritud salvestusruum." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Eraomanduses kaheastmelise logimise valikud, nagu näiteks YubiKey ja Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Kõik Sendid", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Oled kindel, et soovid kasutada valikut \"Mitte kunagi\"? Sellega talletatakse sinu hoidla krüpteerimise võtit seadme mälus. Peaksid olema väga hoolas ja kindel, et seade on ohutu ja selles ei ole pahavara." }, "vault": { - "message": "Hoidla" + "message": "Hoidla", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Logi sisse ülemparooliga" diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 36401df0078..451e77c4ec0 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Gehiago ikasi" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Ezaugarria ez dago erabilgarri" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "Eranskinentzako 1GB-eko zifratutako biltegia." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Send guztiak", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Ziur zaude \"Inoiz ez\" aukera erabili nahi duzula? Zure blokeo aukerak \"Inoiz ez\" bezala konfiguratzeak kutxa gotorraren zifratze-gakoa gailuan gordetzen du. Aukera hau erabiltzen baduzu, gailua behar bezala babestuta duzula ziurtatu behar duzu." }, "vault": { - "message": "Kutxa gotorra" + "message": "Kutxa gotorra", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Hasi saioa pasahitz nagusiarekin" diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index caa241eb036..f7dedc42542 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -42,7 +42,7 @@ "message": "جستجوی گاوصندوق" }, "resetSearch": { - "message": "Reset search" + "message": "پاک کردن جستجو" }, "addItem": { "message": "افزودن مورد" @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "شما اجازه ویرایش این آیتم را ندارید" }, "welcomeBack": { "message": "خوش آمدید" @@ -579,7 +579,7 @@ "message": "کپی کد تأیید (TOTP)" }, "copyFieldCipherName": { - "message": "Copy $FIELD$, $CIPHERNAME$", + "message": "کپی$FIELD$،$CIPHERNAME$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -706,10 +706,10 @@ "message": "پیوست ذخیره شد" }, "addAttachment": { - "message": "Add attachment" + "message": "افزودن پیوست" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "حداکثر حجم فایل ۵۰۰ مگابایت است" }, "file": { "message": "پرونده" @@ -775,7 +775,7 @@ "message": "استفاده از ورود تک مرحله‌ای" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "سازمان شما ورود یکپارچه را الزامی کرده است." }, "submit": { "message": "ثبت" @@ -1039,7 +1039,7 @@ "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "آدرس‌های وب باید از HTTPS استفاده کنند." }, "customEnvironment": { "message": "محیط سفارشی" @@ -1093,6 +1093,24 @@ "learnMore": { "message": "بیشتر بدانید" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "ویژگی موجود نیست" }, @@ -1232,7 +1250,7 @@ "message": "کلمه عبور اصلی نامعتبر است" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "رمز عبور اصلی نامعتبر است. تأیید کنید که ایمیل شما صحیح است و حساب کاربری شما در $HOST$ ایجاد شده است.", "placeholders": { "host": { "content": "$1", @@ -1247,7 +1265,7 @@ "message": "ورود دو مرحله‌ای" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "وقفه زمانی گاوصندوق" }, "vaultTimeout": { "message": "متوقف شدن گاو‌صندوق" @@ -1256,7 +1274,7 @@ "message": "پایان زمان" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "اقدام وقفه زمانی" }, "vaultTimeoutDesc": { "message": "انتخاب کنید که گاو‌صندوق شما چه زمانی عمل توقف زمانی گاوصندوق را انجام دهد." @@ -1321,7 +1339,7 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "showIconsChangePasswordUrls": { - "message": "Show website icons and retrieve change password URLs" + "message": "نمایش آیکون‌های وب‌سایت و دریافت آدرس‌های تغییر رمز عبور" }, "enableMinToTray": { "message": "کوچک کردن به نماد سینی" @@ -1467,7 +1485,7 @@ "description": "Copy credit card security code (CVV)" }, "cardNumber": { - "message": "card number" + "message": "شماره کارت" }, "premiumMembership": { "message": "عضویت پرمیوم" @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "۱ گیگابایت فضای ذخیره‌سازی رمزنگاری شده برای پرونده‌های پیوست." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "گزینه‌های ورود اضافی دو مرحله‌ای مانند YubiKey و Duo." }, @@ -1862,10 +1889,10 @@ "message": "در زمان شروع مجدد، با کلمه عبور اصلی قفل کن" }, "requireMasterPasswordOrPinOnAppRestart": { - "message": "Require master password or PIN on app restart" + "message": "درخواست رمز عبور اصلی یا پین هنگام راه‌اندازی مجدد برنامه" }, "requireMasterPasswordOnAppRestart": { - "message": "Require master password on app restart" + "message": "درخواست رمز عبور اصلی هنگام راه‌اندازی مجدد برنامه" }, "deleteAccount": { "message": "حذف حساب کاربری" @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "اطلاعات تماس" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "همه ارسال‌ها", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2437,13 +2468,13 @@ "message": "کلمه عبور اصلی شما با یک یا چند سیاست سازمان‌تان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روزرسانی کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه‌های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, "changePasswordWarning": { - "message": "After changing your password, you will need to log in with your new password. Active sessions on other devices will be logged out within one hour." + "message": "پس از تغییر رمز عبور، باید با رمز عبور جدید خود وارد شوید. نشست‌های فعال در سایر دستگاه‌ها ظرف یک ساعت خارج خواهند شد." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Change your master password to complete account recovery." + "message": "برای تکمیل بازیابی حساب، رمز عبور اصلی خود را تغییر دهید." }, "updateMasterPasswordSubtitle": { - "message": "Your master password does not meet this organization’s requirements. Change your master password to continue." + "message": "رمز عبور اصلی شما با الزامات این سازمان مطابقت ندارد. برای ادامه، رمز عبور اصلی خود را تغییر دهید." }, "tdeDisabledMasterPasswordRequired": { "message": "سازمان شما رمزگذاری دستگاه‌های مورد اعتماد را غیرفعال کرده است. لطفاً برای دسترسی به گاوصندوق خود یک کلمه عبور اصلی تنظیم کنید." @@ -2533,10 +2564,10 @@ "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", @@ -2549,7 +2580,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", @@ -2562,7 +2593,7 @@ } }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "حداقل زمان وقفه سفارشی ۱ دقیقه است." }, "inviteAccepted": { "message": "دعوتنامه پذیرفته شد" @@ -2679,7 +2710,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "فقط از گاوصندوق سازمانی مرتبط با $ORGANIZATION$ خروجی گرفته خواهد شد.", "placeholders": { "organization": { "content": "$1", @@ -2688,7 +2719,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "فقط از گاوصندوق سازمانی مرتبط با $ORGANIZATION$ خروجی گرفته خواهد شد. مجموعه‌های «آیتم‌های من» شامل نخواهند شد.", "placeholders": { "organization": { "content": "$1", @@ -2991,7 +3022,8 @@ "message": "آیا جداً می‌خواهید از گزینه \"هرگز\" استفاده کنید؟ تنظیم کردن گزینه قفل به \"هرگز\" کلیدهای رمزنگاری گاوصندوقتان را بر روی دستگاه شما ذخیره خواهد کرد. اگر از این گزینه استفاده می‌کنید باید اطمینان داشته باشید که دستگاه شما کاملا محافظت شده است." }, "vault": { - "message": "گاوصندوق" + "message": "گاوصندوق", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "با کلمه عبور اصلی وارد شوید" @@ -3073,7 +3105,7 @@ } }, "loginRequestApprovedForEmailOnDevice": { - "message": "Login request approved for $EMAIL$ on $DEVICE$", + "message": "درخواست ورود برای $EMAIL$ در $DEVICE$ تأیید شد", "placeholders": { "email": { "content": "$1", @@ -3086,21 +3118,21 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this was you, try to log in with the device again." + "message": "شما یک تلاش ورود از دستگاهی دیگر را رد کردید. اگر این شما بودید، دوباره با آن دستگاه برای ورود تلاش کنید." }, "webApp": { - "message": "Web app" + "message": "نسخه وب" }, "mobile": { - "message": "Mobile", + "message": "موبایل", "description": "Mobile app" }, "extension": { - "message": "Extension", + "message": "افزونه", "description": "Browser extension/addon" }, "desktop": { - "message": "Desktop", + "message": "دسکتاپ", "description": "Desktop app" }, "cli": { @@ -3111,10 +3143,10 @@ "description": "Software Development Kit" }, "server": { - "message": "Server" + "message": "سرور" }, "loginRequest": { - "message": "Login request" + "message": "درخواست ورود" }, "deviceType": { "message": "نوع دستگاه" @@ -3258,10 +3290,10 @@ "message": "درخواست تأیید مدیر" }, "unableToCompleteLogin": { - "message": "Unable to complete login" + "message": "امکان تکمیل ورود وجود ندارد" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { - "message": "You need to log in on a trusted device or ask your administrator to assign you a password." + "message": "شما باید در یک دستگاه قابل اعتماد وارد شوید یا از مدیر خود بخواهید رمز عبوری به شما اختصاص دهد." }, "region": { "message": "منطقه" @@ -3528,10 +3560,10 @@ "message": "یک مجموعه انتخاب کنید" }, "importTargetHintCollection": { - "message": "Select this option if you want the imported file contents moved to a collection" + "message": "اگر می‌خواهید محتویات فایل وارد شده به یک مجموعه منتقل شود، این گزینه را انتخاب کنید" }, "importTargetHintFolder": { - "message": "Select this option if you want the imported file contents moved to a folder" + "message": "اگر می‌خواهید محتویات فایل وارد شده به یک پوشه منتقل شود، این گزینه را انتخاب کنید" }, "importUnassignedItemsError": { "message": "پرونده حاوی موارد اختصاص نیافته است." @@ -3628,10 +3660,10 @@ "message": "لطفاً برای ورود، از اطلاعات کاربری شرکت خود استفاده کنید." }, "importDirectlyFromBrowser": { - "message": "Import directly from browser" + "message": "وارد کردن مستقیم از مرورگر" }, "browserProfile": { - "message": "Browser Profile" + "message": "پروفایل مرورگر" }, "seeDetailedInstructions": { "message": "دستورالعمل‌های کامل را در سایت راهنمای ما مشاهده کنید در", @@ -3673,11 +3705,11 @@ "description": "Link to match detection docs on warning dialog for advance match strategy" }, "uriAdvancedOption": { - "message": "Advanced options", + "message": "گزینه‌های پیشرفته", "description": "Advanced option placeholder for uri option component" }, "warningCapitalized": { - "message": "Warning", + "message": "هشدار", "description": "Warning (should maintain locale-relevant capitalization)" }, "success": { @@ -3876,10 +3908,10 @@ "message": "تغییر کلمه عبور در معرض خطر" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "This login is at-risk and missing a website. Add a website and change the password for stronger security." + "message": "این ورود در معرض خطر است و فاقد وب‌سایت می‌باشد. برای امنیت بیشتر، یک وب‌سایت اضافه کنید و رمز عبور را تغییر دهید." }, "missingWebsite": { - "message": "Missing website" + "message": "وب‌سایت وجود ندارد" }, "cannotRemoveViewOnlyCollections": { "message": "نمی‌توانید مجموعه‌هایی را که فقط دسترسی مشاهده دارند حذف کنید: $COLLECTIONS$", @@ -3977,10 +4009,10 @@ "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "aboutThisSetting": { - "message": "About this setting" + "message": "درباره این تنظیم" }, "permitCipherDetailsDescription": { - "message": "Bitwarden will use saved login URIs to identify which icon or change password URL should be used to improve your experience. No information is collected or saved when you use this service." + "message": "بیت‌واردن از URI های ورود ذخیره شده استفاده می‌کند تا تشخیص دهد کدام آیکون یا آدرس تغییر رمز عبور باید برای بهبود تجربه شما استفاده شود. هنگام استفاده از این سرویس، هیچ اطلاعاتی جمع‌آوری یا ذخیره نمی‌شود." }, "assignToCollections": { "message": "اختصاص به مجموعه‌ها" @@ -4117,111 +4149,111 @@ } }, "showMore": { - "message": "Show more" + "message": "نمایش بیشتر" }, "showLess": { - "message": "Show less" + "message": "نمایش کمتر" }, "enableAutotypeDescription": { - "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." + "message": "بیت‌واردن مکان‌های ورودی را اعتبارسنجی نمی‌کند؛ قبل از استفاده از میانبر، مطمئن شوید که در پنجره و فیلد صحیح قرار دارید." }, "typeShortcut": { - "message": "Type shortcut" + "message": "تایپ میانبر" }, "editAutotypeShortcutDescription": { - "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + "message": "شامل یک یا دو مورد از کلیدهای تغییردهنده زیر: Ctrl، Alt، Win یا Shift و یک حرف." }, "invalidShortcut": { - "message": "Invalid shortcut" + "message": "میانبر نامعتبر" }, "moreBreadcrumbs": { - "message": "More breadcrumbs", + "message": "مسیرهای بیشتر", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." }, "next": { - "message": "Next" + "message": "بعدی" }, "confirmKeyConnectorDomain": { - "message": "Confirm Key Connector domain" + "message": "تأیید دامنه Key Connector" }, "confirm": { - "message": "Confirm" + "message": "تأیید" }, "enableAutotypeShortcutPreview": { - "message": "Enable autotype shortcut (Feature Preview)" + "message": "فعال‌سازی میانبر تایپ خودکار (پیش‌نمایش ویژگی)" }, "enableAutotypeShortcutDescription": { - "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." + "message": "برای جلوگیری از پر کردن داده‌ها در مکان اشتباه، قبل از استفاده از میانبر مطمئن شوید که در فیلد صحیح قرار دارید." }, "editShortcut": { - "message": "Edit shortcut" + "message": "ویرایش میانبر" }, "archiveNoun": { - "message": "Archive", + "message": "بایگانی", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "بایگانی کردن", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "خارج کردن از بایگانی" }, "itemsInArchive": { - "message": "Items in archive" + "message": "آیتم‌های موجود در بایگانی" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "آیتمی در بایگانی وجود ندارد" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "آیتم‌های بایگانی‌شده در اینجا نمایش داده می‌شوند و از نتایج جستجوی عمومی و پیشنهاد ها پر کردن خودکار حذف خواهند شد." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "آیتم به بایگانی فرستاده شد" }, "itemWasUnarchived": { - "message": "Item was unarchived" + "message": "آیتم از بایگانی خارج شد" }, "archiveItem": { - "message": "Archive item" + "message": "بایگانی آیتم" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "آیتم‌های بایگانی‌شده از نتایج جستجوی عمومی و پیشنهاد ها پر کردن خودکار حذف می‌شوند. آیا مطمئن هستید که می‌خواهید این آیتم را بایگانی کنید؟" }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "کد پستی" }, "cardNumberLabel": { - "message": "Card number" + "message": "شماره کارت" }, "upgradeNow": { - "message": "Upgrade now" + "message": "هم‌اکنون ارتقا دهید" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "تولیدکننده رمز دوعاملی داخلی" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "ذخیره‌سازی امن فایل" }, "emergencyAccess": { - "message": "Emergency access" + "message": "دسترسی اضطراری" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "پایش نشت اطلاعات" }, "andMoreFeatures": { - "message": "And more!" + "message": "و بیشتر!" }, "planDescPremium": { - "message": "Complete online security" + "message": "امنیت آنلاین کامل" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "ارتقا به نسخه پرمیوم" }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "اقدام وقفه زمانی" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "وقفه زمانی نشست" } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index e2952659d03..2021248bae4 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Lue lisää" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Ominaisuus ei ole käytettävissä" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 Gt salattua tallennustilaa tiedostoliitteille." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Kaksivaiheisen kirjautumisen erikoisvaihtoehdot, kuten YubiKey ja Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Yhteystiedot" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Kaikki Sendit", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Haluatko varmasti käyttää asetusta \"Ei koskaan\"? Se tallentaa holvisi salausavaimen laitteellesi. Jos käytät asetusta, varmista, että laite on suojattu hyvin." }, "vault": { - "message": "Holvi" + "message": "Holvi", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Kirjaudu pääsalasanalla" diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 6eaa5577807..9c8bbaf0a34 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Matuto nang higit pa" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Hindi magagamit ang tampok" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB naka encrypt na imbakan para sa mga attachment ng file." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "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", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Sigurado ka bang gusto mong gamitin ang opsyon na \"Never\" Ang pagtatakda ng iyong mga pagpipilian sa lock sa \"Hindi kailanman\" ay nag iimbak ng key ng pag encrypt ng iyong vault sa iyong aparato. Kung gagamitin mo ang pagpipiliang ito dapat mong tiyakin na pinapanatili mong protektado nang maayos ang iyong aparato." }, "vault": { - "message": "Ayos" + "message": "Ayos", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Mag-login gamit ang pangunahing password" diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 6cca98444b8..8f7bfc39c4a 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "En savoir plus" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Fonctionnalité non disponible" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 Go de stockage chiffré pour les fichiers joints." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Options de connexion propriétaires à deux facteurs telles que YubiKey et Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Informations de contact" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Tous les Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Êtes-vous sûr de vouloir utiliser l'option \"Jamais\" ? Définir le verrouillage sur \"Jamais\" stocke la clé de chiffrement de votre coffre sur votre appareil. Si vous utilisez cette option, vous devez vous assurer de correctement protéger votre appareil." }, "vault": { - "message": "Coffre" + "message": "Coffre", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Se connecter avec le mot de passe principal" diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index d607bb8d097..0211550b08d 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 868cd9ccbc5..5881405c190 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "למידע נוסף" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "יכולת זו לא זמינה" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 ג'יגה של מקום אחסון מוצפן עבור קבצים מצורפים." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "אפשרויות כניסה דו־שלבית קנייניות כגון YubiKey ו־Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "פרטי איש קשר" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "כל הסֵנְדים", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "האם אתה בטוח שברצונך להשתמש באפשרות \"לעולם לא\"? הגדרת אפשרויות הנעילה שלך ל\"לעולם לא\" מאחסנת את מפתח ההצפנה של הכספת שלך במכשיר שלך. אם אתה משתמש באפשרות זו עליך לוודא שאתה שומר על המכשיר שלך מוגן כראוי." }, "vault": { - "message": "כספת" + "message": "כספת", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "כניסה עם סיסמה ראשית" diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 2ab323eedc9..2fac0a369fd 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 0f7a8185118..07effb638b8 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Saznaj više" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Značajka nije dostupna" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB šifriranog prostora za pohranu podataka." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Mogućnosti za prijavu u dva koraka kao što su YubiKey i Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Kontaktne informacije" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Svi Sendovi", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Sigurno želiš koristiti opciju „Nikada”? Postavljanje opcija zaključavanja na „Nikada” pohranjuje šifru tvojeg trezora na tvom uređaju. Ako koristiš ovu opciju, trebalo bi osigurati da je uređaj pravilno zaštićen." }, "vault": { - "message": "Trezor" + "message": "Trezor", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Prijava glavnom lozinkom" diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 9a6dd787f8c..583d5b86a59 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "További információ" }, + "migrationsFailed": { + "message": "Hiba történt a titkosítási beállítások frissítésekor." + }, + "updateEncryptionSettingsTitle": { + "message": "A titkosítási beállítások frissítése" + }, + "updateEncryptionSettingsDesc": { + "message": "Az új ajánlott titkosítási beállítások javítják a fiók biztonságát. Adjuk meg a mesterjelszót a frissítéshez most." + }, + "confirmIdentityToContinue": { + "message": "A folytatáshoz meg kell erősíteni a személyazonosságot." + }, + "enterYourMasterPassword": { + "message": "Mesterjelszó megadása" + }, + "updateSettings": { + "message": "Beállítások frissítése" + }, "featureUnavailable": { "message": "Ez a funkció nem érhető el." }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB titkosított fájlmelléklet tárhely." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ titkosított tárhely a fájlmellékletekhez.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Saját kétlépcsős bejelentkezési lehetőségek mint a YubiKey és a Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Elérhetőségi adatok" }, + "send": { + "message": "Send", + "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", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Biztosan szeretnénk használni a \"Soha\" opciót? A zárolási opciók \"Soha\" értékre állítása a széf titkosítási kulcsát az eszközön tárolja. Ennek az opciónak a használatakor célszerű az eszköz megfelelő védettségét biztosítani." }, "vault": { - "message": "Széf" + "message": "Széf", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Bejelentkezés mesterjelszóval" diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 188ee153da1..fbbb1440990 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Pelajari lebih lanjut" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Fitur Tidak Tersedia" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB penyimpanan berkas yang dienkripsi." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Semua Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Brankas" + "message": "Brankas", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 1656a301b42..64e31e4136f 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Non hai i permessi per modificare questo elemento" }, "welcomeBack": { "message": "Bentornato" @@ -775,7 +775,7 @@ "message": "Usa il Single Sign-On" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "La tua organizzazione richiede un accesso Single Sign-On (SSO)." }, "submit": { "message": "Invia" @@ -1039,7 +1039,7 @@ "message": "Devi aggiungere lo URL del server di base o almeno un ambiente personalizzato." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "Gli indirizzi devono usare il protocollo HTTPS." }, "customEnvironment": { "message": "Ambiente personalizzato" @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Ulteriori informazioni" }, + "migrationsFailed": { + "message": "Si è verificato un errore durante l'aggiornamento delle impostazioni di cifratura." + }, + "updateEncryptionSettingsTitle": { + "message": "Aggiorna le impostazioni di crittografia" + }, + "updateEncryptionSettingsDesc": { + "message": "Le nuove impostazioni di crittografia consigliate miglioreranno la sicurezza del tuo account. Inserisci la tua password principale per aggiornare." + }, + "confirmIdentityToContinue": { + "message": "Conferma la tua identità per continuare" + }, + "enterYourMasterPassword": { + "message": "Inserisci la tua password principale" + }, + "updateSettings": { + "message": "Aggiorna impostazioni" + }, "featureUnavailable": { "message": "Funzionalità non disponibile" }, @@ -1232,7 +1250,7 @@ "message": "Password principale errata" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Password principale errata. Verifica che l'email sia corretta e che l'account sia stato creato su $HOST$.", "placeholders": { "host": { "content": "$1", @@ -1321,7 +1339,7 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "showIconsChangePasswordUrls": { - "message": "Show website icons and retrieve change password URLs" + "message": "Mostra le icone dei siti e recupera gli URL di cambio password" }, "enableMinToTray": { "message": "Minimizza nell'area di notifica" @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB di spazio di archiviazione crittografato per gli allegati." }, + "premiumSignUpStorageV2": { + "message": "Archivio crittografato di $SIZE$ per allegati.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Opzioni di verifica in due passaggi proprietarie come YubiKey e Duo." }, @@ -1862,10 +1889,10 @@ "message": "Blocca con password principale al riavvio" }, "requireMasterPasswordOrPinOnAppRestart": { - "message": "Require master password or PIN on app restart" + "message": "Richiedi la password principale dopo il riavvio" }, "requireMasterPasswordOnAppRestart": { - "message": "Require master password on app restart" + "message": "Richiedi la password principale dopo il riavvio" }, "deleteAccount": { "message": "Elimina account" @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Informazioni di contatto" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Tutti i Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2562,7 +2593,7 @@ } }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "Il timeout personalizzato minimo è di 1 minuto." }, "inviteAccepted": { "message": "Invito accettato" @@ -2679,7 +2710,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Solo la cassaforte dell'organizzazione associata a $ORGANIZATION$ sarà esportata.", "placeholders": { "organization": { "content": "$1", @@ -2688,7 +2719,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Solo la cassaforte dell'organizzazione associata a $ORGANIZATION$ sarà esportata. Eventuali raccolte di elementi non saranno incluse.", "placeholders": { "organization": { "content": "$1", @@ -2991,7 +3022,8 @@ "message": "Sei sicuro di voler usare l'opzione \"Mai\"? Impostare le opzioni di blocco su \"Mai\" salverà la chiave di crittografia della cassaforte sul tuo dispositivo. Se usi questa opzione, assicurati di mantenere il tuo dispositivo adeguatamente protetto." }, "vault": { - "message": "Cassaforte" + "message": "Cassaforte", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Accedi con password principale" @@ -3628,10 +3660,10 @@ "message": "Continua ad accedere utilizzando le tue credenziali aziendali." }, "importDirectlyFromBrowser": { - "message": "Import directly from browser" + "message": "Importa direttamente dal browser" }, "browserProfile": { - "message": "Browser Profile" + "message": "Profilo Browser" }, "seeDetailedInstructions": { "message": "Consulta le istruzioni dettagliate sul nostro sito di assistenza su", @@ -3876,10 +3908,10 @@ "message": "Modifica la password non sicura o esposta" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "This login is at-risk and missing a website. Add a website and change the password for stronger security." + "message": "Questo login è a rischio e non contiene un sito web. Aggiungi un sito web e cambia la password per maggiore sicurezza." }, "missingWebsite": { - "message": "Missing website" + "message": "Sito Web mancante" }, "cannotRemoveViewOnlyCollections": { "message": "Non puoi rimuovere raccolte con i soli permessi di visualizzazione: $COLLECTIONS$", @@ -3977,10 +4009,10 @@ "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "aboutThisSetting": { - "message": "About this setting" + "message": "Informazioni su questa opzione" }, "permitCipherDetailsDescription": { - "message": "Bitwarden will use saved login URIs to identify which icon or change password URL should be used to improve your experience. No information is collected or saved when you use this service." + "message": "Bitwarden userà gli URL memorizzati in ogni login per mostrare, se possibile, l'icona del sito Web e l'URL di modifica password per facilitare la modifica delle credenziali. Nessuna informazione è raccolta o archiviata per il funzionamento di questo servizio." }, "assignToCollections": { "message": "Assegna a una raccolta" @@ -4126,13 +4158,13 @@ "message": "Bitwarden non convalida i campi di input: assicurati di essere nella finestra e nel campo di testo corretti prima di usare la scorciatoia." }, "typeShortcut": { - "message": "Type shortcut" + "message": "Premi i tasti da impostare per la scorciatoia" }, "editAutotypeShortcutDescription": { - "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + "message": "Includi uno o due dei seguenti modificatori: Ctrl, Alt, Win, o Shift, più una lettera." }, "invalidShortcut": { - "message": "Invalid shortcut" + "message": "Scorciatoia non valida" }, "moreBreadcrumbs": { "message": "Ulteriori segmenti", @@ -4148,80 +4180,80 @@ "message": "Conferma" }, "enableAutotypeShortcutPreview": { - "message": "Enable autotype shortcut (Feature Preview)" + "message": "Abilita la scorciatoia automatica (beta)" }, "enableAutotypeShortcutDescription": { - "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." + "message": "Assicurati di essere nel campo corretto prima di usare la scorciatoia." }, "editShortcut": { - "message": "Edit shortcut" + "message": "Modifica scorciatoia" }, "archiveNoun": { - "message": "Archive", + "message": "Archivio", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archivia", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "Rimuovi dall'Archivio" }, "itemsInArchive": { - "message": "Items in archive" + "message": "Elementi archiviati" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "Nessun elemento nell'archivio" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "Gli elementi archiviati appariranno qui e saranno esclusi dai risultati di ricerca e dall'auto-riempimento." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "Elemento archiviato" }, "itemWasUnarchived": { - "message": "Item was unarchived" + "message": "Elemento rimosso dall'archivio" }, "archiveItem": { - "message": "Archive item" + "message": "Archivia elemento" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "Gli elementi archiviati sono esclusi dai risultati di ricerca e dall'auto-riempimento. Vuoi davvero archiviare questo elemento?" }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "CAP / codice postale" }, "cardNumberLabel": { - "message": "Card number" + "message": "Numero carta" }, "upgradeNow": { - "message": "Upgrade now" + "message": "Aggiorna ora" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "App di autenticazione integrata" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "Archiviazione sicura di file" }, "emergencyAccess": { - "message": "Emergency access" + "message": "Accesso di emergenza" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "Monitoraggio delle violazioni" }, "andMoreFeatures": { - "message": "And more!" + "message": "E molto altro!" }, "planDescPremium": { - "message": "Complete online security" + "message": "Sicurezza online completa" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Aggiorna a Premium" }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Azione al timeout" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Timeout della sessione" } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index ca50828b12c..5accef2b5ee 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "詳細情報" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "サービスが利用できません" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1GB の暗号化されたファイルストレージ。" }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "YubiKey、Duo などのプロプライエタリな2段階認証オプション。" }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "すべての Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "本当に「なし」オプションを使用しますか?ロックオプションを「なし」に設定すると、保管庫の暗号化キーがデバイスに保存されます。 このオプションを使用する場合は、デバイスを適切に保護する必要があります。" }, "vault": { - "message": "保管庫" + "message": "保管庫", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "マスターパスワードでログイン" diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 9337286d3fd..cca3ab548cf 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "გაიგეთ მეტი" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "საცავი" + "message": "საცავი", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index d607bb8d097..0211550b08d 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index d1375efee8c..4ef1f0edd05 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "ವೈಶಿಷ್ಟ್ಯ ಲಭ್ಯವಿಲ್ಲ" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "ಫೈಲ್ ಲಗತ್ತುಗಳಿಗಾಗಿ 1 ಜಿಬಿ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಸಂಗ್ರಹ." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "ಎಲ್ಲಾ ಕಳುಹಿಸುತ್ತದೆ", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 2e40b8d7f23..9f6153cb314 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "더 알아보기" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "기능 사용할 수 없음" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1GB의 암호화된 파일 저장소." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "모든 Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "정말 \"잠그지 않음\" 옵션을 사용하시겠습니까? 잠금 옵션을 \"잠그지 않음\"으로 설정하면 사용자 보관함의 암호화 키를 사용자의 기기에 보관합니다. 이 옵션을 사용하기 전에 사용자의 기기가 잘 보호되어 있는 상태인지 확인하십시오." }, "vault": { - "message": "보관함" + "message": "보관함", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "마스터 비밀번호로 로그인" diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 16f328d6240..d7612765f1a 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Sužinoti daugiau" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funkcija nepasiekiama" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB užšifruotos vietos diske failų prisegimams." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Patentuotos dviejų žingsnių prisijungimo parinktys, tokios kaip YubiKey ir Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Visi Sendai", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Ar jūs tikrai norite naudoti \"Niekada\" pasirinkimą? Nustačius savo užraktą į \"Niekada\", saugyklos šifravimo raktas bus laikomas jūsų įrenginyje. Jei norite naudotis šiuo pasirinkimu, užtikrinkite savo įrenginio saugą." }, "vault": { - "message": "Saugykla" + "message": "Saugykla", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Prisijungti su pagrindiniu slaptažodžiu" diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 7800a4e9024..5624f89f5db 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Uzzināt vairāk" }, + "migrationsFailed": { + "message": "Atgadījās kļūda šifrēšanas iestatījumu atjaunināšanā." + }, + "updateEncryptionSettingsTitle": { + "message": "Atjaunini savus šifrēšanas iestatījumus" + }, + "updateEncryptionSettingsDesc": { + "message": "Jaunie ieteicamie šifrēšanas iestatījumi uzlabos Tava konta drošību. Jāievada sava galvenā parole, lai atjauninātu tagad." + }, + "confirmIdentityToContinue": { + "message": "Jāapliecina sava identitāte, lai turpinātu" + }, + "enterYourMasterPassword": { + "message": "Jāievada sava galvenā parole" + }, + "updateSettings": { + "message": "Atjaunināt Iestatījumus" + }, "featureUnavailable": { "message": "Iespēja nav pieejama" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB šifrētas krātuves datņu pielikumiem." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ šifrētas krātuves datņu pielikumiem.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Tādas slēgtā pirmavota divpakāpju pieteikšanās iespējas kā YubiKey un Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Saziņas informācija" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Visi Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Vai tiešām izmantot uzstādījumu \"Nekad\"? Uzstādot aizslēgšanas iespēju uz \"Nekad\", šifrēšanas atslēga tiek glabāta ierīcē. Ja šī iespēja tiek izmantota, jāpārliecinās, ka ierīce tiek pienācīgi aizsargāta." }, "vault": { - "message": "Glabātava" + "message": "Glabātava", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Pieteikties ar galveno paroli" diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 29e3cefee0c..684c4682aa0 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Saznaj više" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funkcija nije dostupna" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB šifrovanog skladišta za priloge datoteka." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 662ce9a1fc6..e91ca21a686 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "കൂടുതൽ അറിയുക" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "സവിശേഷത ലഭ്യമല്ല" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "ഫയൽ അറ്റാച്ചുമെന്റുകൾക്കായി 1 GB എൻക്രിപ്റ്റുചെയ്‌ത സ്റ്റോറേജ്." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "എല്ലാം Send-കൾ", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index d607bb8d097..0211550b08d 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index bcbd26cede3..969e67d3560 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 42fb6d479c0..35bd7750481 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Lær mer" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funksjonen er utilgjengelig" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB med kryptert fillagring." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Kontaktinformasjon" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Alle Send-er", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Er du sikker på at du vil bruke alternativet «Aldri»? Ved å angi låsemulighetene til «Aldri» lagres hvelvets krypteringsnøkkel på enheten. Hvis du bruker dette alternativet, bør du sørge for at du holder enheten forsvarlig beskyttet." }, "vault": { - "message": "Hvelv" + "message": "Hvelv", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Logg inn med hovedpassord" diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index cce8f6a2ba5..b8038093f90 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 82b51b018c5..8833f59489a 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Meer informatie" }, + "migrationsFailed": { + "message": "Er is een fout opgetreden bij het bijwerken van de versleutelingsinstellingen." + }, + "updateEncryptionSettingsTitle": { + "message": "Je versleutelingsinstellingen bijwerken" + }, + "updateEncryptionSettingsDesc": { + "message": "De nieuwe aanbevolen versleutelingsinstellingen verbeteren de beveiliging van je account. Voer je hoofdwachtwoord in om nu bij te werken." + }, + "confirmIdentityToContinue": { + "message": "Bevestig je identiteit om door te gaan" + }, + "enterYourMasterPassword": { + "message": "Voer je hoofdwachtwoord in" + }, + "updateSettings": { + "message": "Instellingen bijwerken" + }, "featureUnavailable": { "message": "Functionaliteit niet beschikbaar" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB versleutelde opslag voor bijlagen." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ versleutelde opslag voor bijlagen.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Eigen opties voor tweestapsaanmelding zoals YubiKey en Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact informatie" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Alle Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Weet je zeker dat je de optie \"Nooit\" wilt gebruiken? De vergrendelingsoptie \"Nooit\" bewaart de sleutel van je kluis op je apparaat. Als je deze optie gebruikt, moet je ervoor zorgen dat je je apparaat naar behoren beschermt." }, "vault": { - "message": "Kluis" + "message": "Kluis", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Inloggen met je hoofdwachtwoord" @@ -3631,7 +3663,7 @@ "message": "Import directly from browser" }, "browserProfile": { - "message": "Browser Profile" + "message": "Browserprofiel" }, "seeDetailedInstructions": { "message": "Zie gedetailleerde instructies op onze helpsite op", diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 08567979e8b..5264ce8c561 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Lær meir" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funksjon er utilgjengeleg" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Alle Send-ar", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 4ca05acaac5..b145306e14d 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index c05e7f05cb1..4cb6650e2c7 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Dowiedz się więcej" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funkcja jest niedostępna" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB miejsca na zaszyfrowane załączniki." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Specjalne opcje logowania dwustopniowego, takie jak YubiKey i Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Informacje kontaktowe" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Wszystkie wysyłki", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Czy na pewno chcesz użyć opcji „Nigdy”? Ustawienie blokady na „Nigdy” spowoduje przechowywanie klucza szyfrowania sejfu na urządzeniu. Upewnij się, że urządzenie jest odpowiednio chronione." }, "vault": { - "message": "Sejf" + "message": "Sejf", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Logowanie hasłem głównym" diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 7871ac72533..dc64f1b701b 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -36,13 +36,13 @@ "message": "Pastas" }, "collections": { - "message": "Coleções" + "message": "Conjuntos" }, "searchVault": { - "message": "Pesquisar no cofre" + "message": "Buscar no cofre" }, "resetSearch": { - "message": "Apagar pesquisa" + "message": "Apagar busca" }, "addItem": { "message": "Adicionar item" @@ -268,7 +268,7 @@ "message": "Requer Premium" }, "premiumRequiredDesc": { - "message": "Uma assinatura Premium é necessária para usar esse recurso." + "message": "Um plano Premium é necessário para usar esse recurso." }, "errorOccurred": { "message": "Ocorreu um erro." @@ -287,7 +287,7 @@ "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 perca adicional dos dados.", + "message": "para evitar a perca de dados adicionais.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "january": { @@ -681,16 +681,16 @@ "description": "Indicates that a policy limits the credential generator screen." }, "searchCollection": { - "message": "Pesquisar coleção" + "message": "Buscar no conjunto" }, "searchFolder": { - "message": "Pesquisar pasta" + "message": "Buscar na pasta" }, "searchFavorites": { - "message": "Pesquisar favoritos" + "message": "Buscar nos favoritos" }, "searchType": { - "message": "Pesquisar tipo", + "message": "Buscar tipo", "description": "Search item type" }, "newAttachment": { @@ -854,7 +854,7 @@ "message": "Solicitar dica" }, "requestPasswordHint": { - "message": "Dica da senha principal" + "message": "Solicitar dica da senha" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Digite o endereço de e-mail da sua conta e sua dica da senha será enviada para você" @@ -1093,11 +1093,29 @@ "learnMore": { "message": "Saiba mais" }, + "migrationsFailed": { + "message": "Ocorreu um erro ao atualizar as configurações de criptografia." + }, + "updateEncryptionSettingsTitle": { + "message": "Atualize suas configurações de criptografia" + }, + "updateEncryptionSettingsDesc": { + "message": "As novas configurações de criptografia recomendadas melhorarão a segurança da sua conta. Digite sua senha principal para atualizar agora." + }, + "confirmIdentityToContinue": { + "message": "Confirme sua identidade para continuar" + }, + "enterYourMasterPassword": { + "message": "Digite sua senha principal" + }, + "updateSettings": { + "message": "Atualizar configurações" + }, "featureUnavailable": { "message": "Recurso indisponível" }, "loggedOut": { - "message": "Sessão encerrada" + "message": "Desconectado" }, "loggedOutDesc": { "message": "Você foi desconectado de sua conta." @@ -1121,7 +1139,7 @@ "message": "Você tem certeza que deseja sair?" }, "logOut": { - "message": "Sair" + "message": "Desconectar" }, "addNewLogin": { "message": "Nova credencial" @@ -1295,16 +1313,16 @@ "message": "4 horas" }, "onIdle": { - "message": "Quando o sistema ficar inativo" + "message": "Na inatividade do sistema" }, "onSleep": { - "message": "Quando o sistema hibernar" + "message": "Na hibernação do sistema" }, "onLocked": { - "message": "Quando o sistema for bloqueado" + "message": "No bloqueio do sistema" }, "onRestart": { - "message": "Ao reiniciar" + "message": "No reinício" }, "never": { "message": "Nunca" @@ -1470,26 +1488,35 @@ "message": "número do cartão" }, "premiumMembership": { - "message": "Assinatura Premium" + "message": "Plano Premium" }, "premiumManage": { - "message": "Gerenciar assinatura" + "message": "Gerenciar plano" }, "premiumManageAlert": { - "message": "Você pode gerenciar a sua assinatura no cofre web do bitwarden.com. Você deseja visitar o site agora?" + "message": "Você pode gerenciar o seu plano no cofre web do bitwarden.com. Você deseja visitar o site agora?" }, "premiumRefresh": { - "message": "Recarregar assinatura" + "message": "Recarregar plano" }, "premiumNotCurrentMember": { "message": "Você não é um membro Premium atualmente." }, "premiumSignUpAndGet": { - "message": "Inscreva-se para uma assinatura Premium e receba:" + "message": "Inscreva-se para um plano Premium e receba:" }, "premiumSignUpStorage": { "message": "1 GB de armazenamento criptografado para anexos de arquivos." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ de armazenamento criptografado para anexos de arquivo.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Opções proprietárias de autenticação em duas etapas como YubiKey e Duo." }, @@ -1792,7 +1819,7 @@ "message": "Você não faz parte de uma organização. Organizações permitem-lhe compartilhar itens com segurança com outros usuários." }, "noCollectionsInList": { - "message": "Não há coleções para listar." + "message": "Não há conjuntos para listar." }, "ownership": { "message": "Propriedade" @@ -1898,10 +1925,10 @@ "message": "Sempre mostrar um ícone na barra de menu." }, "hideToMenuBar": { - "message": "Esconder na barra de menu" + "message": "Ocultar na barra de menu" }, "selectOneCollection": { - "message": "Você deve selecionar pelo menos uma coleção." + "message": "Você deve selecionar pelo menos um conjunto." }, "premiumUpdated": { "message": "Você fez upgrade para o Premium." @@ -1955,7 +1982,7 @@ "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { - "message": "Pesquisar na lixeira" + "message": "Buscar na lixeira" }, "permanentlyDeleteItem": { "message": "Apagar item para sempre" @@ -1973,7 +2000,7 @@ "message": "Apagar para sempre" }, "vaultTimeoutLogOutConfirmation": { - "message": "Sair irá remover todo o acesso ao seu cofre e requer autenticação online após o período de tempo limite. Tem certeza de que deseja usar esta configuração?" + "message": "Ao desconectar-se, todo o seu acesso ao cofre será removido e será necessário autenticação on-line após o período do tempo limite. Tem certeza que quer usar esta configuração?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Confirmação de ação do tempo limite" @@ -2088,16 +2115,16 @@ } }, "policyInEffectUppercase": { - "message": "Contém um ou mais caracteres em maiúsculo" + "message": "Conter um ou mais caracteres em maiúsculo" }, "policyInEffectLowercase": { - "message": "Contém um ou mais caracteres em minúsculo" + "message": "Conter um ou mais caracteres em minúsculo" }, "policyInEffectNumbers": { - "message": "Contém um ou mais números" + "message": "Conter um ou mais números" }, "policyInEffectSpecial": { - "message": "Contém um ou mais dos seguintes caracteres especiais $CHARS$", + "message": "Conter um ou mais dos seguintes caracteres especiais $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -2205,7 +2232,7 @@ "message": "Devido ao método de instalação, o suporte à biometria não pôde ser ativado automaticamente. Você gostaria de abrir a documentação sobre como fazer isso manualmente?" }, "personalOwnershipSubmitError": { - "message": "Devido a uma política empresarial, você não pode salvar itens no seu cofre pessoal. Altere a opção de propriedade para uma organização e escolha entre as coleções disponíveis." + "message": "Devido a uma política empresarial, você não pode salvar itens no seu cofre pessoal. Altere a opção de propriedade para uma organização e escolha entre os conjuntos disponíveis." }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { "message": "Sua senha nova não pode ser a mesma que a sua atual." @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Informações de contato" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Todos os Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2239,7 +2270,7 @@ "message": "Texto" }, "searchSends": { - "message": "Pesquisar Sends", + "message": "Buscar nos Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -2688,7 +2719,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. As coleções de itens não serão incluídos.", + "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Os itens dos meus conjuntos não serão incluídos.", "placeholders": { "organization": { "content": "$1", @@ -2991,7 +3022,8 @@ "message": "Você tem certeza que deseja usar a opção \"Nunca\"? Ao usar o \"Nunca\", a chave de criptografia do seu cofre é armazenada no seu dispositivo. Se você usar esta opção, deve garantir que mantém seu dispositivo devidamente protegido." }, "vault": { - "message": "Cofre" + "message": "Cofre", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Conectar-se com senha principal" @@ -3261,13 +3293,13 @@ "message": "Incapaz de concluir a autenticação" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { - "message": "Você precisa entrar com um dispositivo confiável ou solicitar ao administrador que lhe atribua uma senha." + "message": "Você precisa se conectar com um dispositivo confiado ou solicitar ao administrador que lhe atribua uma senha." }, "region": { "message": "Região" }, "ssoIdentifierRequired": { - "message": "Identificador de SSO da organização é necessário." + "message": "O identificador de SSO da organização é necessário." }, "eu": { "message": "União Europeia", @@ -3286,19 +3318,19 @@ "message": "Aprovação do administrador necessária" }, "adminApprovalRequestSentToAdmins": { - "message": "Seu pedido foi enviado para seu administrador." + "message": "Sua solicitação foi enviada para seu administrador." }, "troubleLoggingIn": { "message": "Problemas para acessar?" }, "loginApproved": { - "message": "Sessão aprovada" + "message": "Autenticação aprovada" }, "userEmailMissing": { "message": "E-mail do usuário ausente" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "E-mail de usuário ativo não encontrado. Desconectando." + "message": "E-mail do usuário ativo não encontrado. Desconectando você." }, "deviceTrusted": { "message": "Dispositivo confiado" @@ -3316,13 +3348,13 @@ "message": "Organização não foi confiada" }, "emergencyAccessTrustWarning": { - "message": "Para a segurança da sua conta, apenas confirme se você permitiu o acesso de emergência a esse usuário e se a frase biométrica dele coincide com a que é exibida em sua conta" + "message": "Para a segurança da sua conta, apenas confirme se você permitiu o acesso de emergência a esse usuário e se a frase biométrica dele coincide com a que é exibida na conta deles" }, "orgTrustWarning": { "message": "Para a segurança da sua conta, prossiga apenas se você for um membro dessa organização, tem uma recuperação de conta ativa e a frase biométrica exibida abaixo corresponde com a da organização." }, "orgTrustWarning1": { - "message": "Esta organização tem uma política empresarial que lhe inscreverá na recuperação de conta. A matrícula permitirá que os administradores da organização alterem sua senha. Prossiga somente se você reconhecer esta organização e se a frase biométrica exibida abaixo corresponde com a impressão digital da organização." + "message": "Esta organização tem uma política empresarial que lhe inscreverá na recuperação de conta. A inscrição permitirá que os administradores da organização alterem sua senha. Prossiga somente se você reconhecer esta organização e se a frase biométrica exibida abaixo corresponde com a da organização." }, "trustUser": { "message": "Confiar no usuário" @@ -3334,7 +3366,7 @@ "message": "obrigatório" }, "search": { - "message": "Pesquisar" + "message": "Buscar" }, "inputMinLength": { "message": "A entrada deve ter pelo menos $COUNT$ caracteres.", @@ -3373,7 +3405,7 @@ } }, "inputMaxValue": { - "message": "O valor de entrada não deve exceder $MAX$.", + "message": "O valor de entrada deve não exceder $MAX$.", "placeholders": { "max": { "content": "$1", @@ -3428,7 +3460,7 @@ "message": "Submenu" }, "toggleSideNavigation": { - "message": "Ativar/desativar navegação lateral" + "message": "Habilitar navegação lateral" }, "skipToContent": { "message": "Ir para o conteúdo" @@ -3440,10 +3472,10 @@ "message": "A chave de acesso não será copiada" }, "passkeyNotCopiedAlert": { - "message": "A senha não será copiada para o item clonado. Deseja continuar clonando este item?" + "message": "A chave de acesso não será copiada para o item clonado. Deseja continuar clonando este item?" }, "aliasDomain": { - "message": "Alias do domínio" + "message": "Domínio de alias" }, "importData": { "message": "Importar dados", @@ -3492,10 +3524,10 @@ "message": "A autenticação em duas etapas do Duo é necessária para sua conta." }, "duoTwoFactorRequiredPageSubtitle": { - "message": "A autenticação de dois fatores é necessária para sua conta. Siga os passos abaixo para conseguir entrar." + "message": "A autenticação de duas etapas do Duo é necessária para sua conta. Siga os passos abaixo para conseguir se conectar." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Siga os passos abaixo para finalizar a autenticação." + "message": "Siga os passos abaixo para terminar de se conectar." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { "message": "Siga os passos abaixo para finalizar a autenticação com a sua chave de segurança." @@ -3525,13 +3557,13 @@ "message": "Selecione uma pasta" }, "selectImportCollection": { - "message": "Selecione uma coleção" + "message": "Selecione um conjunto" }, "importTargetHintCollection": { - "message": "Selecione esta opção caso queira importar os conteúdos de arquivos para uma coleção" + "message": "Selecione esta opção caso queira importar os conteúdos do arquivo para um conjunto" }, "importTargetHintFolder": { - "message": "Selecione esta opção caso queira importar os conteúdos de arquivos para uma pasta" + "message": "Selecione esta opção caso queira importar os conteúdos do arquivo para uma pasta" }, "importUnassignedItemsError": { "message": "Arquivo contém itens não atribuídos." @@ -3543,7 +3575,7 @@ "message": "Selecione o arquivo de importação" }, "chooseFile": { - "message": "Escolher arquivo" + "message": "Selecionar arquivo" }, "noFileChosen": { "message": "Nenhum arquivo escolhido" @@ -3604,7 +3636,7 @@ "message": "Importando sua conta..." }, "lastPassMFARequired": { - "message": "Requer autenticação multifatores do LastPass" + "message": "Requer autenticação de múltiplos fatores do LastPass" }, "lastPassMFADesc": { "message": "Digite sua senha única do app autenticador" @@ -3625,7 +3657,7 @@ "message": "Aguardando autenticação do SSO" }, "awaitingSSODesc": { - "message": "Continue autenticando-se usando as credenciais da sua empresa." + "message": "Continue conectando-se usando as credenciais da sua empresa." }, "importDirectlyFromBrowser": { "message": "Importar diretamente do navegador" @@ -3647,7 +3679,7 @@ "message": "Tente novamente ou procure um e-mail do LastPass para verificar que é você." }, "collection": { - "message": "Coleção" + "message": "Conjunto" }, "lastPassYubikeyDesc": { "message": "Insira a YubiKey associada com a sua conta do LastPass na porta USB do seu computador, e depois toque no botão dela." @@ -3669,7 +3701,7 @@ "description": "Content for dialog which warns a user when selecting 'starts with' matching strategy as a cipher match strategy" }, "uriMatchWarningDialogLink": { - "message": "Mais sobre detecção de correspondências", + "message": "Mais sobre a detecção de correspondência", "description": "Link to match detection docs on warning dialog for advance match strategy" }, "uriAdvancedOption": { @@ -3699,13 +3731,13 @@ "message": "Chave de acesso removida" }, "errorAssigningTargetCollection": { - "message": "Erro ao atribuir coleção de destino." + "message": "Erro ao atribuir conjunto de destino." }, "errorAssigningTargetFolder": { "message": "Erro ao atribuir pasta de destino." }, "viewItemsIn": { - "message": "Visualizar itens em $NAME$", + "message": "Ver itens em $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3768,10 +3800,10 @@ "message": "O desbloqueio por biometria está indisponível no momento." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "O desbloqueio por biometria está indisponível devido a algum erro na configuração dos arquivos de sistema." + "message": "O desbloqueio por biometria está indisponível devido a algum erro na configuração dos arquivos do sistema." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "O desbloqueio por biometria está indisponível devido a algum erro na configuração dos arquivos de sistema." + "message": "O desbloqueio por biometria está indisponível devido a algum erro na configuração dos arquivos do sistema." }, "biometricsStatusHelptextNotEnabledLocally": { "message": "O desbloqueio por biometria está indisponível, pois a opção não foi ativada no aplicativo de computador de $EMAIL$.", @@ -3834,7 +3866,7 @@ "message": "assinar uma mensagem" }, "sshActionGitSign": { - "message": "assinar um commit no git" + "message": "assinar uma commit no git" }, "unknownApplication": { "message": "Um aplicativo" @@ -3852,7 +3884,7 @@ "message": "Chave SSH importada com sucesso" }, "fileSavedToDevice": { - "message": "Arquivo salvo no dispositivo. Gerencie a partir dos downloads do seu dispositivo." + "message": "Arquivo salvo no dispositivo. Gerencie a partir das transferências do seu dispositivo." }, "allowScreenshots": { "message": "Permitir captura de tela" @@ -3870,7 +3902,7 @@ "message": "Atualização de extensão necessária" }, "updateBrowserOrDisableFingerprintDialogMessage": { - "message": "A extensão de navegador que você está usando está desatualizada. Atualize-a ou desative a validação de integração de frase biométrica do navegador nas configurações do aplicativo da área de trabalho." + "message": "A extensão de navegador que você está usando está desatualizada. Atualize-a ou desative a validação da frase biométrica da integração do navegador nas configurações do aplicativo de computador." }, "changeAtRiskPassword": { "message": "Alterar senhas em risco" @@ -3882,7 +3914,7 @@ "message": "Site ausente" }, "cannotRemoveViewOnlyCollections": { - "message": "Você não pode remover coleções com permissões de Apenas visualização: $COLLECTIONS$", + "message": "Você não pode remover conjuntos com permissões de Apenas ver: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -3983,25 +4015,25 @@ "message": "O Bitwarden usará URIs de credenciais salvas para identificar qual ícone ou URL de mudança de senha deverá ser usado para melhorar sua experiência. Nenhuma informação é coletada ou salva quando você utiliza este serviço." }, "assignToCollections": { - "message": "Atribuir a coleções" + "message": "Atribuir a conjuntos" }, "assignToTheseCollections": { - "message": "Atribuir a estas coleções" + "message": "Atribuir a estes conjuntos" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Apenas membros da organização com acesso a essas coleções poderão ver o item." + "message": "Apenas membros da organização com acesso a estes conjuntos poderão ver o item." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Apenas membros da organização com acesso a essas coleções poderão ver os itens." + "message": "Apenas membros da organização com acesso a estes conjuntos poderão ver os itens." }, "noCollectionsAssigned": { - "message": "Nenhuma coleção foi atribuída" + "message": "Nenhum conjunto foi atribuído" }, "assign": { "message": "Atribuir" }, "bulkCollectionAssignmentDialogDescription": { - "message": "Apenas membros da organização com acesso a essas coleções poderão ver os itens." + "message": "Apenas membros da organização com acesso a estes conjuntos poderão ver os itens." }, "bulkCollectionAssignmentWarning": { "message": "Você selecionou $TOTAL_COUNT$ itens. Você não pode atualizar $READONLY_COUNT$ dos itens, pois você não tem as permissões de edição.", @@ -4016,10 +4048,10 @@ } }, "selectCollectionsToAssign": { - "message": "Selecione coleções para atribuir" + "message": "Selecione conjuntos a atribuir" }, "personalItemsTransferWarning": { - "message": "$PERSONAL_ITEMS_COUNT$ serão permanentemente transferidas para a organização selecionada. Você deixará de ser o proprietário desses itens.", + "message": "$PERSONAL_ITEMS_COUNT$ serão permanentemente transferidos para a organização selecionada. Você deixará de ser o proprietário desses itens.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4028,7 +4060,7 @@ } }, "personalItemsWithOrgTransferWarning": { - "message": "$PERSONAL_ITEMS_COUNT$ serão permanentemente transferidas para $ORG$. Você deixará de ser o proprietário desses itens.", + "message": "$PERSONAL_ITEMS_COUNT$ serão permanentemente transferidos para $ORG$. Você deixará de ser o proprietário desses itens.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4053,7 +4085,7 @@ } }, "successfullyAssignedCollections": { - "message": "Coleções atribuídas com sucesso" + "message": "Conjuntos atribuídos com sucesso" }, "nothingSelected": { "message": "Você não selecionou nada." @@ -4126,10 +4158,10 @@ "message": "O Bitwarden não valida localizações de entrada, tenha certeza de estar na janela e campo corretos antes de utilizar o atalho." }, "typeShortcut": { - "message": "Digitar atalho" + "message": "Atalho de digitação" }, "editAutotypeShortcutDescription": { - "message": "Inclua um ou dois dos seguintes modificadores: Ctrl, Alt, Win, Shift e uma letra." + "message": "Inclua um ou dois dos seguintes modificadores: Ctrl, Alt, Win, ou Shift, e uma letra." }, "invalidShortcut": { "message": "Atalho inválido" @@ -4142,13 +4174,13 @@ "message": "Avançar" }, "confirmKeyConnectorDomain": { - "message": "Confirmar domínio do Conector de Chave" + "message": "Confirmar domínio do Key Connector" }, "confirm": { "message": "Confirmar" }, "enableAutotypeShortcutPreview": { - "message": "Ativar atalho da digitação automática (Feature Preview)" + "message": "Ativar atalho de digitação automática (Feature Preview)" }, "enableAutotypeShortcutDescription": { "message": "Certifique-se que está no campo correto antes de usar o atalho para evitar preencher dados no lugar errado." @@ -4174,7 +4206,7 @@ "message": "Nenhum item no arquivo" }, "noItemsInArchiveDesc": { - "message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados de pesquisa gerais e das sugestões de preenchimento automático." + "message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados gerais de busca e das sugestões de preenchimento automático." }, "itemWasSentToArchive": { "message": "O item foi enviado para o arquivo" @@ -4186,7 +4218,7 @@ "message": "Arquivar item" }, "archiveItemConfirmDesc": { - "message": "Itens arquivados são excluídos dos resultados da pesquisa geral e das sugestões de preenchimento automático. Tem certeza de que deseja arquivar este item?" + "message": "Itens arquivados são excluídos dos resultados gerais de busca e das sugestões de preenchimento automático. Tem certeza de que deseja arquivar este item?" }, "zipPostalCodeLabel": { "message": "CEP / Código postal" @@ -4207,7 +4239,7 @@ "message": "Acesso de emergência" }, "breachMonitoring": { - "message": "Monitoramento de brechas" + "message": "Monitoramento de vazamentos" }, "andMoreFeatures": { "message": "E mais!" diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index de0427ddab0..c0e396c63c4 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Saber mais" }, + "migrationsFailed": { + "message": "Ocorreu um erro ao atualizar as definições de encriptação." + }, + "updateEncryptionSettingsTitle": { + "message": "Atualize as suas definições de encriptação" + }, + "updateEncryptionSettingsDesc": { + "message": "As novas definições de encriptação recomendadas irão melhorar a segurança da sua conta. Introduza a sua palavra-passe mestra para atualizar agora." + }, + "confirmIdentityToContinue": { + "message": "Confirme a sua identidade para continuar" + }, + "enterYourMasterPassword": { + "message": "Introduza a sua palavra-passe mestra" + }, + "updateSettings": { + "message": "Atualizar definições" + }, "featureUnavailable": { "message": "Funcionalidade indisponível" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB de armazenamento encriptado para anexos de ficheiros." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ de armazenamento encriptado para anexos de ficheiros.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Opções proprietárias de verificação de dois passos, como YubiKey e Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Informações de contacto" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Todos os Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Tem a certeza de que deseja utilizar a opção \"Nunca\"? Ao definir as opções de bloqueio para \"Nunca\" armazena a chave de encriptação do seu cofre no seu dispositivo. Se utilizar esta opção deve assegurar-se de que mantém o seu dispositivo devidamente protegido." }, "vault": { - "message": "Cofre" + "message": "Cofre", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Iniciar sessão com a palavra-passe mestra" @@ -3048,7 +3080,7 @@ "message": "Será notificado quando o pedido for aprovado" }, "needAnotherOption": { - "message": "O início de sessão com o dispositivo deve ser ativado nas definições da aplicação Bitwarden. Precisa de outra opção?" + "message": "O início de sessão com o dispositivo deve ser ativado nas definições da app Bitwarden. Precisa de outra opção?" }, "viewAllLogInOptions": { "message": "Ver todas as opções de início de sessão" diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index a72ce3547e9..d2e589836e0 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Aflați mai multe" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funcție indisponibilă" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB spațiu de stocare criptat pentru atașamente de fișiere." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Toate Send-urile", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Sunteți sigur că doriți să folosiți opțiunea „Niciodată”? Setarea opțiunilor de blocare la „Niciodată” stochează cheia de criptare a seifului pe dispozitivul dumneavoastră. Dacă utilizați această opțiune, trebuie să vă asigurați că vă păstrați dispozitivul protejat corespunzător." }, "vault": { - "message": "Seif" + "message": "Seif", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Autentificați-vă cu parola principală" diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 914bb603630..a59ae2282d5 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Подробнее" }, + "migrationsFailed": { + "message": "Произошла ошибка при обновлении настроек шифрования." + }, + "updateEncryptionSettingsTitle": { + "message": "Обновите настройки шифрования" + }, + "updateEncryptionSettingsDesc": { + "message": "Новые рекомендуемые настройки шифрования повысят безопасность вашего аккаунта. Введите мастер-пароль, чтобы обновить сейчас." + }, + "confirmIdentityToContinue": { + "message": "Подтвердите вашу личность, чтобы продолжить" + }, + "enterYourMasterPassword": { + "message": "Введите ваш мастер-пароль" + }, + "updateSettings": { + "message": "Обновить настройки" + }, "featureUnavailable": { "message": "Функция недоступна" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 ГБ зашифрованного хранилища для вложенных файлов." }, + "premiumSignUpStorageV2": { + "message": "Зашифрованного хранилища для вложенных файлов: $SIZE$", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Проприетарные варианты двухэтапной аутентификации, такие как YubiKey или Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Контактная информация" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Все Send’ы", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Вы действительно хотите отключить блокировку хранилища? В этом случае ключ шифрования вашего хранилища будет сохранен на вашем устройстве. Отключая блокировку, вы должны убедиться, что ваше устройство надежно защищено." }, "vault": { - "message": "Хранилище" + "message": "Хранилище", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Войти с мастер-паролем" diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index a83b2cbf536..51333386f8a 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 0b14b961bbb..0e62a8ee8b0 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Zistiť viac" }, + "migrationsFailed": { + "message": "Pri aktualizácii nastavení šifrovania došlo k chybe." + }, + "updateEncryptionSettingsTitle": { + "message": "Aktualizujte nastavenie šifrovania" + }, + "updateEncryptionSettingsDesc": { + "message": "Nové odporúčané nastavenia šifrovania zlepšia bezpečnosť vášho účtu. Ak ich chcete aktualizovať teraz, zadajte hlavné heslo." + }, + "confirmIdentityToContinue": { + "message": "Ak chcete pokračovať, potvrďte svoju identitu" + }, + "enterYourMasterPassword": { + "message": "Zadajte hlavné heslo" + }, + "updateSettings": { + "message": "Aktualizovať nastavenia" + }, "featureUnavailable": { "message": "Funkcia nie je k dispozícii" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB šifrovaného úložiska." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ šifrovaného úložiska na prílohy.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietárne možnosti dvojstupňového prihlásenia ako napríklad YubiKey a Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Kontaktné informácie" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Všetky Sendy", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Ste si istí, že chcete použiť možnosť \"Nikdy\"? Táto predvoľba ukladá šifrovací kľúč od trezora priamo na zariadení. Ak použijete túto možnosť, mali by ste svoje zariadenie náležite zabezpečiť." }, "vault": { - "message": "Trezor" + "message": "Trezor", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Prihlásenie pomocou hlavného hesla" diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 353c6858afa..c640320ab1a 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Več o tem" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Funkcija ni na voljo" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Vsi Sendsi", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 1bc4a0ed016..4e69efe726f 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Сазнај више" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Функција је недоступна" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1ГБ шифровано складиште за прилоге." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Приоритарне опције пријаве у два корака као што су YubiKey и Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Контакт подаци" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Сва слања", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Да ли сте сигурни да желите да користите опцију „Никад“? Ако поставите опције закључавања на „Никада“, на вашем уређају се чува кључ за шифровање сефа. Ако користите ову опцију, осигурајте да је уређај правилно заштићен." }, "vault": { - "message": "Сеф" + "message": "Сеф", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Пријавите се са главном лозинком" diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 93d56419ae3..6f3e68c8959 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Läs mer" }, + "migrationsFailed": { + "message": "Ett fel inträffade när krypteringsinställningarna skulle uppdateras." + }, + "updateEncryptionSettingsTitle": { + "message": "Uppdatera dina krypteringsinställningar" + }, + "updateEncryptionSettingsDesc": { + "message": "De nya rekommenderade krypteringsinställningarna kommer att förbättra säkerheten för ditt konto. Ange ditt huvudlösenord för att uppdatera nu." + }, + "confirmIdentityToContinue": { + "message": "Bekräfta din identitet för att fortsätta" + }, + "enterYourMasterPassword": { + "message": "Ange ditt huvudlösenord" + }, + "updateSettings": { + "message": "Uppdatera inställningar" + }, "featureUnavailable": { "message": "Funktion ej tillgänglig" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB krypterad lagring." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ krypterad lagring för filbilagor.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Premium-alternativ för tvåstegsverifiering, såsom YubiKey och Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Kontaktinformation" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Alla Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Är du säker på att du vill använda alternativet ”Aldrig”? Att ställa in låsnings-alternativet till ”Aldrig” lagrar valvets krypteringsnyckel på datorn. Om du använder det här alternativet bör du se till att du håller datorn ordentligt skyddad." }, "vault": { - "message": "Valv" + "message": "Valv", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Logga in med huvudlösenord" diff --git a/apps/desktop/src/locales/ta/messages.json b/apps/desktop/src/locales/ta/messages.json index 2f9d12917d6..a83867a9eff 100644 --- a/apps/desktop/src/locales/ta/messages.json +++ b/apps/desktop/src/locales/ta/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "மேலும் அறியவும்" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "வசதி கிடைக்கவில்லை" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "கோப்பு இணைப்புகளுக்கு 1 GB குறியாக்கப்பட்ட சேமிப்பகம்." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "YubiKey மற்றும் Duo போன்ற பிரத்யேக இரண்டு-படி உள்நுழைவு விருப்பங்கள்." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "தொடர்புத் தகவல்" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "அனைத்து அனுப்புதல்களும்", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "\"ஒருபோதும் இல்லை\" விருப்பத்தைப் பயன்படுத்த விரும்புகிறீர்களா? உங்கள் பூட்டு விருப்பங்களை \"ஒருபோதும் இல்லை\" என அமைப்பது உங்கள் வால்ட்டின் குறியாக்க சாவியை உங்கள் சாதனத்தில் சேமிக்கிறது. இந்த விருப்பத்தைப் பயன்படுத்தினால், உங்கள் சாதனத்தை நீங்கள் சரியாகப் பாதுகாப்பதை உறுதி செய்ய வேண்டும்." }, "vault": { - "message": "வால்ட்" + "message": "வால்ட்", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "முதன்மை கடவுச்சொல்லுடன் உள்நுழைக" diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index d607bb8d097..0211550b08d 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index d794ace629c..f5bbde79a86 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "เรียนรู้เพิ่มเติม" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Feature Unavailable" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 GB of encrypted file storage." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Contact information" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "ส่งทั้งหมด", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Vault", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Log in with master password" diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index ac67b177cbf..bb0b6f2fd51 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Daha fazla bilgi al" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Özellik kullanılamıyor" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "Dosya ekleri için 1 GB şifrelenmiş depolama." }, + "premiumSignUpStorageV2": { + "message": "Dosya ekleri için $SIZE$ şifrelenmiş depolama.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "YubiKey ve Duo gibi marka bazlı iki aşamalı giriş seçenekleri." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "İletişim bilgileri" }, + "send": { + "message": "Send", + "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", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "\"Asla\" seçeneğini kullanmak istediğinizden emin misiniz? Kilit seçeneklerinizi \"Asla\" olarak ayarlarsanız kasanızın şifreleme anahtarı cihazınızda saklanacaktır. Bu seçeneği kullanırsanız cihazınızı çok iyi korumalısınız." }, "vault": { - "message": "Kasa" + "message": "Kasa", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Ana parola ile giriş yap" diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 7ed0710ca74..bed09352f03 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Докладніше" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Функція недоступна" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1 ГБ зашифрованого сховища для файлів." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Додаткові можливості двоетапної авторизації, як-от YubiKey та Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Контактна інформація" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Усі відправлення", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Ви впевнені, що ніколи не хочете блокувати? Встановивши параметр блокування \"Ніколи\", ключ шифрування сховища зберігатиметься на вашому пристрої. Користуючись цим параметром, ви маєте бути впевнені в тому, що ваш пристрій надійно захищений." }, "vault": { - "message": "Сховище" + "message": "Сховище", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Увійти з головним паролем" diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 8bf88aba458..30038f046db 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "Tìm hiểu thêm" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "Tính năng không có sẵn" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "1GB bộ nhớ lưu trữ được mã hóa cho các tệp đính kèm." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Các tùy chọn xác minh hai bước như YubiKey và Duo." }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "Thông tin liên hệ" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "Tất cả Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "Bạn có chắc chắn muốn chọn \"Không bao giờ\" không? Lựa chọn này sẽ lưu khóa mã hóa kho của bạn trực tiếp trên thiết bị. Hãy nhớ bảo vệ thiết bị của bạn thật cẩn thận nếu bạn chọn tùy chọn này." }, "vault": { - "message": "Kho lưu trữ" + "message": "Kho lưu trữ", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "Đăng nhập bằng mật khẩu chính" diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index b5e68b83bde..80965415475 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -268,7 +268,7 @@ "message": "需要高级版" }, "premiumRequiredDesc": { - "message": "使用此功能需要高级会员资格。" + "message": "需要高级会员才能使用此功能。" }, "errorOccurred": { "message": "发生错误。" @@ -1093,6 +1093,24 @@ "learnMore": { "message": "进一步了解" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "功能不可用" }, @@ -1387,7 +1405,7 @@ "message": "语言" }, "languageDesc": { - "message": "更改应用程序所使用的语言。重启后生效。" + "message": "更改应用程序所使用的语言。需要重启。" }, "theme": { "message": "主题" @@ -1488,7 +1506,16 @@ "message": "注册高级会员将获得:" }, "premiumSignUpStorage": { - "message": "1 GB 文件附件加密存储。" + "message": "1 GB 文件附件加密存储空间。" + }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ 文件附件加密存储空间。", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } }, "premiumSignUpTwoStepOptions": { "message": "专有的两步登录选项,如 YubiKey 和 Duo。" @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "联系信息" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "所有的 Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "确定要使用「从不」选项吗?将锁定选项设置为「从不」会将密码库的加密密钥存储在您的设备上。如果使用此选项,您必须确保您的设备安全。" }, "vault": { - "message": "密码库" + "message": "密码库", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "使用主密码登录" @@ -3980,7 +4012,7 @@ "message": "关于此设置" }, "permitCipherDetailsDescription": { - "message": "Bitwarden 将使用已保存的登录 URI 来识别应使用哪个图标或更改密码的 URL 来改善您的体验。当您使用此服务时,不会收集或保存任何信息。" + "message": "Bitwarden 将使用已保存的登录 URI 来确定应使用的图标或更改密码的 URL,以提升您的使用体验。使用此服务时不会收集或保存任何信息。" }, "assignToCollections": { "message": "分配到集合" diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 61fc00543ed..e412adf9e5b 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -1093,6 +1093,24 @@ "learnMore": { "message": "了解更多" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "featureUnavailable": { "message": "功能不可用" }, @@ -1490,6 +1508,15 @@ "premiumSignUpStorage": { "message": "用於檔案附件的 1 GB 的加密檔案儲存空間。" }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "專有的兩步驟登入選項,例如 YubiKey 和 Duo。" }, @@ -2228,6 +2255,10 @@ "contactInfo": { "message": "聯絡資訊" }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "allSends": { "message": "所有 Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2991,7 +3022,8 @@ "message": "您確定要使用「永不」選項嗎?將鎖定選項設定為「永不」會將密碼庫的加密金鑰儲存在您的裝置上。如果使用此選項,應確保您的裝置是安全的。" }, "vault": { - "message": "密碼庫" + "message": "密碼庫", + "description": "'Vault' is a noun and refers to the Bitwarden Vault feature." }, "loginWithMasterPassword": { "message": "使用主密碼登入" diff --git a/apps/desktop/src/main/native-messaging.main.ts b/apps/desktop/src/main/native-messaging.main.ts index ba5d8616752..a0c17a115e0 100644 --- a/apps/desktop/src/main/native-messaging.main.ts +++ b/apps/desktop/src/main/native-messaging.main.ts @@ -14,7 +14,7 @@ import { isDev } from "../utils"; import { WindowMain } from "./window.main"; export class NativeMessagingMain { - private ipcServer: ipc.IpcServer | null; + private ipcServer: ipc.NativeIpcServer | null; private connected: number[] = []; constructor( @@ -78,7 +78,7 @@ export class NativeMessagingMain { this.ipcServer.stop(); } - this.ipcServer = await ipc.IpcServer.listen("bw", (error, msg) => { + this.ipcServer = await ipc.NativeIpcServer.listen("bw", (error, msg) => { switch (msg.kind) { case ipc.IpcMessageType.Connected: { this.connected.push(msg.clientId); @@ -314,6 +314,7 @@ export class NativeMessagingMain { "Microsoft Edge Canary": `${this.homedir()}/Library/Application\ Support/Microsoft\ Edge\ Canary/`, Vivaldi: `${this.homedir()}/Library/Application\ Support/Vivaldi/`, Zen: `${this.homedir()}/Library/Application\ Support/Zen/`, + Helium: `${this.homedir()}/Library/Application\ Support/net.imput.helium/`, }; /* eslint-enable no-useless-escape */ } diff --git a/apps/desktop/src/main/tray.main.ts b/apps/desktop/src/main/tray.main.ts index b7ddefe6e1b..81df6497ca8 100644 --- a/apps/desktop/src/main/tray.main.ts +++ b/apps/desktop/src/main/tray.main.ts @@ -53,9 +53,14 @@ export class TrayMain { }, { visible: isDev(), - label: "Fake Popup", + label: "Fake Popup Select", click: () => this.fakePopup(), }, + { + visible: isDev(), + label: "Fake Popup Create", + click: () => this.fakePopupCreate(), + }, { type: "separator" }, { label: this.i18nService.t("exit"), @@ -218,4 +223,8 @@ export class TrayMain { private async fakePopup() { await this.messagingService.send("loadurl", { url: "/passkeys", modal: true }); } + + private async fakePopupCreate() { + await this.messagingService.send("loadurl", { url: "/create-passkey", modal: true }); + } } diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index d148a1a35f8..bbdd2ad0a0f 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -100,10 +100,10 @@ export class WindowMain { 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) { + } else if (newValue.isModalModeActive) { // Apply the popup modal styles this.logService.info("Applying popup modal styles", newValue.modalPosition); - applyPopupModalStyles(this.win, newValue.modalPosition); + applyPopupModalStyles(this.win, newValue.showTrafficButtons, newValue.modalPosition); this.win.show(); } }), @@ -185,6 +185,7 @@ export class WindowMain { await this.createWindow(); resolve(); + if (this.argvCallback != null) { this.argvCallback(process.argv); } @@ -272,7 +273,7 @@ export class WindowMain { this.win = new BrowserWindow({ width: this.windowStates[mainWindowSizeKey].width, height: this.windowStates[mainWindowSizeKey].height, - minWidth: 680, + minWidth: 600, minHeight: 500, x: this.windowStates[mainWindowSizeKey].x, y: this.windowStates[mainWindowSizeKey].y, diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index d16be39369d..9d8eae15791 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.11.3", + "version": "2025.12.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.11.3", + "version": "2025.12.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 2f58b712a86..2ac5d339a95 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.11.3", + "version": "2025.12.1", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/platform/components/approve-ssh-request.html b/apps/desktop/src/platform/components/approve-ssh-request.html index 55092788079..c691891487e 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.html +++ b/apps/desktop/src/platform/components/approve-ssh-request.html @@ -2,13 +2,11 @@
    {{ "sshkeyApprovalTitle" | i18n }}
    - + @if (params.isAgentForwarding) { + {{ 'agentForwardingWarningText' | i18n }} - + + } {{params.applicationName}} {{ "sshkeyApprovalMessageInfix" | i18n }} {{params.cipherName}} diff --git a/apps/desktop/src/platform/components/approve-ssh-request.ts b/apps/desktop/src/platform/components/approve-ssh-request.ts index 1741124774d..a2cae3d59e7 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.ts +++ b/apps/desktop/src/platform/components/approve-ssh-request.ts @@ -12,6 +12,7 @@ import { FormFieldModule, IconButtonModule, DialogService, + CalloutModule, } from "@bitwarden/components"; export interface ApproveSshRequestParams { @@ -35,6 +36,7 @@ export interface ApproveSshRequestParams { ReactiveFormsModule, AsyncActionsModule, FormFieldModule, + CalloutModule, ], }) export class ApproveSshRequestComponent { 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 71cfcab84ba..c0d860d74db 100644 --- a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts +++ b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts @@ -7,6 +7,11 @@ import { WindowMain } from "../../../main/window.main"; import { CommandDefinition } from "./command"; +type BufferedMessage = { + channel: string; + data: any; +}; + export type RunCommandParams = { namespace: C["namespace"]; command: C["name"]; @@ -16,13 +21,44 @@ export type RunCommandParams = { export type RunCommandResult = C["output"]; export class NativeAutofillMain { - private ipcServer: autofill.IpcServer | null; + private ipcServer?: autofill.AutofillIpcServer; + private messageBuffer: BufferedMessage[] = []; + private listenerReady = false; constructor( private logService: LogService, private windowMain: WindowMain, ) {} + /** + * Safely sends a message to the renderer, buffering it if the server isn't ready yet + */ + private safeSend(channel: string, data: any) { + if (this.listenerReady && this.windowMain.win?.webContents) { + this.windowMain.win.webContents.send(channel, data); + } else { + this.messageBuffer.push({ channel, data }); + } + } + + /** + * Flushes all buffered messages to the renderer + */ + private flushMessageBuffer() { + if (!this.windowMain.win?.webContents) { + this.logService.error("Cannot flush message buffer - window not available"); + return; + } + + this.logService.info(`Flushing ${this.messageBuffer.length} buffered messages`); + + for (const { channel, data } of this.messageBuffer) { + this.windowMain.win.webContents.send(channel, data); + } + + this.messageBuffer = []; + } + async init() { ipcMain.handle( "autofill.runCommand", @@ -34,16 +70,16 @@ export class NativeAutofillMain { }, ); - this.ipcServer = await autofill.IpcServer.listen( + this.ipcServer = await autofill.AutofillIpcServer.listen( "af", // RegistrationCallback (error, clientId, sequenceNumber, request) => { if (error) { this.logService.error("autofill.IpcServer.registration", error); - this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + this.ipcServer?.completeError(clientId, sequenceNumber, String(error)); return; } - this.windowMain.win.webContents.send("autofill.passkeyRegistration", { + this.safeSend("autofill.passkeyRegistration", { clientId, sequenceNumber, request, @@ -53,10 +89,10 @@ export class NativeAutofillMain { (error, clientId, sequenceNumber, request) => { if (error) { this.logService.error("autofill.IpcServer.assertion", error); - this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + this.ipcServer?.completeError(clientId, sequenceNumber, String(error)); return; } - this.windowMain.win.webContents.send("autofill.passkeyAssertion", { + this.safeSend("autofill.passkeyAssertion", { clientId, sequenceNumber, request, @@ -66,33 +102,54 @@ export class NativeAutofillMain { (error, clientId, sequenceNumber, request) => { if (error) { this.logService.error("autofill.IpcServer.assertion", error); - this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + this.ipcServer?.completeError(clientId, sequenceNumber, String(error)); return; } - this.windowMain.win.webContents.send("autofill.passkeyAssertionWithoutUserInterface", { + this.safeSend("autofill.passkeyAssertionWithoutUserInterface", { clientId, sequenceNumber, request, }); }, + // NativeStatusCallback + (error, clientId, sequenceNumber, status) => { + if (error) { + this.logService.error("autofill.IpcServer.nativeStatus", error); + this.ipcServer?.completeError(clientId, sequenceNumber, String(error)); + return; + } + this.safeSend("autofill.nativeStatus", { + clientId, + sequenceNumber, + status, + }); + }, ); + ipcMain.on("autofill.listenerReady", () => { + this.listenerReady = true; + this.logService.info( + `Listener is ready, flushing ${this.messageBuffer.length} buffered messages`, + ); + this.flushMessageBuffer(); + }); + ipcMain.on("autofill.completePasskeyRegistration", (event, data) => { - this.logService.warning("autofill.completePasskeyRegistration", data); + this.logService.debug("autofill.completePasskeyRegistration", data); const { clientId, sequenceNumber, response } = data; - this.ipcServer.completeRegistration(clientId, sequenceNumber, response); + this.ipcServer?.completeRegistration(clientId, sequenceNumber, response); }); ipcMain.on("autofill.completePasskeyAssertion", (event, data) => { - this.logService.warning("autofill.completePasskeyAssertion", data); + this.logService.debug("autofill.completePasskeyAssertion", data); const { clientId, sequenceNumber, response } = data; - this.ipcServer.completeAssertion(clientId, sequenceNumber, response); + this.ipcServer?.completeAssertion(clientId, sequenceNumber, response); }); ipcMain.on("autofill.completeError", (event, data) => { - this.logService.warning("autofill.completeError", data); + this.logService.debug("autofill.completeError", data); const { clientId, sequenceNumber, error } = data; - this.ipcServer.completeError(clientId, sequenceNumber, String(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 0efc9a1efab..ab52531bb5d 100644 --- a/apps/desktop/src/platform/models/domain/window-state.ts +++ b/apps/desktop/src/platform/models/domain/window-state.ts @@ -14,5 +14,6 @@ export class WindowState { export class ModalModeState { isModalModeActive: boolean; + showTrafficButtons?: 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 index 5c5619bd463..6ad00b44171 100644 --- a/apps/desktop/src/platform/popup-modal-styles.ts +++ b/apps/desktop/src/platform/popup-modal-styles.ts @@ -3,15 +3,19 @@ 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; +const popupWidth = 600; +const popupHeight = 600; type Position = { x: number; y: number }; -export function applyPopupModalStyles(window: BrowserWindow, position?: Position) { +export function applyPopupModalStyles( + window: BrowserWindow, + showTrafficButtons: boolean = true, + position?: Position, +) { window.unmaximize(); window.setSize(popupWidth, popupHeight); - window.setWindowButtonVisibility?.(false); + window.setWindowButtonVisibility?.(showTrafficButtons); window.setMenuBarVisibility?.(false); window.setResizable(false); window.setAlwaysOnTop(true); @@ -40,7 +44,7 @@ function positionWindow(window: BrowserWindow, position?: Position) { } export function applyMainWindowStyles(window: BrowserWindow, existingWindowState: WindowState) { - window.setMinimumSize(680, 500); + window.setMinimumSize(popupWidth, popupHeight); // need to guard against null/undefined values diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index 5af2fa571ec..5f643242a9c 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -17,6 +17,7 @@ import { isFlatpak, isMacAppStore, isSnapStore, + isWindowsPortable, isWindowsStore, } from "../utils"; @@ -108,8 +109,13 @@ const ephemeralStore = { }; const localhostCallbackService = { - openSsoPrompt: (codeChallenge: string, state: string, email: string): Promise => { - return ipcRenderer.invoke("openSsoPrompt", { codeChallenge, state, email }); + openSsoPrompt: ( + codeChallenge: string, + state: string, + email: string, + orgSsoIdentifier?: string, + ): Promise => { + return ipcRenderer.invoke("openSsoPrompt", { codeChallenge, state, email, orgSsoIdentifier }); }, }; @@ -128,6 +134,7 @@ export default { isDev: isDev(), isMacAppStore: isMacAppStore(), isWindowsStore: isWindowsStore(), + isWindowsPortable: isWindowsPortable(), isFlatpak: isFlatpak(), isSnapStore: isSnapStore(), isAppImage: isAppImage(), diff --git a/apps/desktop/src/platform/services/desktop-settings.service.ts b/apps/desktop/src/platform/services/desktop-settings.service.ts index c11f10646d7..d7c17433471 100644 --- a/apps/desktop/src/platform/services/desktop-settings.service.ts +++ b/apps/desktop/src/platform/services/desktop-settings.service.ts @@ -335,9 +335,14 @@ export class DesktopSettingsService { * 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 }) { + async setModalMode( + value: boolean, + showTrafficButtons?: boolean, + modalPosition?: { x: number; y: number }, + ) { await this.modalModeState.update(() => ({ isModalModeActive: value, + showTrafficButtons, modalPosition, })); } diff --git a/apps/desktop/src/platform/services/electron-log.main.service.ts b/apps/desktop/src/platform/services/electron-log.main.service.ts index 947f4449271..e148f7a45c8 100644 --- a/apps/desktop/src/platform/services/electron-log.main.service.ts +++ b/apps/desktop/src/platform/services/electron-log.main.service.ts @@ -22,7 +22,7 @@ export class ElectronLogMainService extends BaseLogService { return; } - log.transports.file.level = "info"; + log.transports.file.level = isDev() ? "debug" : "info"; if (this.logDir != null) { log.transports.file.resolvePathFn = () => path.join(this.logDir, "app.log"); } 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 de63c6b28aa..9377ac567ec 100644 --- a/apps/desktop/src/platform/services/electron-platform-utils.service.ts +++ b/apps/desktop/src/platform/services/electron-platform-utils.service.ts @@ -151,4 +151,20 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { getAutofillKeyboardShortcut(): Promise { return null; } + + async packageType(): Promise { + if (ipc.platform.isMacAppStore) { + return "MacAppStore"; + } else if (ipc.platform.isWindowsStore) { + return "WindowsStore"; + } else if (ipc.platform.isAppImage) { + return "AppImage"; + } else if (ipc.platform.isSnapStore) { + return "Snap"; + } else if (ipc.platform.isFlatpak) { + return "Flatpak"; + } else { + return "Unknown"; + } + } } 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 75a84919b07..fdd9bc29237 100644 --- a/apps/desktop/src/platform/services/sso-localhost-callback.service.ts +++ b/apps/desktop/src/platform/services/sso-localhost-callback.service.ts @@ -25,20 +25,25 @@ export class SSOLocalhostCallbackService { private messagingService: MessageSender, private ssoUrlService: SsoUrlService, ) { - ipcMain.handle("openSsoPrompt", async (event, { codeChallenge, state, email }) => { - // Close any existing server before starting new one - if (this.currentServer) { - await this.closeCurrentServer(); - } + ipcMain.handle( + "openSsoPrompt", + async (event, { codeChallenge, state, email, orgSsoIdentifier }) => { + // Close any existing server before starting new one + if (this.currentServer) { + await this.closeCurrentServer(); + } - return this.openSsoPrompt(codeChallenge, state, email).then(({ ssoCode, recvState }) => { - this.messagingService.send("ssoCallback", { - code: ssoCode, - state: recvState, - redirectUri: this.ssoRedirectUri, - }); - }); - }); + return this.openSsoPrompt(codeChallenge, state, email, orgSsoIdentifier).then( + ({ ssoCode, recvState }) => { + this.messagingService.send("ssoCallback", { + code: ssoCode, + state: recvState, + redirectUri: this.ssoRedirectUri, + }); + }, + ); + }, + ); } private async closeCurrentServer(): Promise { @@ -58,6 +63,7 @@ export class SSOLocalhostCallbackService { codeChallenge: string, state: string, email: string, + orgSsoIdentifier?: string, ): Promise<{ ssoCode: string; recvState: string }> { const env = await firstValueFrom(this.environmentService.environment$); @@ -121,6 +127,7 @@ export class SSOLocalhostCallbackService { state, codeChallenge, email, + orgSsoIdentifier, ); // Set up error handler before attempting to listen diff --git a/apps/desktop/src/scss/migration.scss b/apps/desktop/src/scss/migration.scss new file mode 100644 index 00000000000..e3078158283 --- /dev/null +++ b/apps/desktop/src/scss/migration.scss @@ -0,0 +1,15 @@ +/** + * Desktop UI Migration + * + * These are temporary styles during the desktop ui migration. + **/ + +/** + * This removes any padding applied by the bit-layout to content. + * This should be revisited once the table is migrated, and again once drawers are migrated. + **/ +bit-layout { + #main-content { + padding: 0 0 0 0; + } +} diff --git a/apps/desktop/src/scss/styles.scss b/apps/desktop/src/scss/styles.scss index c579e6acdc0..b4082afd38c 100644 --- a/apps/desktop/src/scss/styles.scss +++ b/apps/desktop/src/scss/styles.scss @@ -15,5 +15,6 @@ @import "left-nav.scss"; @import "loading.scss"; @import "plugins.scss"; +@import "migration.scss"; @import "../../../../libs/angular/src/scss/icons.scss"; @import "../../../../libs/components/src/multi-select/scss/bw.theme"; 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 dec1e63d5e8..49d346bfa3a 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.spec.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.spec.ts @@ -503,19 +503,4 @@ describe("BiometricMessageHandlerService", () => { }, ); }); - - describe("init", () => { - it("enables Windows v2 biometrics when feature flag enabled", async () => { - configService.getFeatureFlag.mockReturnValue(true); - - await service.init(); - expect(biometricsService.enableWindowsV2Biometrics).toHaveBeenCalled(); - }); - it("does not enable Windows v2 biometrics when feature flag disabled", async () => { - configService.getFeatureFlag.mockReturnValue(false); - - await service.init(); - expect(biometricsService.enableWindowsV2Biometrics).not.toHaveBeenCalled(); - }); - }); }); diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index 022ccffcc91..ca4ea14c1e0 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -119,13 +119,6 @@ export class BiometricMessageHandlerService { "[BiometricMessageHandlerService] Initializing biometric message handler", ); - const windowsV2Enabled = await this.configService.getFeatureFlag( - FeatureFlag.WindowsBiometricsV2, - ); - if (windowsV2Enabled) { - await this.biometricsService.enableWindowsV2Biometrics(); - } - const linuxV2Enabled = await this.configService.getFeatureFlag(FeatureFlag.LinuxBiometricsV2); if (linuxV2Enabled) { await this.biometricsService.enableLinuxV2Biometrics(); diff --git a/apps/desktop/src/types/biometric-message.ts b/apps/desktop/src/types/biometric-message.ts index 62aa9fb1ce2..7ae20945e96 100644 --- a/apps/desktop/src/types/biometric-message.ts +++ b/apps/desktop/src/types/biometric-message.ts @@ -17,9 +17,6 @@ export enum BiometricAction { EnrollPersistent = "enrollPersistent", HasPersistentKey = "hasPersistentKey", - EnableWindowsV2 = "enableWindowsV2", - IsWindowsV2Enabled = "isWindowsV2Enabled", - EnableLinuxV2 = "enableLinuxV2", IsLinuxV2Enabled = "isLinuxV2Enabled", } diff --git a/apps/desktop/src/vault/app/vault-v3/vault.component.html b/apps/desktop/src/vault/app/vault-v3/vault.component.html new file mode 100644 index 00000000000..a9a25f57994 --- /dev/null +++ b/apps/desktop/src/vault/app/vault-v3/vault.component.html @@ -0,0 +1,70 @@ +
    + + +
    + +
    +
    +
    + + + + + + + +
    +
    +
    +
    + +
    + diff --git a/apps/desktop/src/vault/app/vault-v3/vault.component.ts b/apps/desktop/src/vault/app/vault-v3/vault.component.ts new file mode 100644 index 00000000000..21ba7547f8b --- /dev/null +++ b/apps/desktop/src/vault/app/vault-v3/vault.component.ts @@ -0,0 +1,1020 @@ +import { CommonModule } from "@angular/common"; +import { + ChangeDetectorRef, + Component, + NgZone, + OnDestroy, + OnInit, + ViewChild, + ViewContainerRef, +} from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom, Observable } from "rxjs"; +import { filter, map, take } from "rxjs/operators"; + +import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; +import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/view-password-history.service"; +import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; +import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; +import { ApiService } from "@bitwarden/common/abstractions/api.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 { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { EventType } from "@bitwarden/common/enums"; +import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { 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 { getByIds } from "@bitwarden/common/platform/misc"; +import { SyncService } from "@bitwarden/common/platform/sync"; +import { CipherId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; +import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; +import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; +import { CipherType, toCipherType } from "@bitwarden/common/vault/enums"; +import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + CipherViewLike, + CipherViewLikeUtils, +} from "@bitwarden/common/vault/utils/cipher-view-like-utils"; +import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities"; +import { + BadgeModule, + ButtonModule, + DialogService, + ItemModule, + ToastService, + CopyClickListener, + COPY_CLICK_LISTENER, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { + AddEditFolderDialogComponent, + AddEditFolderDialogResult, + AttachmentDialogResult, + AttachmentsV2Component, + ChangeLoginPasswordService, + CipherFormConfig, + CipherFormConfigService, + CipherFormGenerationService, + CipherFormMode, + CipherFormModule, + CipherViewComponent, + CollectionAssignmentResult, + DecryptionFailureDialogComponent, + DefaultChangeLoginPasswordService, + DefaultCipherFormConfigService, + PasswordRepromptService, + CipherFormComponent, + ArchiveCipherUtilitiesService, +} from "@bitwarden/vault"; + +import { SearchBarService } from "../../../app/layout/search/search-bar.service"; +import { DesktopCredentialGenerationService } from "../../../services/desktop-cipher-form-generator.service"; +import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service"; +import { invokeMenu, RendererMenuItem } from "../../../utils"; +import { AssignCollectionsDesktopComponent } from "../vault/assign-collections"; +import { ItemFooterComponent } from "../vault/item-footer.component"; +import { VaultFilterComponent } from "../vault/vault-filter/vault-filter.component"; +import { VaultFilterModule } from "../vault/vault-filter/vault-filter.module"; +import { VaultItemsV2Component } from "../vault/vault-items-v2.component"; + +const BroadcasterSubscriptionId = "VaultComponent"; + +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +@Component({ + selector: "app-vault-v3", + templateUrl: "vault.component.html", + imports: [ + BadgeModule, + CommonModule, + CipherFormModule, + CipherViewComponent, + ItemFooterComponent, + I18nPipe, + ItemModule, + ButtonModule, + PremiumBadgeComponent, + VaultFilterModule, + VaultItemsV2Component, + ], + providers: [ + { + provide: CipherFormConfigService, + useClass: DefaultCipherFormConfigService, + }, + { + provide: ChangeLoginPasswordService, + useClass: DefaultChangeLoginPasswordService, + }, + { + provide: ViewPasswordHistoryService, + useClass: VaultViewPasswordHistoryService, + }, + { + provide: PremiumUpgradePromptService, + useClass: DesktopPremiumUpgradePromptService, + }, + { provide: CipherFormGenerationService, useClass: DesktopCredentialGenerationService }, + { + provide: COPY_CLICK_LISTENER, + useExisting: VaultComponent, + }, + ], +}) +export class VaultComponent + implements OnInit, OnDestroy, CopyClickListener +{ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals + @ViewChild(VaultItemsV2Component, { static: true }) + vaultItemsComponent: VaultItemsV2Component | null = null; + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals + @ViewChild(VaultFilterComponent, { static: true }) + vaultFilterComponent: VaultFilterComponent | null = null; + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals + @ViewChild("folderAddEdit", { read: ViewContainerRef, static: true }) + folderAddEditModalRef: ViewContainerRef | null = null; + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals + @ViewChild(CipherFormComponent) + cipherFormComponent: CipherFormComponent | null = null; + + action: CipherFormMode | "view" | null = null; + cipherId: string | null = null; + favorites = false; + type: CipherType | null = null; + folderId: string | null = null; + collectionId: string | null = null; + organizationId: string | null = null; + myVaultOnly = false; + addType: CipherType | undefined = undefined; + addOrganizationId: string | null = null; + addCollectionIds: string[] | null = null; + showingModal = false; + deleted = false; + userHasPremiumAccess = false; + activeFilter: VaultFilter = new VaultFilter(); + activeUserId: UserId | null = null; + cipherRepromptId: string | null = null; + cipher: CipherView | null = new CipherView(); + collections: CollectionView[] | null = null; + config: CipherFormConfig | null = null; + + /** Tracks the disabled status of the edit cipher form */ + protected formDisabled: boolean = false; + + private organizations$: Observable = this.accountService.activeAccount$.pipe( + map((a) => a?.id), + filterOutNullish(), + switchMap((id) => this.organizationService.organizations$(id)), + ); + + protected canAccessAttachments$ = this.accountService.activeAccount$.pipe( + filter((account): account is Account => !!account), + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); + + private componentIsDestroyed$ = new Subject(); + private allOrganizations: Organization[] = []; + private allCollections: CollectionView[] = []; + + constructor( + private route: ActivatedRoute, + private router: Router, + private i18nService: I18nService, + private broadcasterService: BroadcasterService, + private changeDetectorRef: ChangeDetectorRef, + private ngZone: NgZone, + private syncService: SyncService, + private messagingService: MessagingService, + private platformUtilsService: PlatformUtilsService, + private eventCollectionService: EventCollectionService, + private totpService: TotpService, + private passwordRepromptService: PasswordRepromptService, + private searchBarService: SearchBarService, + private apiService: ApiService, + private dialogService: DialogService, + private billingAccountProfileStateService: BillingAccountProfileStateService, + private toastService: ToastService, + private accountService: AccountService, + private cipherService: CipherService, + private formConfigService: CipherFormConfigService, + private premiumUpgradePromptService: PremiumUpgradePromptService, + private collectionService: CollectionService, + private organizationService: OrganizationService, + private folderService: FolderService, + private configService: ConfigService, + private authRequestService: AuthRequestServiceAbstraction, + private cipherArchiveService: CipherArchiveService, + private policyService: PolicyService, + private archiveCipherUtilitiesService: ArchiveCipherUtilitiesService, + ) {} + + async ngOnInit() { + this.accountService.activeAccount$ + .pipe( + filter((account): account is Account => !!account), + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + takeUntil(this.componentIsDestroyed$), + ) + .subscribe((canAccessPremium: boolean) => { + this.userHasPremiumAccess = canAccessPremium; + }); + + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone + .run(async () => { + let detectChanges = true; + try { + switch (message.command) { + case "newLogin": + await this.addCipher(CipherType.Login).catch(() => {}); + break; + case "newCard": + await this.addCipher(CipherType.Card).catch(() => {}); + break; + case "newIdentity": + await this.addCipher(CipherType.Identity).catch(() => {}); + break; + case "newSecureNote": + await this.addCipher(CipherType.SecureNote).catch(() => {}); + break; + case "newSshKey": + await this.addCipher(CipherType.SshKey).catch(() => {}); + break; + case "focusSearch": + (document.querySelector("#search") as HTMLInputElement)?.select(); + detectChanges = false; + break; + case "syncCompleted": + if (this.vaultItemsComponent) { + await this.vaultItemsComponent + .reload(this.activeFilter.buildFilter()) + .catch(() => {}); + } + if (this.vaultFilterComponent) { + await this.vaultFilterComponent + .reloadCollectionsAndFolders(this.activeFilter) + .catch(() => {}); + await this.vaultFilterComponent.reloadOrganizations().catch(() => {}); + } + break; + case "modalShown": + this.showingModal = true; + break; + case "modalClosed": + this.showingModal = false; + break; + case "copyUsername": { + if (this.cipher?.login?.username) { + this.copyValue(this.cipher, this.cipher?.login?.username, "username", "Username"); + } + break; + } + case "copyPassword": { + if (this.cipher?.login?.password && this.cipher.viewPassword) { + this.copyValue(this.cipher, this.cipher.login.password, "password", "Password"); + await this.eventCollectionService + .collect(EventType.Cipher_ClientCopiedPassword, this.cipher.id) + .catch(() => {}); + } + break; + } + case "copyTotp": { + if ( + this.cipher?.login?.hasTotp && + (this.cipher.organizationUseTotp || this.userHasPremiumAccess) + ) { + const value = await firstValueFrom( + this.totpService.getCode$(this.cipher.login.totp), + ).catch((): any => null); + if (value) { + this.copyValue(this.cipher, value.code, "verificationCodeTotp", "TOTP"); + } + } + break; + } + default: + detectChanges = false; + break; + } + } catch { + // Ignore errors + } + if (detectChanges) { + this.changeDetectorRef.detectChanges(); + } + }) + .catch(() => {}); + }); + + if (!this.syncService.syncInProgress) { + await this.load().catch(() => {}); + } + + this.searchBarService.setEnabled(true); + this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault")); + + const authRequests = await firstValueFrom( + this.authRequestService.getLatestPendingAuthRequest$()!, + ); + if (authRequests != null) { + this.messagingService.send("openLoginApproval", { + notificationId: authRequests.id, + }); + } + + this.activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getUserId), + ).catch((): any => null); + + if (this.activeUserId) { + this.cipherService + .failedToDecryptCiphers$(this.activeUserId) + .pipe( + map((ciphers) => ciphers?.filter((c) => !c.isDeleted) ?? []), + filter((ciphers) => ciphers.length > 0), + take(1), + takeUntil(this.componentIsDestroyed$), + ) + .subscribe((ciphers) => { + DecryptionFailureDialogComponent.open(this.dialogService, { + cipherIds: ciphers.map((c) => c.id as CipherId), + }); + }); + } + + this.organizations$.pipe(takeUntil(this.componentIsDestroyed$)).subscribe((orgs) => { + this.allOrganizations = orgs; + }); + + if (!this.activeUserId) { + throw new Error("No user found."); + } + + this.collectionService + .decryptedCollections$(this.activeUserId) + .pipe(takeUntil(this.componentIsDestroyed$)) + .subscribe((collections) => { + this.allCollections = collections; + }); + } + + ngOnDestroy() { + this.searchBarService.setEnabled(false); + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + this.componentIsDestroyed$.next(true); + this.componentIsDestroyed$.complete(); + } + + async load() { + const params = await firstValueFrom(this.route.queryParams).catch(); + const paramCipherAddType = toCipherType(params.addType); + if (params.cipherId) { + const cipherView = new CipherView(); + cipherView.id = params.cipherId; + if (params.action === "clone") { + await this.cloneCipher(cipherView).catch(() => {}); + } else if (params.action === "edit") { + await this.editCipher(cipherView).catch(() => {}); + } else { + await this.viewCipher(cipherView).catch(() => {}); + } + } else if (params.action === "add" && paramCipherAddType) { + this.addType = paramCipherAddType; + await this.addCipher(this.addType).catch(() => {}); + } + + const paramCipherType = toCipherType(params.type); + this.activeFilter = new VaultFilter({ + status: params.deleted ? "trash" : params.favorites ? "favorites" : "all", + cipherType: params.action === "add" || paramCipherType == null ? undefined : paramCipherType, + selectedFolderId: params.folderId, + selectedCollectionId: params.selectedCollectionId, + selectedOrganizationId: params.selectedOrganizationId, + myVaultOnly: params.myVaultOnly ?? false, + }); + if (this.vaultItemsComponent) { + await this.vaultItemsComponent.reload(this.activeFilter.buildFilter()).catch(() => {}); + } + } + + /** + * Handler for Vault level CopyClickDirectives to send the minimizeOnCopy message + */ + onCopy() { + this.messagingService.send("minimizeOnCopy"); + } + + async viewCipher(c: CipherViewLike) { + if (CipherViewLikeUtils.decryptionFailure(c)) { + DecryptionFailureDialogComponent.open(this.dialogService, { + cipherIds: [c.id as CipherId], + }); + return; + } + const cipher = await this.cipherService.getFullCipherView(c); + if (await this.shouldReprompt(cipher, "view")) { + return; + } + this.cipherId = cipher.id; + this.cipher = cipher; + this.collections = + this.vaultFilterComponent?.collections?.fullList.filter((c) => + cipher.collectionIds.includes(c.id), + ) ?? null; + this.action = "view"; + + await this.go().catch(() => {}); + await this.eventCollectionService.collect( + EventType.Cipher_ClientViewed, + cipher.id, + false, + cipher.organizationId, + ); + } + + formStatusChanged(status: "disabled" | "enabled") { + this.formDisabled = status === "disabled"; + } + + async openAttachmentsDialog() { + if (!this.userHasPremiumAccess) { + return; + } + const dialogRef = AttachmentsV2Component.open(this.dialogService, { + cipherId: this.cipherId as CipherId, + }); + const result = await firstValueFrom(dialogRef.closed).catch((): any => null); + if ( + result?.action === AttachmentDialogResult.Removed || + result?.action === AttachmentDialogResult.Uploaded + ) { + await this.vaultItemsComponent?.refresh().catch(() => {}); + + if (this.cipherFormComponent == null) { + return; + } + + // The encrypted state of ciphers is updated when an attachment is added, + // but the cache is also cleared. Depending on timing, `cipherService.get` can return the + // old cipher. Retrieve the updated cipher from `cipherViews$`, + // which refreshes after the cached is cleared. + const updatedCipherView = await firstValueFrom( + this.cipherService.cipherViews$(this.activeUserId!).pipe( + filter((c) => !!c), + map((ciphers) => ciphers.find((c) => c.id === this.cipherId)), + ), + ); + + // `find` can return undefined but that shouldn't happen as + // this would mean that the cipher was deleted. + // To make TypeScript happy, exit early if it isn't found. + if (!updatedCipherView) { + return; + } + + this.cipherFormComponent.patchCipher((currentCipher) => { + currentCipher.attachments = updatedCipherView.attachments; + currentCipher.revisionDate = updatedCipherView.revisionDate; + + return currentCipher; + }); + } + } + + async viewCipherMenu(c: CipherViewLike) { + const cipher = await this.cipherService.getFullCipherView(c); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const userCanArchive = await firstValueFrom(this.cipherArchiveService.userCanArchive$(userId)); + const orgOwnershipPolicy = await firstValueFrom( + this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId), + ); + + const menu: RendererMenuItem[] = [ + { + label: this.i18nService.t("view"), + click: () => { + this.functionWithChangeDetection(() => { + this.viewCipher(cipher).catch(() => {}); + }); + }, + }, + ]; + + if (cipher.decryptionFailure) { + invokeMenu(menu); + } + + if (!cipher.isDeleted) { + menu.push({ + label: this.i18nService.t("edit"), + click: () => { + this.functionWithChangeDetection(() => { + this.editCipher(cipher).catch(() => {}); + }); + }, + }); + + const archivedWithOrgOwnership = cipher.isArchived && orgOwnershipPolicy; + const canCloneArchived = !cipher.isArchived || userCanArchive; + + if (!cipher.organizationId && !archivedWithOrgOwnership && canCloneArchived) { + menu.push({ + label: this.i18nService.t("clone"), + click: () => { + this.functionWithChangeDetection(() => { + this.cloneCipher(cipher).catch(() => {}); + }); + }, + }); + } + + const hasEditableCollections = this.allCollections.some((collection) => !collection.readOnly); + + if (cipher.canAssignToCollections && hasEditableCollections) { + menu.push({ + label: this.i18nService.t("assignToCollections"), + click: () => + this.functionWithChangeDetection(async () => { + await this.shareCipher(cipher); + }), + }); + } + } + + if (!cipher.organizationId && !cipher.isDeleted && !cipher.isArchived) { + menu.push({ + label: this.i18nService.t("archiveVerb"), + click: async () => { + if (!userCanArchive) { + await this.premiumUpgradePromptService.promptForPremium(); + return; + } + + await this.archiveCipherUtilitiesService.archiveCipher(cipher); + await this.refreshCurrentCipher(); + }, + }); + } + + if (cipher.isArchived) { + menu.push({ + label: this.i18nService.t("unArchive"), + click: async () => { + await this.archiveCipherUtilitiesService.unarchiveCipher(cipher); + await this.refreshCurrentCipher(); + }, + }); + } + + switch (cipher.type) { + case CipherType.Login: + if ( + cipher.login.canLaunch || + cipher.login.username != null || + cipher.login.password != null + ) { + menu.push({ type: "separator" }); + } + if (cipher.login.canLaunch) { + menu.push({ + label: this.i18nService.t("launch"), + click: () => this.platformUtilsService.launchUri(cipher.login.launchUri), + }); + } + if (cipher.login.username != null) { + menu.push({ + label: this.i18nService.t("copyUsername"), + click: () => this.copyValue(cipher, cipher.login.username, "username", "Username"), + }); + } + if (cipher.login.password != null && cipher.viewPassword) { + menu.push({ + label: this.i18nService.t("copyPassword"), + click: () => { + this.copyValue(cipher, cipher.login.password, "password", "Password"); + this.eventCollectionService + .collect(EventType.Cipher_ClientCopiedPassword, cipher.id) + .catch(() => {}); + }, + }); + } + if (cipher.login.hasTotp && (cipher.organizationUseTotp || this.userHasPremiumAccess)) { + menu.push({ + label: this.i18nService.t("copyVerificationCodeTotp"), + click: async () => { + const value = await firstValueFrom( + this.totpService.getCode$(cipher.login.totp), + ).catch((): any => null); + if (value) { + this.copyValue(cipher, value.code, "verificationCodeTotp", "TOTP"); + } + }, + }); + } + break; + case CipherType.Card: + if (cipher.card.number != null || cipher.card.code != null) { + menu.push({ type: "separator" }); + } + if (cipher.card.number != null) { + menu.push({ + label: this.i18nService.t("copyNumber"), + click: () => this.copyValue(cipher, cipher.card.number, "number", "Card Number"), + }); + } + if (cipher.card.code != null) { + menu.push({ + label: this.i18nService.t("copySecurityCode"), + click: () => { + this.copyValue(cipher, cipher.card.code, "securityCode", "Security Code"); + this.eventCollectionService + .collect(EventType.Cipher_ClientCopiedCardCode, cipher.id) + .catch(() => {}); + }, + }); + } + break; + default: + break; + } + invokeMenu(menu); + } + + async shouldReprompt(cipher: CipherView, action: "edit" | "clone" | "view"): Promise { + return !(await this.canNavigateAway(action, cipher)) || !(await this.passwordReprompt(cipher)); + } + + async buildFormConfig(action: CipherFormMode) { + this.config = await this.formConfigService + .buildConfig(action, this.cipherId as CipherId, this.addType) + .catch((): any => null); + } + + async editCipher(cipher: CipherView) { + if (await this.shouldReprompt(cipher, "edit")) { + return; + } + this.cipherId = cipher.id; + this.cipher = cipher; + await this.buildFormConfig("edit"); + if (!cipher.edit && this.config) { + this.config.mode = "partial-edit"; + } + this.action = "edit"; + await this.go().catch(() => {}); + } + + async cloneCipher(cipher: CipherView) { + if (await this.shouldReprompt(cipher, "clone")) { + return; + } + this.cipherId = cipher.id; + this.cipher = cipher; + await this.buildFormConfig("clone"); + this.action = "clone"; + await this.go().catch(() => {}); + } + + async shareCipher(cipher: CipherView) { + if (!cipher) { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("nothingSelected"), + }); + return; + } + + if (!(await this.passwordReprompt(cipher))) { + return; + } + + const availableCollections = this.getAvailableCollections(cipher); + + const dialog = AssignCollectionsDesktopComponent.open(this.dialogService, { + data: { + ciphers: [cipher], + organizationId: cipher.organizationId as OrganizationId, + availableCollections, + }, + }); + + const result = await lastValueFrom(dialog.closed); + if (result === CollectionAssignmentResult.Saved) { + const updatedCipher = await firstValueFrom( + // Fetch the updated cipher from the service + this.cipherService.cipherViews$(this.activeUserId as UserId).pipe( + filter((ciphers) => ciphers != null), + map((ciphers) => ciphers!.find((c) => c.id === cipher.id)), + filter((foundCipher) => foundCipher != null), + ), + ); + await this.savedCipher(updatedCipher); + } + } + + async addCipher(type: CipherType) { + if (this.action === "add") { + return; + } + this.addType = type || this.activeFilter.cipherType; + this.cipher = new CipherView(); + this.cipherId = null; + await this.buildFormConfig("add"); + this.action = "add"; + this.prefillCipherFromFilter(); + await this.go().catch(() => {}); + + if (type === CipherType.SshKey) { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("sshKeyGenerated"), + }); + } + } + + async savedCipher(cipher: CipherView) { + this.cipherId = null; + this.action = "view"; + await this.vaultItemsComponent?.refresh().catch(() => {}); + + if (!this.activeUserId) { + throw new Error("No userId provided."); + } + + this.collections = await firstValueFrom( + this.collectionService + .decryptedCollections$(this.activeUserId) + .pipe(getByIds(cipher.collectionIds)), + ); + + this.cipherId = cipher.id; + this.cipher = cipher; + await this.go().catch(() => {}); + await this.vaultItemsComponent?.refresh().catch(() => {}); + } + + async deleteCipher() { + this.cipherId = null; + this.cipher = null; + this.action = null; + await this.go().catch(() => {}); + await this.vaultItemsComponent?.refresh().catch(() => {}); + } + + async restoreCipher() { + this.cipherId = null; + this.action = null; + await this.go().catch(() => {}); + await this.vaultItemsComponent?.refresh().catch(() => {}); + } + + async cancelCipher(cipher: CipherView) { + this.cipherId = cipher.id; + this.cipher = cipher; + this.action = this.cipherId ? "view" : null; + await this.go().catch(() => {}); + } + + async applyVaultFilter(vaultFilter: VaultFilter) { + this.searchBarService.setPlaceholderText( + this.i18nService.t(this.calculateSearchBarLocalizationString(vaultFilter)), + ); + this.activeFilter = vaultFilter; + await this.vaultItemsComponent + ?.reload( + this.activeFilter.buildFilter(), + vaultFilter.status === "trash", + vaultFilter.status === "archive", + ) + .catch(() => {}); + await this.go().catch(() => {}); + } + + private getAvailableCollections(cipher: CipherView): CollectionView[] { + const orgId = cipher.organizationId; + if (!orgId || orgId === "MyVault") { + return []; + } + + const organization = this.allOrganizations.find((o) => o.id === orgId); + return this.allCollections.filter((c) => c.organizationId === organization?.id && !c.readOnly); + } + + private calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string { + if (vaultFilter.status === "favorites") { + return "searchFavorites"; + } + if (vaultFilter.status === "trash") { + return "searchTrash"; + } + if (vaultFilter.cipherType != null) { + return "searchType"; + } + if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId !== "none") { + return "searchFolder"; + } + if (vaultFilter.selectedCollectionId != null) { + return "searchCollection"; + } + if (vaultFilter.selectedOrganizationId != null) { + return "searchOrganization"; + } + if (vaultFilter.myVaultOnly) { + return "searchMyVault"; + } + return "searchVault"; + } + + async addFolder() { + this.messagingService.send("newFolder"); + } + + async editFolder(folderId: string) { + if (!this.activeUserId) { + return; + } + const folderView = await firstValueFrom( + this.folderService.getDecrypted$(folderId, this.activeUserId), + ); + + if (!folderView) { + return; + } + + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, { + editFolderConfig: { + folder: { + ...folderView, + }, + }, + }); + + const result = await lastValueFrom(dialogRef.closed); + + if ( + result === AddEditFolderDialogResult.Deleted || + result === AddEditFolderDialogResult.Created + ) { + await this.vaultFilterComponent?.reloadCollectionsAndFolders(this.activeFilter); + } + } + + /** Refresh the current cipher object */ + protected async refreshCurrentCipher() { + if (!this.cipher) { + return; + } + + this.cipher = await firstValueFrom( + this.cipherService.cipherViews$(this.activeUserId!).pipe( + filter((c) => !!c), + map((ciphers) => ciphers.find((c) => c.id === this.cipherId) ?? null), + ), + ); + } + + private dirtyInput(): boolean { + return ( + (this.action === "add" || this.action === "edit" || this.action === "clone") && + document.querySelectorAll("vault-cipher-form .ng-dirty").length > 0 + ); + } + + private async wantsToSaveChanges(): Promise { + const confirmed = await this.dialogService + .openSimpleDialog({ + title: { key: "unsavedChangesTitle" }, + content: { key: "unsavedChangesConfirmation" }, + type: "warning", + }) + .catch(() => false); + return !confirmed; + } + + private async go(queryParams: any = null) { + if (queryParams == null) { + queryParams = { + action: this.action, + cipherId: this.cipherId, + favorites: this.favorites ? true : null, + type: this.type, + folderId: this.folderId, + collectionId: this.collectionId, + deleted: this.deleted ? true : null, + organizationId: this.organizationId, + myVaultOnly: this.myVaultOnly, + }; + } + this.router + .navigate([], { + relativeTo: this.route, + queryParams: queryParams, + replaceUrl: true, + }) + .catch(() => {}); + } + + private copyValue(cipher: CipherView, value: string, labelI18nKey: string, aType: string) { + this.functionWithChangeDetection(() => { + (async () => { + if ( + cipher.reprompt !== CipherRepromptType.None && + this.passwordRepromptService.protectedFields().includes(aType) && + !(await this.passwordReprompt(cipher)) + ) { + return; + } + this.platformUtilsService.copyToClipboard(value); + this.toastService.showToast({ + variant: "info", + title: undefined, + message: this.i18nService.t("valueCopied", this.i18nService.t(labelI18nKey)), + }); + this.messagingService.send("minimizeOnCopy"); + })().catch(() => {}); + }); + } + + private functionWithChangeDetection(func: () => void) { + this.ngZone.run(() => { + func(); + this.changeDetectorRef.detectChanges(); + }); + } + + private prefillCipherFromFilter() { + if (this.activeFilter.selectedCollectionId != null && this.vaultFilterComponent != null) { + const collections = this.vaultFilterComponent.collections?.fullList.filter( + (c) => c.id === this.activeFilter.selectedCollectionId, + ); + if (collections.length > 0) { + this.addOrganizationId = collections[0].organizationId; + this.addCollectionIds = [this.activeFilter.selectedCollectionId]; + } + } else if (this.activeFilter.selectedOrganizationId) { + this.addOrganizationId = this.activeFilter.selectedOrganizationId; + } else { + // clear out organizationId when the user switches to a personal vault filter + this.addOrganizationId = null; + } + if (this.activeFilter.selectedFolderId && this.activeFilter.selectedFolder) { + this.folderId = this.activeFilter.selectedFolderId; + } + + if (this.config == null) { + return; + } + + this.config.initialValues = { + ...this.config.initialValues, + organizationId: this.addOrganizationId as OrganizationId, + }; + } + + private async canNavigateAway(action: string, cipher?: CipherView) { + if (this.action === action && (!cipher || this.cipherId === cipher.id)) { + return false; + } else if (this.dirtyInput() && (await this.wantsToSaveChanges())) { + return false; + } + return true; + } + + private async passwordReprompt(cipher: CipherView) { + if (cipher.reprompt === CipherRepromptType.None) { + this.cipherRepromptId = null; + return true; + } + if (this.cipherRepromptId === cipher.id) { + return true; + } + const repromptResult = await this.passwordRepromptService.showPasswordPrompt(); + if (repromptResult) { + this.cipherRepromptId = cipher.id; + } + return repromptResult; + } +} diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.ts b/apps/desktop/src/vault/app/vault/item-footer.component.ts index 0034bd9a43c..0ac12c928f2 100644 --- a/apps/desktop/src/vault/app/vault/item-footer.component.ts +++ b/apps/desktop/src/vault/app/vault/item-footer.component.ts @@ -225,7 +225,7 @@ export class ItemFooterComponent implements OnInit, OnChanges { switchMap((id) => combineLatest([ this.cipherArchiveService.userCanArchive$(id), - this.cipherArchiveService.hasArchiveFlagEnabled$(), + this.cipherArchiveService.hasArchiveFlagEnabled$, ]), ), ), diff --git a/apps/web/package.json b/apps/web/package.json index c2db376e5da..344a78f2a2c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.11.3", + "version": "2025.12.0", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/apps/web/src/app/admin-console/common/people-table-data-source.spec.ts b/apps/web/src/app/admin-console/common/people-table-data-source.spec.ts new file mode 100644 index 00000000000..e9cf87a114d --- /dev/null +++ b/apps/web/src/app/admin-console/common/people-table-data-source.spec.ts @@ -0,0 +1,130 @@ +import { TestBed } from "@angular/core/testing"; +import { ReplaySubject } from "rxjs"; + +import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { + Environment, + EnvironmentService, +} from "@bitwarden/common/platform/abstractions/environment.service"; + +import { PeopleTableDataSource } from "./people-table-data-source"; + +interface MockUser { + id: string; + name: string; + email: string; + status: OrganizationUserStatusType; + checked?: boolean; +} + +class TestPeopleTableDataSource extends PeopleTableDataSource { + protected statusType = OrganizationUserStatusType; +} + +describe("PeopleTableDataSource", () => { + let dataSource: TestPeopleTableDataSource; + + const createMockUser = (id: string, checked: boolean = false): MockUser => ({ + id, + name: `User ${id}`, + email: `user${id}@example.com`, + status: OrganizationUserStatusType.Confirmed, + checked, + }); + + const createMockUsers = (count: number, checked: boolean = false): MockUser[] => { + return Array.from({ length: count }, (_, i) => createMockUser(`${i + 1}`, checked)); + }; + + beforeEach(() => { + const featureFlagSubject = new ReplaySubject(1); + featureFlagSubject.next(false); + + const environmentSubject = new ReplaySubject(1); + environmentSubject.next({ + isCloud: () => false, + } as Environment); + + const mockConfigService = { + getFeatureFlag$: jest.fn(() => featureFlagSubject.asObservable()), + } as any; + + const mockEnvironmentService = { + environment$: environmentSubject.asObservable(), + } as any; + + TestBed.configureTestingModule({ + providers: [ + { provide: ConfigService, useValue: mockConfigService }, + { provide: EnvironmentService, useValue: mockEnvironmentService }, + ], + }); + + dataSource = TestBed.runInInjectionContext( + () => new TestPeopleTableDataSource(mockConfigService, mockEnvironmentService), + ); + }); + + describe("limitAndUncheckExcess", () => { + it("should return all users when under limit", () => { + const users = createMockUsers(10, true); + dataSource.data = users; + + const result = dataSource.limitAndUncheckExcess(users, 500); + + expect(result).toHaveLength(10); + expect(result).toEqual(users); + expect(users.every((u) => u.checked)).toBe(true); + }); + + it("should limit users and uncheck excess", () => { + const users = createMockUsers(600, true); + dataSource.data = users; + + const result = dataSource.limitAndUncheckExcess(users, 500); + + expect(result).toHaveLength(500); + expect(result).toEqual(users.slice(0, 500)); + expect(users.slice(0, 500).every((u) => u.checked)).toBe(true); + expect(users.slice(500).every((u) => u.checked)).toBe(false); + }); + + it("should only affect users in the provided array", () => { + const allUsers = createMockUsers(1000, true); + dataSource.data = allUsers; + + // Pass only a subset (simulates filtering by status) + const subset = allUsers.slice(0, 600); + + const result = dataSource.limitAndUncheckExcess(subset, 500); + + expect(result).toHaveLength(500); + expect(subset.slice(0, 500).every((u) => u.checked)).toBe(true); + expect(subset.slice(500).every((u) => u.checked)).toBe(false); + // Users outside subset remain checked + expect(allUsers.slice(600).every((u) => u.checked)).toBe(true); + }); + }); + + describe("status counts", () => { + it("should correctly count users by status", () => { + const users: MockUser[] = [ + { ...createMockUser("1"), status: OrganizationUserStatusType.Invited }, + { ...createMockUser("2"), status: OrganizationUserStatusType.Invited }, + { ...createMockUser("3"), status: OrganizationUserStatusType.Accepted }, + { ...createMockUser("4"), status: OrganizationUserStatusType.Confirmed }, + { ...createMockUser("5"), status: OrganizationUserStatusType.Confirmed }, + { ...createMockUser("6"), status: OrganizationUserStatusType.Confirmed }, + { ...createMockUser("7"), status: OrganizationUserStatusType.Revoked }, + ]; + dataSource.data = users; + + expect(dataSource.invitedUserCount).toBe(2); + expect(dataSource.acceptedUserCount).toBe(1); + expect(dataSource.confirmedUserCount).toBe(3); + expect(dataSource.revokedUserCount).toBe(1); + expect(dataSource.activeUserCount).toBe(6); // All except revoked + }); + }); +}); diff --git a/apps/web/src/app/admin-console/common/people-table-data-source.ts b/apps/web/src/app/admin-console/common/people-table-data-source.ts index 4696f8a6738..9ac370d8c0d 100644 --- a/apps/web/src/app/admin-console/common/people-table-data-source.ts +++ b/apps/web/src/app/admin-console/common/people-table-data-source.ts @@ -1,14 +1,30 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { computed, Signal } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { map } from "rxjs"; + import { OrganizationUserStatusType, ProviderUserStatusType, } 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 { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { TableDataSource } from "@bitwarden/components"; import { StatusType, UserViewTypes } from "./base-members.component"; -const MaxCheckedCount = 500; +/** + * Default maximum for most bulk operations (confirm, remove, delete, etc.) + */ +export const MaxCheckedCount = 500; + +/** + * Maximum for bulk reinvite operations when the IncreaseBulkReinviteLimitForCloud + * feature flag is enabled on cloud environments. + */ +export const CloudBulkReinviteLimit = 8000; /** * Returns true if the user matches the status, or where the status is `null`, if the user is active (not revoked). @@ -56,6 +72,20 @@ export abstract class PeopleTableDataSource extends Tab confirmedUserCount: number; revokedUserCount: number; + /** True when increased bulk limit feature is enabled (feature flag + cloud environment) */ + readonly isIncreasedBulkLimitEnabled: Signal; + + constructor(configService: ConfigService, environmentService: EnvironmentService) { + super(); + + const featureFlagEnabled = toSignal( + configService.getFeatureFlag$(FeatureFlag.IncreaseBulkReinviteLimitForCloud), + ); + const isCloud = toSignal(environmentService.environment$.pipe(map((env) => env.isCloud()))); + + this.isIncreasedBulkLimitEnabled = computed(() => featureFlagEnabled() && isCloud()); + } + override set data(data: T[]) { super.data = data; @@ -89,6 +119,14 @@ export abstract class PeopleTableDataSource extends Tab return this.data.filter((u) => (u as any).checked); } + /** + * Gets checked users in the order they appear in the filtered/sorted table view. + * Use this when enforcing limits to ensure visual consistency (top N visible rows stay checked). + */ + getCheckedUsersInVisibleOrder() { + return this.filteredData.filter((u) => (u as any).checked); + } + /** * Check all filtered users (i.e. those rows that are currently visible) * @param select check the filtered users (true) or uncheck the filtered users (false) @@ -101,8 +139,13 @@ export abstract class PeopleTableDataSource extends Tab const filteredUsers = this.filteredData; - const selectCount = - filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length; + // When the increased bulk limit feature is enabled, allow checking all users. + // Individual bulk operations will enforce their specific limits. + // When disabled, enforce the legacy limit at check time. + const selectCount = this.isIncreasedBulkLimitEnabled() + ? filteredUsers.length + : Math.min(filteredUsers.length, MaxCheckedCount); + for (let i = 0; i < selectCount; i++) { this.checkUser(filteredUsers[i], select); } @@ -132,4 +175,41 @@ export abstract class PeopleTableDataSource extends Tab this.data = updatedData; } } + + /** + * Limits an array of users and unchecks those beyond the limit. + * Returns the limited array. + * + * @param users The array of users to limit + * @param limit The maximum number of users to keep + * @returns The users array limited to the specified count + */ + limitAndUncheckExcess(users: T[], limit: number): T[] { + if (users.length <= limit) { + return users; + } + + // Uncheck users beyond the limit + users.slice(limit).forEach((user) => this.checkUser(user, false)); + + return users.slice(0, limit); + } + + /** + * Gets checked users with optional limiting based on the IncreaseBulkReinviteLimitForCloud feature flag. + * + * When the feature flag is enabled: Returns checked users in visible order, limited to the specified count. + * When the feature flag is disabled: Returns all checked users without applying any limit. + * + * @param limit The maximum number of users to return (only applied when feature flag is enabled) + * @returns The checked users array + */ + getCheckedUsersWithLimit(limit: number): T[] { + if (this.isIncreasedBulkLimitEnabled()) { + const allUsers = this.getCheckedUsersInVisibleOrder(); + return this.limitAndUncheckExcess(allUsers, limit); + } else { + return this.getCheckedUsers(); + } + } } diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts index 01e61f0ab28..a253bb87c50 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts @@ -11,6 +11,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -59,6 +60,7 @@ export class VaultFilterComponent protected restrictedItemTypesService: RestrictedItemTypesService, protected cipherService: CipherService, protected cipherArchiveService: CipherArchiveService, + premiumUpgradePromptService: PremiumUpgradePromptService, ) { super( vaultFilterService, @@ -72,6 +74,7 @@ export class VaultFilterComponent restrictedItemTypesService, cipherService, cipherArchiveService, + premiumUpgradePromptService, ); } 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 accb5f77fdc..59bc03babd4 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -2,12 +2,15 @@ - + + @if (canShowAccessIntelligenceTab(organization)) { + + } + userType = OrganizationUserType; userStatusType = OrganizationUserStatusType; memberTab = MemberDialogTab; - protected dataSource = new MembersTableDataSource(); + protected dataSource: MembersTableDataSource; readonly organization: Signal; status: OrganizationUserStatusType | undefined; @@ -113,6 +119,8 @@ export class MembersComponent extends BaseMembersComponent private policyService: PolicyService, private policyApiService: PolicyApiServiceAbstraction, private organizationMetadataService: OrganizationMetadataServiceAbstraction, + private configService: ConfigService, + private environmentService: EnvironmentService, ) { super( apiService, @@ -126,6 +134,8 @@ export class MembersComponent extends BaseMembersComponent toastService, ); + this.dataSource = new MembersTableDataSource(this.configService, this.environmentService); + const organization$ = this.route.params.pipe( concatMap((params) => this.userId$.pipe( @@ -356,10 +366,9 @@ export class MembersComponent extends BaseMembersComponent return; } - await this.memberDialogManager.openBulkRemoveDialog( - organization, - this.dataSource.getCheckedUsers(), - ); + const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); + + await this.memberDialogManager.openBulkRemoveDialog(organization, users); this.organizationMetadataService.refreshMetadataCache(); await this.load(organization); } @@ -369,10 +378,9 @@ export class MembersComponent extends BaseMembersComponent return; } - await this.memberDialogManager.openBulkDeleteDialog( - organization, - this.dataSource.getCheckedUsers(), - ); + const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); + + await this.memberDialogManager.openBulkDeleteDialog(organization, users); await this.load(organization); } @@ -389,11 +397,9 @@ export class MembersComponent extends BaseMembersComponent return; } - await this.memberDialogManager.openBulkRestoreRevokeDialog( - organization, - this.dataSource.getCheckedUsers(), - isRevoking, - ); + const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); + + await this.memberDialogManager.openBulkRestoreRevokeDialog(organization, users, isRevoking); await this.load(organization); } @@ -402,8 +408,28 @@ export class MembersComponent extends BaseMembersComponent return; } - const users = this.dataSource.getCheckedUsers(); - const filteredUsers = users.filter((u) => u.status === OrganizationUserStatusType.Invited); + let users: OrganizationUserView[]; + if (this.dataSource.isIncreasedBulkLimitEnabled()) { + users = this.dataSource.getCheckedUsersInVisibleOrder(); + } else { + users = this.dataSource.getCheckedUsers(); + } + + const allInvitedUsers = users.filter((u) => u.status === OrganizationUserStatusType.Invited); + + // Capture the original count BEFORE enforcing the limit + const originalInvitedCount = allInvitedUsers.length; + + // When feature flag is enabled, limit invited users and uncheck the excess + let filteredUsers: OrganizationUserView[]; + if (this.dataSource.isIncreasedBulkLimitEnabled()) { + filteredUsers = this.dataSource.limitAndUncheckExcess( + allInvitedUsers, + CloudBulkReinviteLimit, + ); + } else { + filteredUsers = allInvitedUsers; + } if (filteredUsers.length <= 0) { this.toastService.showToast({ @@ -417,20 +443,44 @@ export class MembersComponent extends BaseMembersComponent try { const result = await this.memberActionsService.bulkReinvite( organization, - filteredUsers.map((user) => user.id), + filteredUsers.map((user) => user.id as UserId), ); if (!result.successful) { throw new Error(); } - // Bulk Status component open - await this.memberDialogManager.openBulkStatusDialog( - users, - filteredUsers, - Promise.resolve(result.successful), - this.i18nService.t("bulkReinviteMessage"), - ); + // When feature flag is enabled, show toast instead of dialog + if (this.dataSource.isIncreasedBulkLimitEnabled()) { + const selectedCount = originalInvitedCount; + const invitedCount = filteredUsers.length; + + if (selectedCount > CloudBulkReinviteLimit) { + const excludedCount = selectedCount - CloudBulkReinviteLimit; + this.toastService.showToast({ + variant: "success", + message: this.i18nService.t( + "bulkReinviteLimitedSuccessToast", + CloudBulkReinviteLimit.toLocaleString(), + selectedCount.toLocaleString(), + excludedCount.toLocaleString(), + ), + }); + } else { + this.toastService.showToast({ + variant: "success", + message: this.i18nService.t("bulkReinviteSuccessToast", invitedCount.toString()), + }); + } + } else { + // Feature flag disabled - show legacy dialog + await this.memberDialogManager.openBulkStatusDialog( + users, + filteredUsers, + Promise.resolve(result.successful), + this.i18nService.t("bulkReinviteMessage"), + ); + } } catch (e) { this.validationService.showError(e); } @@ -442,15 +492,14 @@ export class MembersComponent extends BaseMembersComponent return; } - await this.memberDialogManager.openBulkConfirmDialog( - organization, - this.dataSource.getCheckedUsers(), - ); + const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); + + await this.memberDialogManager.openBulkConfirmDialog(organization, users); await this.load(organization); } async bulkEnableSM(organization: Organization) { - const users = this.dataSource.getCheckedUsers(); + const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); await this.memberDialogManager.openBulkEnableSecretsManagerDialog(organization, users); diff --git a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts index e856ab7afd1..80a330b0db1 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts @@ -4,6 +4,7 @@ import { of } from "rxjs"; import { OrganizationUserApiService, OrganizationUserBulkResponse, + OrganizationUserService, } from "@bitwarden/admin-console/common"; import { OrganizationUserType, @@ -22,9 +23,8 @@ import { newGuid } from "@bitwarden/guid"; import { KeyService } from "@bitwarden/key-management"; import { OrganizationUserView } from "../../../core/views/organization-user.view"; -import { OrganizationUserService } from "../organization-user/organization-user.service"; -import { MemberActionsService } from "./member-actions.service"; +import { REQUESTS_PER_BATCH, MemberActionsService } from "./member-actions.service"; describe("MemberActionsService", () => { let service: MemberActionsService; @@ -308,41 +308,308 @@ describe("MemberActionsService", () => { }); describe("bulkReinvite", () => { - const userIds = [newGuid(), newGuid(), newGuid()]; + const userIds = [newGuid() as UserId, newGuid() as UserId, newGuid() as UserId]; - it("should successfully reinvite multiple users", async () => { - const mockResponse = { - data: userIds.map((id) => ({ - id, - error: null, - })), - continuationToken: null, - } as ListResponse; - organizationUserApiService.postManyOrganizationUserReinvite.mockResolvedValue(mockResponse); - - const result = await service.bulkReinvite(mockOrganization, userIds); - - expect(result).toEqual({ - successful: mockResponse, - failed: [], + describe("when feature flag is false", () => { + beforeEach(() => { + configService.getFeatureFlag$.mockReturnValue(of(false)); + }); + + it("should successfully reinvite multiple users", async () => { + const mockResponse = new ListResponse( + { + data: userIds.map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + organizationUserApiService.postManyOrganizationUserReinvite.mockResolvedValue(mockResponse); + + const result = await service.bulkReinvite(mockOrganization, userIds); + + expect(result.failed).toEqual([]); + expect(result.successful).toBeDefined(); + expect(result.successful).toEqual(mockResponse); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledWith( + organizationId, + userIds, + ); + }); + + it("should handle bulk reinvite errors", async () => { + const errorMessage = "Bulk reinvite failed"; + organizationUserApiService.postManyOrganizationUserReinvite.mockRejectedValue( + new Error(errorMessage), + ); + + const result = await service.bulkReinvite(mockOrganization, userIds); + + expect(result.successful).toBeUndefined(); + expect(result.failed).toHaveLength(3); + expect(result.failed[0]).toEqual({ id: userIds[0], error: errorMessage }); }); - expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledWith( - organizationId, - userIds, - ); }); - it("should handle bulk reinvite errors", async () => { - const errorMessage = "Bulk reinvite failed"; - organizationUserApiService.postManyOrganizationUserReinvite.mockRejectedValue( - new Error(errorMessage), - ); + describe("when feature flag is true (batching behavior)", () => { + beforeEach(() => { + configService.getFeatureFlag$.mockReturnValue(of(true)); + }); + it("should process users in a single batch when count equals REQUESTS_PER_BATCH", async () => { + const userIdsBatch = Array.from({ length: REQUESTS_PER_BATCH }, () => newGuid() as UserId); + const mockResponse = new ListResponse( + { + data: userIdsBatch.map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); - const result = await service.bulkReinvite(mockOrganization, userIds); + organizationUserApiService.postManyOrganizationUserReinvite.mockResolvedValue(mockResponse); - expect(result.successful).toBeUndefined(); - expect(result.failed).toHaveLength(3); - expect(result.failed[0]).toEqual({ id: userIds[0], error: errorMessage }); + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(result.successful).toBeDefined(); + expect(result.successful?.response).toHaveLength(REQUESTS_PER_BATCH); + expect(result.failed).toHaveLength(0); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledTimes( + 1, + ); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledWith( + organizationId, + userIdsBatch, + ); + }); + + it("should process users in multiple batches when count exceeds REQUESTS_PER_BATCH", async () => { + const totalUsers = REQUESTS_PER_BATCH + 100; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + + const mockResponse1 = new ListResponse( + { + data: userIdsBatch.slice(0, REQUESTS_PER_BATCH).map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + const mockResponse2 = new ListResponse( + { + data: userIdsBatch.slice(REQUESTS_PER_BATCH).map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + organizationUserApiService.postManyOrganizationUserReinvite + .mockResolvedValueOnce(mockResponse1) + .mockResolvedValueOnce(mockResponse2); + + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(result.successful).toBeDefined(); + expect(result.successful?.response).toHaveLength(totalUsers); + expect(result.failed).toHaveLength(0); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledTimes( + 2, + ); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenNthCalledWith( + 1, + organizationId, + userIdsBatch.slice(0, REQUESTS_PER_BATCH), + ); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenNthCalledWith( + 2, + organizationId, + userIdsBatch.slice(REQUESTS_PER_BATCH), + ); + }); + + it("should aggregate results across multiple successful batches", async () => { + const totalUsers = REQUESTS_PER_BATCH + 50; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + + const mockResponse1 = new ListResponse( + { + data: userIdsBatch.slice(0, REQUESTS_PER_BATCH).map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + const mockResponse2 = new ListResponse( + { + data: userIdsBatch.slice(REQUESTS_PER_BATCH).map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + organizationUserApiService.postManyOrganizationUserReinvite + .mockResolvedValueOnce(mockResponse1) + .mockResolvedValueOnce(mockResponse2); + + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(result.successful).toBeDefined(); + expect(result.successful?.response).toHaveLength(totalUsers); + expect(result.successful?.response.slice(0, REQUESTS_PER_BATCH)).toEqual( + mockResponse1.data, + ); + expect(result.successful?.response.slice(REQUESTS_PER_BATCH)).toEqual(mockResponse2.data); + expect(result.failed).toHaveLength(0); + }); + + it("should handle mixed individual errors across multiple batches", async () => { + const totalUsers = REQUESTS_PER_BATCH + 4; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + + const mockResponse1 = new ListResponse( + { + data: userIdsBatch.slice(0, REQUESTS_PER_BATCH).map((id, index) => ({ + id, + error: index % 10 === 0 ? "Rate limit exceeded" : null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + const mockResponse2 = new ListResponse( + { + data: [ + { id: userIdsBatch[REQUESTS_PER_BATCH], error: null }, + { id: userIdsBatch[REQUESTS_PER_BATCH + 1], error: "Invalid email" }, + { id: userIdsBatch[REQUESTS_PER_BATCH + 2], error: null }, + { id: userIdsBatch[REQUESTS_PER_BATCH + 3], error: "User suspended" }, + ], + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + organizationUserApiService.postManyOrganizationUserReinvite + .mockResolvedValueOnce(mockResponse1) + .mockResolvedValueOnce(mockResponse2); + + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + // Count expected failures: every 10th index (0, 10, 20, ..., 490) in first batch + 2 explicit in second batch + // Indices 0 to REQUESTS_PER_BATCH-1 where index % 10 === 0: that's floor((BATCH_SIZE-1)/10) + 1 values + const expectedFailuresInBatch1 = Math.floor((REQUESTS_PER_BATCH - 1) / 10) + 1; + const expectedFailuresInBatch2 = 2; + const expectedTotalFailures = expectedFailuresInBatch1 + expectedFailuresInBatch2; + const expectedSuccesses = totalUsers - expectedTotalFailures; + + expect(result.successful).toBeDefined(); + expect(result.successful?.response).toHaveLength(expectedSuccesses); + expect(result.failed).toHaveLength(expectedTotalFailures); + expect(result.failed.some((f) => f.error === "Rate limit exceeded")).toBe(true); + expect(result.failed.some((f) => f.error === "Invalid email")).toBe(true); + expect(result.failed.some((f) => f.error === "User suspended")).toBe(true); + }); + + it("should aggregate all failures when all batches fail", async () => { + const totalUsers = REQUESTS_PER_BATCH + 100; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + const errorMessage = "All batches failed"; + + organizationUserApiService.postManyOrganizationUserReinvite.mockRejectedValue( + new Error(errorMessage), + ); + + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(result.successful).toBeUndefined(); + expect(result.failed).toHaveLength(totalUsers); + expect(result.failed.every((f) => f.error === errorMessage)).toBe(true); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledTimes( + 2, + ); + }); + + it("should handle empty data in batch response", async () => { + const totalUsers = REQUESTS_PER_BATCH + 50; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + + const mockResponse1 = new ListResponse( + { + data: userIdsBatch.slice(0, REQUESTS_PER_BATCH).map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + const mockResponse2 = new ListResponse( + { + data: [], + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + + organizationUserApiService.postManyOrganizationUserReinvite + .mockResolvedValueOnce(mockResponse1) + .mockResolvedValueOnce(mockResponse2); + + const result = await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(result.successful).toBeDefined(); + expect(result.successful?.response).toHaveLength(REQUESTS_PER_BATCH); + expect(result.failed).toHaveLength(0); + }); + + it("should process batches sequentially in order", async () => { + const totalUsers = REQUESTS_PER_BATCH * 2; + const userIdsBatch = Array.from({ length: totalUsers }, () => newGuid() as UserId); + const callOrder: number[] = []; + + organizationUserApiService.postManyOrganizationUserReinvite.mockImplementation( + async (orgId, ids) => { + const batchIndex = ids.includes(userIdsBatch[0]) ? 1 : 2; + callOrder.push(batchIndex); + + return new ListResponse( + { + data: ids.map((id) => ({ + id, + error: null, + })), + continuationToken: null, + }, + OrganizationUserBulkResponse, + ); + }, + ); + + await service.bulkReinvite(mockOrganization, userIdsBatch); + + expect(callOrder).toEqual([1, 2]); + expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledTimes( + 2, + ); + }); }); }); @@ -427,14 +694,6 @@ describe("MemberActionsService", () => { expect(result).toBe(false); }); - it("should not allow reset password when organization lacks public and private keys", () => { - const org = { ...mockOrganization, hasPublicAndPrivateKeys: false } as Organization; - - const result = service.allowResetPassword(mockOrgUser, org, resetPasswordEnabled); - - expect(result).toBe(false); - }); - it("should not allow reset password when user is not enrolled in reset password", () => { const user = { ...mockOrgUser, resetPasswordEnrolled: false } as OrganizationUserView; @@ -443,12 +702,6 @@ describe("MemberActionsService", () => { expect(result).toBe(false); }); - it("should not allow reset password when reset password is disabled", () => { - const result = service.allowResetPassword(mockOrgUser, mockOrganization, false); - - expect(result).toBe(false); - }); - it("should not allow reset password when user status is not confirmed", () => { const user = { ...mockOrgUser, diff --git a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts index 5e19e26954e..f3774e3cb25 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts @@ -20,9 +20,12 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { KeyService } from "@bitwarden/key-management"; +import { UserId } from "@bitwarden/user-core"; import { OrganizationUserView } from "../../../core/views/organization-user.view"; +export const REQUESTS_PER_BATCH = 500; + export interface MemberActionResult { success: boolean; error?: string; @@ -162,20 +165,36 @@ export class MemberActionsService { } } - async bulkReinvite(organization: Organization, userIds: string[]): Promise { - try { - const result = await this.organizationUserApiService.postManyOrganizationUserReinvite( - organization.id, - userIds, - ); - return { successful: result, failed: [] }; - } catch (error) { - return { - failed: userIds.map((id) => ({ id, error: (error as Error).message ?? String(error) })), - }; + async bulkReinvite(organization: Organization, userIds: UserId[]): Promise { + const increaseBulkReinviteLimitForCloud = await firstValueFrom( + this.configService.getFeatureFlag$(FeatureFlag.IncreaseBulkReinviteLimitForCloud), + ); + if (increaseBulkReinviteLimitForCloud) { + return await this.vNextBulkReinvite(organization, userIds); + } else { + try { + const result = await this.organizationUserApiService.postManyOrganizationUserReinvite( + organization.id, + userIds, + ); + return { successful: result, failed: [] }; + } catch (error) { + return { + failed: userIds.map((id) => ({ id, error: (error as Error).message ?? String(error) })), + }; + } } } + async vNextBulkReinvite( + organization: Organization, + userIds: UserId[], + ): Promise { + return this.processBatchedOperation(userIds, REQUESTS_PER_BATCH, (batch) => + this.organizationUserApiService.postManyOrganizationUserReinvite(organization.id, batch), + ); + } + allowResetPassword( orgUser: OrganizationUserView, organization: Organization, @@ -207,4 +226,52 @@ export class MemberActionsService { orgUser.status === OrganizationUserStatusType.Confirmed ); } + + /** + * Processes user IDs in sequential batches and aggregates results. + * @param userIds - Array of user IDs to process + * @param batchSize - Number of IDs to process per batch + * @param processBatch - Async function that processes a single batch and returns the result + * @returns Aggregated bulk action result + */ + private async processBatchedOperation( + userIds: UserId[], + batchSize: number, + processBatch: (batch: string[]) => Promise>, + ): Promise { + const allSuccessful: OrganizationUserBulkResponse[] = []; + const allFailed: { id: string; error: string }[] = []; + + for (let i = 0; i < userIds.length; i += batchSize) { + const batch = userIds.slice(i, i + batchSize); + + try { + const result = await processBatch(batch); + + if (result?.data) { + for (const response of result.data) { + if (response.error) { + allFailed.push({ id: response.id, error: response.error }); + } else { + allSuccessful.push(response); + } + } + } + } catch (error) { + allFailed.push( + ...batch.map((id) => ({ id, error: (error as Error).message ?? String(error) })), + ); + } + } + + const successful = + allSuccessful.length > 0 + ? new ListResponse(allSuccessful, OrganizationUserBulkResponse) + : undefined; + + return { + successful, + failed: allFailed, + }; + } } 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 df5e7e8a25c..88797f86650 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 @@ -35,13 +35,10 @@ import { OrganizationUserResetPasswordEntry } from "./organization-user-reset-pa @Injectable({ providedIn: "root", }) -export class OrganizationUserResetPasswordService - implements - UserKeyRotationKeyRecoveryProvider< - OrganizationUserResetPasswordWithIdRequest, - OrganizationUserResetPasswordEntry - > -{ +export class OrganizationUserResetPasswordService implements UserKeyRotationKeyRecoveryProvider< + OrganizationUserResetPasswordWithIdRequest, + OrganizationUserResetPasswordEntry +> { constructor( private keyService: KeyService, private encryptService: EncryptService, 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 8df73a50e14..c38092146ab 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.html @@ -1,27 +1,34 @@ +@let organization = organization$ | async; +@let policiesEnabledMap = policiesEnabledMap$ | async; +@let organizationId = organizationId$ | async; + - @if (loading) { + @if (!organization || !policiesEnabledMap || !organizationId) { {{ "loading" | i18n }} - } - @if (!loading) { + } @else { - @for (p of policies$ | async; track p.type) { - - - - @if (policiesEnabledMap.get(p.type)) { - {{ "on" | i18n }} - } - {{ p.description | i18n }} - - + @for (p of policies$ | async; track $index) { + @if (p.display$(organization, configService) | async) { + + + + @if (policiesEnabledMap.get(p.type)) { + {{ "on" | i18n }} + } + {{ p.description | i18n }} + + + } } diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.spec.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.spec.ts new file mode 100644 index 00000000000..0e025a9d52a --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.spec.ts @@ -0,0 +1,498 @@ +import { NO_ERRORS_SCHEMA } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ActivatedRoute } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject, of, firstValueFrom } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +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 { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; +import { newGuid } from "@bitwarden/guid"; + +import { BasePolicyEditDefinition } from "./base-policy-edit.component"; +import { PoliciesComponent } from "./policies.component"; +import { SingleOrgPolicy } from "./policy-edit-definitions/single-org.component"; +import { PolicyEditDialogComponent } from "./policy-edit-dialog.component"; +import { PolicyListService } from "./policy-list.service"; +import { POLICY_EDIT_REGISTER } from "./policy-register-token"; + +describe("PoliciesComponent", () => { + let component: PoliciesComponent; + let fixture: ComponentFixture; + + let mockActivatedRoute: ActivatedRoute; + let mockOrganizationService: MockProxy; + let mockAccountService: FakeAccountService; + let mockPolicyApiService: MockProxy; + let mockPolicyListService: MockProxy; + let mockDialogService: MockProxy; + let mockPolicyService: MockProxy; + let mockConfigService: MockProxy; + let mockI18nService: MockProxy; + let mockPlatformUtilsService: MockProxy; + + let routeParamsSubject: BehaviorSubject; + let queryParamsSubject: BehaviorSubject; + + const mockUserId = newGuid() as UserId; + const mockOrgId = newGuid() as OrganizationId; + const mockOrg = { + id: mockOrgId, + name: "Test Organization", + enabled: true, + } as Organization; + + const mockPolicyResponse = { + id: newGuid(), + enabled: true, + object: "policy", + organizationId: mockOrgId, + type: PolicyType.SingleOrg, + data: null, + }; + + const mockPolicy = new SingleOrgPolicy(); + + beforeEach(async () => { + routeParamsSubject = new BehaviorSubject({ organizationId: mockOrgId }); + queryParamsSubject = new BehaviorSubject({}); + + mockActivatedRoute = { + params: routeParamsSubject.asObservable(), + queryParams: queryParamsSubject.asObservable(), + } as any; + + mockOrganizationService = mock(); + mockOrganizationService.organizations$.mockReturnValue(of([mockOrg])); + + mockAccountService = mockAccountServiceWith(mockUserId); + + mockPolicyApiService = mock(); + mockPolicyApiService.getPolicies.mockResolvedValue( + new ListResponse({ Data: [mockPolicyResponse], ContinuationToken: null }, PolicyResponse), + ); + + mockPolicyListService = mock(); + mockPolicyListService.getPolicies.mockReturnValue([mockPolicy]); + + mockDialogService = mock(); + mockDialogService.open.mockReturnValue({ close: jest.fn() } as any); + + mockPolicyService = mock(); + mockPolicyService.policies$.mockReturnValue(of([])); + + mockConfigService = mock(); + mockI18nService = mock(); + mockPlatformUtilsService = mock(); + + jest.spyOn(PolicyEditDialogComponent, "open").mockReturnValue({ close: jest.fn() } as any); + + await TestBed.configureTestingModule({ + imports: [PoliciesComponent], + providers: [ + { provide: ActivatedRoute, useValue: mockActivatedRoute }, + { provide: OrganizationService, useValue: mockOrganizationService }, + { provide: AccountService, useValue: mockAccountService }, + { provide: PolicyApiServiceAbstraction, useValue: mockPolicyApiService }, + { provide: PolicyListService, useValue: mockPolicyListService }, + { provide: DialogService, useValue: mockDialogService }, + { provide: PolicyService, useValue: mockPolicyService }, + { provide: ConfigService, useValue: mockConfigService }, + { provide: I18nService, useValue: mockI18nService }, + { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, + { provide: POLICY_EDIT_REGISTER, useValue: [] }, + ], + schemas: [NO_ERRORS_SCHEMA], + }) + .overrideComponent(PoliciesComponent, { + remove: { imports: [] }, + add: { template: "
    " }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(PoliciesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + afterEach(() => { + if (fixture) { + fixture.destroy(); + } + jest.restoreAllMocks(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + describe("organizationId$", () => { + it("should extract organizationId from route params", async () => { + const orgId = await firstValueFrom(component.organizationId$); + expect(orgId).toBe(mockOrgId); + }); + + it("should emit new organizationId when route params change", (done) => { + const newOrgId = newGuid() as OrganizationId; + const emittedValues: OrganizationId[] = []; + + const subscription = component.organizationId$.subscribe((orgId) => { + emittedValues.push(orgId); + + if (emittedValues.length === 2) { + expect(emittedValues[0]).toBe(mockOrgId); + expect(emittedValues[1]).toBe(newOrgId); + subscription.unsubscribe(); + done(); + } + }); + + routeParamsSubject.next({ organizationId: newOrgId }); + }); + }); + + describe("organization$", () => { + it("should retrieve organization for current user and organizationId", async () => { + const org = await firstValueFrom(component.organization$); + expect(org).toBe(mockOrg); + expect(mockOrganizationService.organizations$).toHaveBeenCalledWith(mockUserId); + }); + + it("should throw error when organization is not found", async () => { + mockOrganizationService.organizations$.mockReturnValue(of([])); + + await expect(firstValueFrom(component.organization$)).rejects.toThrow( + "No organization found for provided userId", + ); + }); + }); + + describe("policies$", () => { + it("should return policies from PolicyListService", async () => { + const policies = await firstValueFrom(component.policies$); + + expect(policies).toBeDefined(); + expect(Array.isArray(policies)).toBe(true); + }); + }); + + describe("orgPolicies$", () => { + it("should fetch policies from API for current organization", async () => { + const mockPolicyResponsesData = [ + { + id: newGuid(), + organizationId: mockOrgId, + type: PolicyType.TwoFactorAuthentication, + enabled: true, + data: null, + }, + { + id: newGuid(), + organizationId: mockOrgId, + type: PolicyType.RequireSso, + enabled: false, + data: null, + }, + ]; + + const listResponse = new ListResponse( + { Data: mockPolicyResponsesData, ContinuationToken: null }, + PolicyResponse, + ); + + mockPolicyApiService.getPolicies.mockResolvedValue(listResponse); + + const policies = await firstValueFrom(component["orgPolicies$"]); + expect(policies).toEqual(listResponse.data); + expect(mockPolicyApiService.getPolicies).toHaveBeenCalledWith(mockOrgId); + }); + + it("should return empty array when API returns no data", async () => { + mockPolicyApiService.getPolicies.mockResolvedValue( + new ListResponse({ Data: [], ContinuationToken: null }, PolicyResponse), + ); + + const policies = await firstValueFrom(component["orgPolicies$"]); + expect(policies).toEqual([]); + }); + + it("should return empty array when API returns null data", async () => { + mockPolicyApiService.getPolicies.mockResolvedValue( + new ListResponse({ Data: null, ContinuationToken: null }, PolicyResponse), + ); + + const policies = await firstValueFrom(component["orgPolicies$"]); + expect(policies).toEqual([]); + }); + }); + + describe("policiesEnabledMap$", () => { + it("should create a map of policy types to their enabled status", async () => { + const mockPolicyResponsesData = [ + { + id: "policy-1", + organizationId: mockOrgId, + type: PolicyType.TwoFactorAuthentication, + enabled: true, + data: null, + }, + { + id: "policy-2", + organizationId: mockOrgId, + type: PolicyType.RequireSso, + enabled: false, + data: null, + }, + { + id: "policy-3", + organizationId: mockOrgId, + type: PolicyType.SingleOrg, + enabled: true, + data: null, + }, + ]; + + mockPolicyApiService.getPolicies.mockResolvedValue( + new ListResponse( + { Data: mockPolicyResponsesData, ContinuationToken: null }, + PolicyResponse, + ), + ); + + const map = await firstValueFrom(component.policiesEnabledMap$); + expect(map.size).toBe(3); + expect(map.get(PolicyType.TwoFactorAuthentication)).toBe(true); + expect(map.get(PolicyType.RequireSso)).toBe(false); + expect(map.get(PolicyType.SingleOrg)).toBe(true); + }); + + it("should create empty map when no policies exist", async () => { + mockPolicyApiService.getPolicies.mockResolvedValue( + new ListResponse({ Data: [], ContinuationToken: null }, PolicyResponse), + ); + + const map = await firstValueFrom(component.policiesEnabledMap$); + expect(map.size).toBe(0); + }); + }); + + describe("constructor subscription", () => { + it("should subscribe to policyService.policies$ on initialization", () => { + expect(mockPolicyService.policies$).toHaveBeenCalledWith(mockUserId); + }); + + it("should refresh policies when policyService emits", async () => { + const policiesSubject = new BehaviorSubject([]); + mockPolicyService.policies$.mockReturnValue(policiesSubject.asObservable()); + + let callCount = 0; + mockPolicyApiService.getPolicies.mockImplementation(() => { + callCount++; + return of(new ListResponse({ Data: [], ContinuationToken: null }, PolicyResponse)); + }); + + const newFixture = TestBed.createComponent(PoliciesComponent); + newFixture.detectChanges(); + + const initialCallCount = callCount; + + policiesSubject.next([{ type: PolicyType.TwoFactorAuthentication }]); + + expect(callCount).toBeGreaterThan(initialCallCount); + + newFixture.destroy(); + }); + }); + + describe("handleLaunchEvent", () => { + it("should open policy dialog when policyId is in query params", async () => { + const mockPolicyId = newGuid(); + const mockPolicy: BasePolicyEditDefinition = { + name: "Test Policy", + description: "Test Description", + type: PolicyType.TwoFactorAuthentication, + component: {} as any, + showDescription: true, + display$: () => of(true), + }; + + const mockPolicyResponseData = { + id: mockPolicyId, + organizationId: mockOrgId, + type: PolicyType.TwoFactorAuthentication, + enabled: true, + data: null, + }; + + queryParamsSubject.next({ policyId: mockPolicyId }); + + mockPolicyApiService.getPolicies.mockReturnValue( + of( + new ListResponse( + { Data: [mockPolicyResponseData], ContinuationToken: null }, + PolicyResponse, + ), + ), + ); + + const dialogOpenSpy = jest + .spyOn(PolicyEditDialogComponent, "open") + .mockReturnValue({ close: jest.fn() } as any); + + TestBed.resetTestingModule(); + await TestBed.configureTestingModule({ + imports: [PoliciesComponent], + providers: [ + { provide: ActivatedRoute, useValue: mockActivatedRoute }, + { provide: OrganizationService, useValue: mockOrganizationService }, + { provide: AccountService, useValue: mockAccountService }, + { provide: PolicyApiServiceAbstraction, useValue: mockPolicyApiService }, + { provide: PolicyListService, useValue: mockPolicyListService }, + { provide: DialogService, useValue: mockDialogService }, + { provide: PolicyService, useValue: mockPolicyService }, + { provide: ConfigService, useValue: mockConfigService }, + { provide: I18nService, useValue: mockI18nService }, + { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, + { provide: POLICY_EDIT_REGISTER, useValue: [mockPolicy] }, + ], + schemas: [NO_ERRORS_SCHEMA], + }) + .overrideComponent(PoliciesComponent, { + remove: { imports: [] }, + add: { template: "
    " }, + }) + .compileComponents(); + + const newFixture = TestBed.createComponent(PoliciesComponent); + newFixture.detectChanges(); + + expect(dialogOpenSpy).toHaveBeenCalled(); + const callArgs = dialogOpenSpy.mock.calls[0][1]; + expect(callArgs.data?.policy.type).toBe(mockPolicy.type); + expect(callArgs.data?.organizationId).toBe(mockOrgId); + + newFixture.destroy(); + }); + + it("should not open dialog when policyId is not in query params", async () => { + const editSpy = jest.spyOn(component, "edit"); + + queryParamsSubject.next({}); + + expect(editSpy).not.toHaveBeenCalled(); + }); + + it("should not open dialog when policyId does not match any org policy", async () => { + const mockPolicy: BasePolicyEditDefinition = { + name: "Test Policy", + description: "Test Description", + type: PolicyType.TwoFactorAuthentication, + component: {} as any, + showDescription: true, + display$: () => of(true), + }; + + mockPolicyListService.getPolicies.mockReturnValue([mockPolicy]); + mockPolicyApiService.getPolicies.mockResolvedValue( + new ListResponse({ Data: [], ContinuationToken: null }, PolicyResponse), + ); + + const editSpy = jest.spyOn(component, "edit"); + + queryParamsSubject.next({ policyId: "non-existent-policy-id" }); + + expect(editSpy).not.toHaveBeenCalled(); + }); + }); + + describe("edit", () => { + it("should call dialogService.open with correct parameters when no custom dialog is specified", () => { + const mockPolicy: BasePolicyEditDefinition = { + name: "Test Policy", + description: "Test Description", + type: PolicyType.TwoFactorAuthentication, + component: {} as any, + showDescription: true, + display$: () => of(true), + }; + + const openSpy = jest.spyOn(PolicyEditDialogComponent, "open"); + + component.edit(mockPolicy, mockOrgId); + + expect(openSpy).toHaveBeenCalled(); + const callArgs = openSpy.mock.calls[0]; + expect(callArgs[1]).toEqual({ + data: { + policy: mockPolicy, + organizationId: mockOrgId, + }, + }); + }); + + it("should call custom dialog open method when specified", () => { + const mockDialogRef = { close: jest.fn() }; + const mockCustomDialog = { + open: jest.fn().mockReturnValue(mockDialogRef), + }; + + const mockPolicy: BasePolicyEditDefinition = { + name: "Custom Policy", + description: "Custom Description", + type: PolicyType.RequireSso, + component: {} as any, + editDialogComponent: mockCustomDialog as any, + showDescription: true, + display$: () => of(true), + }; + + component.edit(mockPolicy, mockOrgId); + + expect(mockCustomDialog.open).toHaveBeenCalled(); + const callArgs = mockCustomDialog.open.mock.calls[0]; + expect(callArgs[1]).toEqual({ + data: { + policy: mockPolicy, + organizationId: mockOrgId, + }, + }); + expect(PolicyEditDialogComponent.open).not.toHaveBeenCalled(); + }); + + it("should pass correct organizationId to dialog", () => { + const customOrgId = newGuid() as OrganizationId; + const mockPolicy: BasePolicyEditDefinition = { + name: "Test Policy", + description: "Test Description", + type: PolicyType.SingleOrg, + component: {} as any, + showDescription: true, + display$: () => of(true), + }; + + const openSpy = jest.spyOn(PolicyEditDialogComponent, "open"); + + component.edit(mockPolicy, customOrgId); + + expect(openSpy).toHaveBeenCalled(); + const callArgs = openSpy.mock.calls[0]; + expect(callArgs[1]).toEqual({ + data: { + policy: mockPolicy, + organizationId: customOrgId, + }, + }); + }); + }); +}); 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 e80796fd0af..70daf55f662 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -1,31 +1,19 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, Component, DestroyRef } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute } from "@angular/router"; -import { - combineLatest, - firstValueFrom, - Observable, - of, - switchMap, - first, - map, - withLatestFrom, - tap, -} from "rxjs"; +import { combineLatest, Observable, of, switchMap, first, map } from "rxjs"; -import { - getOrganizationById, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; +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 { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { getById } from "@bitwarden/common/platform/misc"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; import { safeProvider } from "@bitwarden/ui-common"; @@ -37,8 +25,6 @@ import { PolicyEditDialogComponent } from "./policy-edit-dialog.component"; import { PolicyListService } from "./policy-list.service"; import { POLICY_EDIT_REGISTER } from "./policy-register-token"; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ templateUrl: "policies.component.html", imports: [SharedModule, HeaderModule], @@ -48,14 +34,53 @@ import { POLICY_EDIT_REGISTER } from "./policy-register-token"; deps: [POLICY_EDIT_REGISTER], }), ], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class PoliciesComponent implements OnInit { - loading = true; - organizationId: string; - policies$: Observable; +export class PoliciesComponent { + private userId$: Observable = this.accountService.activeAccount$.pipe(getUserId); - private orgPolicies: PolicyResponse[]; - protected policiesEnabledMap: Map = new Map(); + protected organizationId$: Observable = this.route.params.pipe( + map((params) => params.organizationId), + ); + + protected organization$: Observable = combineLatest([ + this.userId$, + this.organizationId$, + ]).pipe( + switchMap(([userId, orgId]) => + this.organizationService.organizations$(userId).pipe( + getById(orgId), + map((org) => { + if (org == null) { + throw new Error("No organization found for provided userId"); + } + return org; + }), + ), + ), + ); + + protected policies$: Observable = of( + this.policyListService.getPolicies(), + ); + + private orgPolicies$: Observable = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.policyService.policies$(userId)), + switchMap(() => this.organizationId$), + switchMap((organizationId) => this.policyApiService.getPolicies(organizationId)), + map((response) => (response.data != null && response.data.length > 0 ? response.data : [])), + ); + + protected policiesEnabledMap$: Observable> = this.orgPolicies$.pipe( + map((orgPolicies) => { + const policiesEnabledMap: Map = new Map(); + orgPolicies.forEach((op) => { + policiesEnabledMap.set(op.type, op.enabled); + }); + return policiesEnabledMap; + }), + ); constructor( private route: ActivatedRoute, @@ -66,60 +91,28 @@ export class PoliciesComponent implements OnInit { private dialogService: DialogService, private policyService: PolicyService, protected configService: ConfigService, + private destroyRef: DestroyRef, ) { - this.accountService.activeAccount$ - .pipe( - getUserId, - switchMap((userId) => this.policyService.policies$(userId)), - tap(async () => await this.load()), - takeUntilDestroyed(), - ) - .subscribe(); + this.handleLaunchEvent(); } - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - this.organizationId = params.organizationId; - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - const organization$ = this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(this.organizationId)); - - this.policies$ = organization$.pipe( - withLatestFrom(of(this.policyListService.getPolicies())), - switchMap(([organization, policies]) => { - return combineLatest( - policies.map((policy) => - policy - .display$(organization, this.configService) - .pipe(map((shouldDisplay) => ({ policy, shouldDisplay }))), - ), - ); - }), - map((results) => - results.filter((result) => result.shouldDisplay).map((result) => result.policy), - ), - ); - - await this.load(); - - // Handle policies component launch from Event message - combineLatest([this.route.queryParams.pipe(first()), this.policies$]) - /* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */ - .subscribe(async ([qParams, policies]) => { + // Handle policies component launch from Event message + private handleLaunchEvent() { + combineLatest([ + this.route.queryParams.pipe(first()), + this.policies$, + this.organizationId$, + this.orgPolicies$, + ]) + .pipe( + map(([qParams, policies, organizationId, orgPolicies]) => { if (qParams.policyId != null) { const policyIdFromEvents: string = qParams.policyId; - for (const orgPolicy of this.orgPolicies) { + for (const orgPolicy of orgPolicies) { if (orgPolicy.id === policyIdFromEvents) { for (let i = 0; i < policies.length; i++) { if (policies[i].type === orgPolicy.type) { - // 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.edit(policies[i]); + this.edit(policies[i], organizationId); break; } } @@ -127,27 +120,19 @@ export class PoliciesComponent implements OnInit { } } } - }); - }); + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(); } - async load() { - const response = await this.policyApiService.getPolicies(this.organizationId); - this.orgPolicies = response.data != null && response.data.length > 0 ? response.data : []; - this.orgPolicies.forEach((op) => { - this.policiesEnabledMap.set(op.type, op.enabled); - }); - - this.loading = false; - } - - async edit(policy: BasePolicyEditDefinition) { + edit(policy: BasePolicyEditDefinition, organizationId: OrganizationId) { const dialogComponent: PolicyDialogComponent = policy.editDialogComponent ?? PolicyEditDialogComponent; dialogComponent.open(this.dialogService, { data: { policy: policy, - organizationId: this.organizationId, + organizationId: organizationId, }, }); } diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.ts index 7fa4fc2eea7..cf2a2929905 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.ts @@ -1,4 +1,11 @@ -import { Component, OnInit, Signal, TemplateRef, viewChild } from "@angular/core"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + Signal, + TemplateRef, + viewChild, +} from "@angular/core"; import { BehaviorSubject, map, Observable } from "rxjs"; import { AutoConfirmSvg } from "@bitwarden/assets/svg"; @@ -26,11 +33,11 @@ export class AutoConfirmPolicy extends BasePolicyEditDefinition { } } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "auto-confirm-policy-edit", templateUrl: "auto-confirm-policy.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutoConfirmPolicyEditComponent extends BasePolicyEditComponent implements OnInit { protected readonly autoConfirmSvg = AutoConfirmSvg; diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/autotype-policy.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/autotype-policy.component.ts index ceace60cd99..809ffd39a64 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/autotype-policy.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/autotype-policy.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -18,10 +18,10 @@ export class DesktopAutotypeDefaultSettingPolicy extends BasePolicyEditDefinitio return configService.getFeatureFlag$(FeatureFlag.WindowsDesktopAutotype); } } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "autotype-policy-edit", templateUrl: "autotype-policy.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DesktopAutotypeDefaultSettingPolicyComponent extends BasePolicyEditComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/disable-send.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/disable-send.component.ts index 103420fbf51..d5d4a207598 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/disable-send.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/disable-send.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -12,10 +12,10 @@ export class DisableSendPolicy extends BasePolicyEditDefinition { component = DisableSendPolicyComponent; } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "disable-send-policy-edit", templateUrl: "disable-send.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DisableSendPolicyComponent extends BasePolicyEditComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/master-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/master-password.component.ts index c1223a2004b..e9926b2aeb1 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/master-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/master-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 { Component, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, Component, OnInit } from "@angular/core"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { firstValueFrom } from "rxjs"; @@ -26,11 +26,11 @@ export class MasterPasswordPolicy extends BasePolicyEditDefinition { component = MasterPasswordPolicyComponent; } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "master-password-policy-edit", templateUrl: "master-password.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MasterPasswordPolicyComponent extends BasePolicyEditComponent implements OnInit { MinPasswordLength = Utils.minimumPasswordLength; diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/organization-data-ownership.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/organization-data-ownership.component.ts index d832dff158a..ec857478a21 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/organization-data-ownership.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/organization-data-ownership.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { map, Observable } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -22,10 +22,10 @@ export class OrganizationDataOwnershipPolicy extends BasePolicyEditDefinition { } } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "organization-data-ownership-policy-edit", templateUrl: "organization-data-ownership.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class OrganizationDataOwnershipPolicyComponent extends BasePolicyEditComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/password-generator.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/password-generator.component.ts index e3a67362cc9..b338f3ec508 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/password-generator.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/password-generator.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { UntypedFormBuilder, Validators } from "@angular/forms"; import { BehaviorSubject, map } from "rxjs"; @@ -19,11 +19,11 @@ export class PasswordGeneratorPolicy extends BasePolicyEditDefinition { component = PasswordGeneratorPolicyComponent; } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "password-generator-policy-edit", templateUrl: "password-generator.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class PasswordGeneratorPolicyComponent extends BasePolicyEditComponent { // these properties forward the application default settings to the UI diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/remove-unlock-with-pin.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/remove-unlock-with-pin.component.ts index ac768d47d6e..443a1792956 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/remove-unlock-with-pin.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/remove-unlock-with-pin.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -12,10 +12,10 @@ export class RemoveUnlockWithPinPolicy extends BasePolicyEditDefinition { component = RemoveUnlockWithPinPolicyComponent; } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "remove-unlock-with-pin-policy-edit", templateUrl: "remove-unlock-with-pin.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class RemoveUnlockWithPinPolicyComponent extends BasePolicyEditComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/require-sso.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/require-sso.component.ts index 904c29ca70d..e7cded03f11 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/require-sso.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/require-sso.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { of } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -19,10 +19,10 @@ export class RequireSsoPolicy extends BasePolicyEditDefinition { } } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "require-sso-policy-edit", templateUrl: "require-sso.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class RequireSsoPolicyComponent extends BasePolicyEditComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/reset-password.component.ts index bfe149048e3..4b194075aef 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/reset-password.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, Component, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { firstValueFrom, of } from "rxjs"; @@ -26,11 +26,11 @@ export class ResetPasswordPolicy extends BasePolicyEditDefinition { } } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "reset-password-policy-edit", templateUrl: "reset-password.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ResetPasswordPolicyComponent extends BasePolicyEditComponent implements OnInit { data = this.formBuilder.group({ diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/restricted-item-types.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/restricted-item-types.component.ts index 554542f8a84..279b40ef5ee 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/restricted-item-types.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/restricted-item-types.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -12,11 +12,11 @@ export class RestrictedItemTypesPolicy extends BasePolicyEditDefinition { component = RestrictedItemTypesPolicyComponent; } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "restricted-item-types-policy-edit", templateUrl: "restricted-item-types.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class RestrictedItemTypesPolicyComponent extends BasePolicyEditComponent { constructor() { diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/send-options.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/send-options.component.ts index b8a59e8f8ef..06fd672fa4e 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/send-options.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/send-options.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { UntypedFormBuilder } from "@angular/forms"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -13,11 +13,11 @@ export class SendOptionsPolicy extends BasePolicyEditDefinition { component = SendOptionsPolicyComponent; } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "send-options-policy-edit", templateUrl: "send-options.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class SendOptionsPolicyComponent extends BasePolicyEditComponent { data = this.formBuilder.group({ diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/single-org.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/single-org.component.ts index 655c5f20610..dfdb4d4ddaf 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/single-org.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/single-org.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, Component, OnInit } from "@angular/core"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -12,11 +12,11 @@ export class SingleOrgPolicy extends BasePolicyEditDefinition { component = SingleOrgPolicyComponent; } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "single-org-policy-edit", templateUrl: "single-org.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class SingleOrgPolicyComponent extends BasePolicyEditComponent implements OnInit { async ngOnInit() { diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/two-factor-authentication.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/two-factor-authentication.component.ts index 62f3d1f3466..8700714f6f4 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/two-factor-authentication.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/two-factor-authentication.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -12,10 +12,10 @@ export class TwoFactorAuthenticationPolicy extends BasePolicyEditDefinition { component = TwoFactorAuthenticationPolicyComponent; } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "two-factor-authentication-policy-edit", templateUrl: "two-factor-authentication.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TwoFactorAuthenticationPolicyComponent extends BasePolicyEditComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/uri-match-default.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/uri-match-default.component.ts index 5c0b667bea2..d88b1d0769a 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/uri-match-default.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/uri-match-default.component.ts @@ -19,6 +19,7 @@ export class UriMatchDefaultPolicy extends BasePolicyEditDefinition { component = UriMatchDefaultPolicyComponent; } @Component({ + selector: "uri-match-default-policy-edit", changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: "uri-match-default.component.html", imports: [SharedModule], diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts index a0d425d5886..ed26dd37801 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; +import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; import { lastValueFrom, Observable } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -34,11 +34,11 @@ export class vNextOrganizationDataOwnershipPolicy extends BasePolicyEditDefiniti } } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "vnext-organization-data-ownership-policy-edit", templateUrl: "vnext-organization-data-ownership.component.html", imports: [SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class vNextOrganizationDataOwnershipPolicyComponent extends BasePolicyEditComponent diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/storybook-utils.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/storybook-utils.ts index fb8bdef1d8c..9c39ca572fb 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/storybook-utils.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/storybook-utils.ts @@ -1,4 +1,4 @@ -import { action } from "@storybook/addon-actions"; +import { action } from "storybook/actions"; import { AccessItemType, AccessItemView } from "./access-selector.models"; diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 30dbee9fac5..c312b4edd7e 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -27,7 +27,7 @@ import { StateEventRunnerService } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { DialogService, RouterFocusManagerService, ToastService } from "@bitwarden/components"; import { KeyService, BiometricStateService } from "@bitwarden/key-management"; const BroadcasterSubscriptionId = "AppComponent"; @@ -76,11 +76,17 @@ export class AppComponent implements OnDestroy, OnInit { private readonly destroy: DestroyRef, private readonly documentLangSetter: DocumentLangSetter, private readonly tokenService: TokenService, + private readonly routerFocusManager: RouterFocusManagerService, ) { this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe(); const langSubscription = this.documentLangSetter.start(); - this.destroy.onDestroy(() => langSubscription.unsubscribe()); + + this.routerFocusManager.start$.pipe(takeUntilDestroyed()).subscribe(); + + this.destroy.onDestroy(() => { + langSubscription.unsubscribe(); + }); } ngOnInit() { 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 5bea0908b0a..8c1bc4bd080 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 @@ -61,8 +61,11 @@ export class WebLoginComponentService email: string, state: string, codeChallenge: string, + orgSsoIdentifier?: string, ): Promise { - await this.router.navigate(["/sso"]); + await this.router.navigate(["/sso"], { + queryParams: { identifier: orgSsoIdentifier }, + }); return; } diff --git a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.ts b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.ts index 7765d01f75c..3fc57e1a22c 100644 --- a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.ts +++ b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.ts @@ -39,9 +39,7 @@ import { WebAuthnLoginAdminApiService } from "./webauthn-login-admin-api.service /** * Service for managing WebAuthnLogin credentials. */ -export class WebauthnLoginAdminService - implements UserKeyRotationDataProvider -{ +export class WebauthnLoginAdminService implements UserKeyRotationDataProvider { static readonly MaxCredentialCount = 5; private navigatorCredentials: CredentialsContainer; 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 b91bc932e83..80b1b27116b 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 @@ -45,13 +45,10 @@ import { EmergencyAccessGranteeDetailsResponse } from "../response/emergency-acc import { EmergencyAccessApiService } from "./emergency-access-api.service"; @Injectable() -export class EmergencyAccessService - implements - UserKeyRotationKeyRecoveryProvider< - EmergencyAccessWithIdRequest, - GranteeEmergencyAccessWithPublicKey - > -{ +export class EmergencyAccessService implements UserKeyRotationKeyRecoveryProvider< + EmergencyAccessWithIdRequest, + GranteeEmergencyAccessWithPublicKey +> { constructor( private emergencyAccessApiService: EmergencyAccessApiService, private apiService: ApiService, diff --git a/apps/web/src/app/auth/verify-recover-delete.component.html b/apps/web/src/app/auth/verify-recover-delete.component.html index 02581b21418..27eda24a118 100644 --- a/apps/web/src/app/auth/verify-recover-delete.component.html +++ b/apps/web/src/app/auth/verify-recover-delete.component.html @@ -1,5 +1,5 @@
    - {{ "deleteAccountWarning" | i18n }} + {{ "deleteAccountWarning" | i18n }}

    {{ email }}

    diff --git a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html index 6b168901b2e..e182659acbb 100644 --- a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html +++ b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html @@ -21,7 +21,7 @@
    @if (premiumCardData$ | async; as premiumData) { f.value) || [], @@ -172,7 +172,7 @@ export class CloudHostedPremiumVNextComponent { return { tier, price: - tier?.passwordManager.type === "packaged" + tier?.passwordManager.type === "packaged" && tier.passwordManager.annualPrice ? Number((tier.passwordManager.annualPrice / 12).toFixed(2)) : 0, features: tier?.passwordManager.features.map((f) => f.value) || [], diff --git a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html index 63c26bd61f1..33e89f21fc0 100644 --- a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html +++ b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html @@ -24,7 +24,7 @@
    • - {{ "premiumSignUpStorage" | i18n }} + {{ "premiumSignUpStorageV2" | i18n: `${(providedStorageGb$ | async)} GB` }}
    • @@ -82,7 +82,10 @@ /> {{ "additionalStorageIntervalDesc" - | i18n: "1 GB" : (storagePrice$ | async | currency: "$") : ("year" | i18n) + | i18n + : `${(providedStorageGb$ | async)} GB` + : (storagePrice$ | async | currency: "$") + : ("year" | i18n) }}
    diff --git a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts index fceeeedf170..86a508d2701 100644 --- a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts @@ -22,8 +22,8 @@ import { debounceTime } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { SubscriptionPricingServiceAbstraction } from "@bitwarden/common/billing/abstractions/subscription-pricing.service.abstraction"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { DefaultSubscriptionPricingService } from "@bitwarden/common/billing/services/subscription-pricing.service"; import { PersonalSubscriptionPricingTierIds } from "@bitwarden/common/billing/types/subscription-pricing-tier"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -75,6 +75,7 @@ export class CloudHostedPremiumComponent { return { seat: premiumPlan.passwordManager.annualPrice, storage: premiumPlan.passwordManager.annualPricePerAdditionalStorageGB, + providedStorageGb: premiumPlan.passwordManager.providedStorageGB, }; }), shareReplay({ bufferSize: 1, refCount: true }), @@ -84,6 +85,8 @@ export class CloudHostedPremiumComponent { storagePrice$ = this.premiumPrices$.pipe(map((prices) => prices.storage)); + providedStorageGb$ = this.premiumPrices$.pipe(map((prices) => prices.providedStorageGb)); + protected isLoadingPrices$ = this.premiumPrices$.pipe( map(() => false), startWith(true), @@ -134,7 +137,7 @@ export class CloudHostedPremiumComponent { private accountService: AccountService, private subscriberBillingClient: SubscriberBillingClient, private taxClient: TaxClient, - private subscriptionPricingService: DefaultSubscriptionPricingService, + private subscriptionPricingService: SubscriptionPricingServiceAbstraction, ) { this.hasPremiumFromAnyOrganization$ = this.accountService.activeAccount$.pipe( switchMap((account) => diff --git a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts index 7f698ae50d1..b28a7b8c4a2 100644 --- a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { Component, input, output } from "@angular/core"; +import { ChangeDetectionStrategy, Component, input, output } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { Router } from "@angular/router"; @@ -28,12 +28,11 @@ import { UnifiedUpgradeDialogStep, } from "./unified-upgrade-dialog.component"; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-upgrade-account", template: "", standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, }) class MockUpgradeAccountComponent { readonly dialogTitleMessageOverride = input(null); @@ -42,12 +41,11 @@ class MockUpgradeAccountComponent { closeClicked = output(); } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-upgrade-payment", template: "", standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, }) class MockUpgradePaymentComponent { readonly selectedPlanId = input(null); @@ -77,10 +75,56 @@ describe("UnifiedUpgradeDialogComponent", () => { planSelectionStepTitleOverride: null, }; + /** + * Helper function to create and configure a fresh component instance with custom dialog data + */ + async function createComponentWithDialogData( + dialogData: UnifiedUpgradeDialogParams, + waitForStable = false, + ): Promise<{ + fixture: ComponentFixture; + component: UnifiedUpgradeDialogComponent; + }> { + TestBed.resetTestingModule(); + jest.clearAllMocks(); + + await TestBed.configureTestingModule({ + imports: [NoopAnimationsModule, UnifiedUpgradeDialogComponent], + providers: [ + { provide: DialogRef, useValue: mockDialogRef }, + { provide: DIALOG_DATA, useValue: dialogData }, + { provide: Router, useValue: mockRouter }, + { provide: PremiumInterestStateService, useValue: mockPremiumInterestStateService }, + ], + }) + .overrideComponent(UnifiedUpgradeDialogComponent, { + remove: { + imports: [UpgradeAccountComponent, UpgradePaymentComponent], + }, + add: { + imports: [MockUpgradeAccountComponent, MockUpgradePaymentComponent], + }, + }) + .compileComponents(); + + const newFixture = TestBed.createComponent(UnifiedUpgradeDialogComponent); + const newComponent = newFixture.componentInstance; + newFixture.detectChanges(); + + if (waitForStable) { + await newFixture.whenStable(); + } + + return { fixture: newFixture, component: newComponent }; + } + beforeEach(async () => { // Reset mocks jest.clearAllMocks(); + // Default mock: no premium interest + mockPremiumInterestStateService.getPremiumInterest.mockResolvedValue(false); + await TestBed.configureTestingModule({ imports: [NoopAnimationsModule, UnifiedUpgradeDialogComponent], providers: [ @@ -117,49 +161,63 @@ describe("UnifiedUpgradeDialogComponent", () => { }); it("should initialize with custom initial step", async () => { - TestBed.resetTestingModule(); - const customDialogData: UnifiedUpgradeDialogParams = { account: mockAccount, initialStep: UnifiedUpgradeDialogStep.Payment, selectedPlan: PersonalSubscriptionPricingTierIds.Premium, }; - await TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, UnifiedUpgradeDialogComponent], - providers: [ - { provide: DialogRef, useValue: mockDialogRef }, - { provide: DIALOG_DATA, useValue: customDialogData }, - { provide: Router, useValue: mockRouter }, - { provide: PremiumInterestStateService, useValue: mockPremiumInterestStateService }, - ], - }) - .overrideComponent(UnifiedUpgradeDialogComponent, { - remove: { - imports: [UpgradeAccountComponent, UpgradePaymentComponent], - }, - add: { - imports: [MockUpgradeAccountComponent, MockUpgradePaymentComponent], - }, - }) - .compileComponents(); - - const customFixture = TestBed.createComponent(UnifiedUpgradeDialogComponent); - const customComponent = customFixture.componentInstance; - customFixture.detectChanges(); + const { component: customComponent } = await createComponentWithDialogData(customDialogData); expect(customComponent["step"]()).toBe(UnifiedUpgradeDialogStep.Payment); expect(customComponent["selectedPlan"]()).toBe(PersonalSubscriptionPricingTierIds.Premium); }); + describe("ngOnInit premium interest handling", () => { + it("should check premium interest on initialization", async () => { + // Component already initialized in beforeEach + expect(mockPremiumInterestStateService.getPremiumInterest).toHaveBeenCalledWith( + mockAccount.id, + ); + }); + + it("should set hasPremiumInterest signal and clear premium interest when it exists", async () => { + mockPremiumInterestStateService.getPremiumInterest.mockResolvedValue(true); + mockPremiumInterestStateService.clearPremiumInterest.mockResolvedValue(undefined); + + const { component: customComponent } = await createComponentWithDialogData( + defaultDialogData, + true, + ); + + expect(mockPremiumInterestStateService.getPremiumInterest).toHaveBeenCalledWith( + mockAccount.id, + ); + expect(mockPremiumInterestStateService.clearPremiumInterest).toHaveBeenCalledWith( + mockAccount.id, + ); + expect(customComponent["hasPremiumInterest"]()).toBe(true); + }); + + it("should not set hasPremiumInterest signal or clear when premium interest does not exist", async () => { + mockPremiumInterestStateService.getPremiumInterest.mockResolvedValue(false); + + const { component: customComponent } = await createComponentWithDialogData(defaultDialogData); + + expect(mockPremiumInterestStateService.getPremiumInterest).toHaveBeenCalledWith( + mockAccount.id, + ); + expect(mockPremiumInterestStateService.clearPremiumInterest).not.toHaveBeenCalled(); + expect(customComponent["hasPremiumInterest"]()).toBe(false); + }); + }); + describe("custom dialog title", () => { it("should use null as default when no override is provided", () => { expect(component["planSelectionStepTitleOverride"]()).toBeNull(); }); it("should use custom title when provided in dialog config", async () => { - TestBed.resetTestingModule(); - const customDialogData: UnifiedUpgradeDialogParams = { account: mockAccount, initialStep: UnifiedUpgradeDialogStep.PlanSelection, @@ -167,28 +225,7 @@ describe("UnifiedUpgradeDialogComponent", () => { planSelectionStepTitleOverride: "upgradeYourPlan", }; - await TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, UnifiedUpgradeDialogComponent], - providers: [ - { provide: DialogRef, useValue: mockDialogRef }, - { provide: DIALOG_DATA, useValue: customDialogData }, - { provide: Router, useValue: mockRouter }, - { provide: PremiumInterestStateService, useValue: mockPremiumInterestStateService }, - ], - }) - .overrideComponent(UnifiedUpgradeDialogComponent, { - remove: { - imports: [UpgradeAccountComponent, UpgradePaymentComponent], - }, - add: { - imports: [MockUpgradeAccountComponent, MockUpgradePaymentComponent], - }, - }) - .compileComponents(); - - const customFixture = TestBed.createComponent(UnifiedUpgradeDialogComponent); - const customComponent = customFixture.componentInstance; - customFixture.detectChanges(); + const { component: customComponent } = await createComponentWithDialogData(customDialogData); expect(customComponent["planSelectionStepTitleOverride"]()).toBe("upgradeYourPlan"); }); @@ -221,8 +258,6 @@ describe("UnifiedUpgradeDialogComponent", () => { }); it("should be set to true when provided in dialog config", async () => { - TestBed.resetTestingModule(); - const customDialogData: UnifiedUpgradeDialogParams = { account: mockAccount, initialStep: null, @@ -230,108 +265,32 @@ describe("UnifiedUpgradeDialogComponent", () => { hideContinueWithoutUpgradingButton: true, }; - await TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, UnifiedUpgradeDialogComponent], - providers: [ - { provide: DialogRef, useValue: mockDialogRef }, - { provide: DIALOG_DATA, useValue: customDialogData }, - { provide: Router, useValue: mockRouter }, - { provide: PremiumInterestStateService, useValue: mockPremiumInterestStateService }, - ], - }) - .overrideComponent(UnifiedUpgradeDialogComponent, { - remove: { - imports: [UpgradeAccountComponent, UpgradePaymentComponent], - }, - add: { - imports: [MockUpgradeAccountComponent, MockUpgradePaymentComponent], - }, - }) - .compileComponents(); - - const customFixture = TestBed.createComponent(UnifiedUpgradeDialogComponent); - const customComponent = customFixture.componentInstance; - customFixture.detectChanges(); + const { component: customComponent } = await createComponentWithDialogData(customDialogData); expect(customComponent["hideContinueWithoutUpgradingButton"]()).toBe(true); }); }); - describe("onComplete with premium interest", () => { - it("should check premium interest, clear it, and route to /vault when premium interest exists", async () => { + describe("onComplete", () => { + it("should route to /vault when upgrading to premium with premium interest", async () => { + // Set up component with premium interest mockPremiumInterestStateService.getPremiumInterest.mockResolvedValue(true); - mockPremiumInterestStateService.clearPremiumInterest.mockResolvedValue(); + mockPremiumInterestStateService.clearPremiumInterest.mockResolvedValue(undefined); mockRouter.navigate.mockResolvedValue(true); - const result: UpgradePaymentResult = { - status: "upgradedToPremium", - organizationId: null, - }; - - await component["onComplete"](result); + const { component: customComponent } = await createComponentWithDialogData( + defaultDialogData, + true, + ); + // Premium interest should be set and cleared during ngOnInit expect(mockPremiumInterestStateService.getPremiumInterest).toHaveBeenCalledWith( mockAccount.id, ); expect(mockPremiumInterestStateService.clearPremiumInterest).toHaveBeenCalledWith( mockAccount.id, ); - expect(mockRouter.navigate).toHaveBeenCalledWith(["/vault"]); - expect(mockDialogRef.close).toHaveBeenCalledWith({ - status: "upgradedToPremium", - organizationId: null, - }); - }); - - it("should not clear premium interest when upgrading to families", async () => { - const result: UpgradePaymentResult = { - status: "upgradedToFamilies", - organizationId: "org-123", - }; - - await component["onComplete"](result); - - expect(mockPremiumInterestStateService.getPremiumInterest).not.toHaveBeenCalled(); - expect(mockPremiumInterestStateService.clearPremiumInterest).not.toHaveBeenCalled(); - expect(mockDialogRef.close).toHaveBeenCalledWith({ - status: "upgradedToFamilies", - organizationId: "org-123", - }); - }); - - it("should use standard redirect when no premium interest exists", async () => { - TestBed.resetTestingModule(); - - const customDialogData: UnifiedUpgradeDialogParams = { - account: mockAccount, - redirectOnCompletion: true, - }; - - mockPremiumInterestStateService.getPremiumInterest.mockResolvedValue(false); - mockRouter.navigate.mockResolvedValue(true); - - await TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, UnifiedUpgradeDialogComponent], - providers: [ - { provide: DialogRef, useValue: mockDialogRef }, - { provide: DIALOG_DATA, useValue: customDialogData }, - { provide: Router, useValue: mockRouter }, - { provide: PremiumInterestStateService, useValue: mockPremiumInterestStateService }, - ], - }) - .overrideComponent(UnifiedUpgradeDialogComponent, { - remove: { - imports: [UpgradeAccountComponent, UpgradePaymentComponent], - }, - add: { - imports: [MockUpgradeAccountComponent, MockUpgradePaymentComponent], - }, - }) - .compileComponents(); - - const customFixture = TestBed.createComponent(UnifiedUpgradeDialogComponent); - const customComponent = customFixture.componentInstance; - customFixture.detectChanges(); + expect(customComponent["hasPremiumInterest"]()).toBe(true); const result: UpgradePaymentResult = { status: "upgradedToPremium", @@ -340,10 +299,55 @@ describe("UnifiedUpgradeDialogComponent", () => { await customComponent["onComplete"](result); - expect(mockPremiumInterestStateService.getPremiumInterest).toHaveBeenCalledWith( - mockAccount.id, - ); - expect(mockPremiumInterestStateService.clearPremiumInterest).not.toHaveBeenCalled(); + // Should route to /vault because hasPremiumInterest signal is true + // No additional service calls should be made in onComplete + expect(mockPremiumInterestStateService.getPremiumInterest).toHaveBeenCalledTimes(1); // Only from ngOnInit + expect(mockPremiumInterestStateService.clearPremiumInterest).toHaveBeenCalledTimes(1); // Only from ngOnInit + expect(mockRouter.navigate).toHaveBeenCalledWith(["/vault"]); + expect(mockDialogRef.close).toHaveBeenCalledWith({ + status: "upgradedToPremium", + organizationId: null, + }); + }); + + it("should close dialog when upgrading to families (premium interest not relevant)", async () => { + const result: UpgradePaymentResult = { + status: "upgradedToFamilies", + organizationId: "org-123", + }; + + await component["onComplete"](result); + + // Premium interest logic only runs for premium upgrades, not families + expect(mockDialogRef.close).toHaveBeenCalledWith({ + status: "upgradedToFamilies", + organizationId: "org-123", + }); + }); + + it("should use standard redirect when upgrading to premium without premium interest", async () => { + const customDialogData: UnifiedUpgradeDialogParams = { + account: mockAccount, + redirectOnCompletion: true, + }; + + // No premium interest + mockPremiumInterestStateService.getPremiumInterest.mockResolvedValue(false); + mockRouter.navigate.mockResolvedValue(true); + + const { component: customComponent } = await createComponentWithDialogData(customDialogData); + + // Verify no premium interest was set during ngOnInit + expect(customComponent["hasPremiumInterest"]()).toBe(false); + + const result: UpgradePaymentResult = { + status: "upgradedToPremium", + organizationId: null, + }; + + await customComponent["onComplete"](result); + + // Should use standard redirect because hasPremiumInterest signal is false expect(mockRouter.navigate).toHaveBeenCalledWith([ "/settings/subscription/user-subscription", ]); @@ -354,70 +358,44 @@ describe("UnifiedUpgradeDialogComponent", () => { }); }); - describe("onCloseClicked with premium interest", () => { - it("should clear premium interest when modal is closed", async () => { - mockPremiumInterestStateService.clearPremiumInterest.mockResolvedValue(); - + describe("onCloseClicked", () => { + it("should close dialog without clearing premium interest (cleared in ngOnInit)", async () => { await component["onCloseClicked"](); - expect(mockPremiumInterestStateService.clearPremiumInterest).toHaveBeenCalledWith( - mockAccount.id, - ); + // Premium interest should have been cleared only once during ngOnInit, not again here + expect(mockPremiumInterestStateService.clearPremiumInterest).toHaveBeenCalledTimes(0); expect(mockDialogRef.close).toHaveBeenCalledWith({ status: "closed" }); }); }); - describe("previousStep with premium interest", () => { - it("should NOT clear premium interest when navigating between steps", async () => { + describe("previousStep", () => { + it("should go back to plan selection when on payment step", async () => { component["step"].set(UnifiedUpgradeDialogStep.Payment); component["selectedPlan"].set(PersonalSubscriptionPricingTierIds.Premium); await component["previousStep"](); - expect(mockPremiumInterestStateService.clearPremiumInterest).not.toHaveBeenCalled(); expect(component["step"]()).toBe(UnifiedUpgradeDialogStep.PlanSelection); expect(component["selectedPlan"]()).toBeNull(); + expect(mockPremiumInterestStateService.clearPremiumInterest).toHaveBeenCalledTimes(0); }); - it("should clear premium interest when backing out of dialog completely", async () => { - TestBed.resetTestingModule(); - + it("should close dialog when backing out from plan selection step (no premium interest cleared)", async () => { const customDialogData: UnifiedUpgradeDialogParams = { account: mockAccount, initialStep: UnifiedUpgradeDialogStep.Payment, selectedPlan: PersonalSubscriptionPricingTierIds.Premium, }; - mockPremiumInterestStateService.clearPremiumInterest.mockResolvedValue(); + mockPremiumInterestStateService.getPremiumInterest.mockResolvedValue(false); - await TestBed.configureTestingModule({ - imports: [NoopAnimationsModule, UnifiedUpgradeDialogComponent], - providers: [ - { provide: DialogRef, useValue: mockDialogRef }, - { provide: DIALOG_DATA, useValue: customDialogData }, - { provide: Router, useValue: mockRouter }, - { provide: PremiumInterestStateService, useValue: mockPremiumInterestStateService }, - ], - }) - .overrideComponent(UnifiedUpgradeDialogComponent, { - remove: { - imports: [UpgradeAccountComponent, UpgradePaymentComponent], - }, - add: { - imports: [MockUpgradeAccountComponent, MockUpgradePaymentComponent], - }, - }) - .compileComponents(); - - const customFixture = TestBed.createComponent(UnifiedUpgradeDialogComponent); - const customComponent = customFixture.componentInstance; - customFixture.detectChanges(); + const { component: customComponent } = await createComponentWithDialogData(customDialogData); + // Start at payment step, go back once to reach plan selection, then go back again to close await customComponent["previousStep"](); - expect(mockPremiumInterestStateService.clearPremiumInterest).toHaveBeenCalledWith( - mockAccount.id, - ); + // Premium interest cleared only in ngOnInit, not in previousStep + expect(mockPremiumInterestStateService.clearPremiumInterest).toHaveBeenCalledTimes(0); expect(mockDialogRef.close).toHaveBeenCalledWith({ status: "closed" }); }); }); diff --git a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts index 02d48e8d8f4..222bf77715c 100644 --- a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts @@ -1,6 +1,6 @@ import { DIALOG_DATA } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; -import { Component, Inject, OnInit, signal } from "@angular/core"; +import { ChangeDetectionStrategy, Component, Inject, OnInit, signal } from "@angular/core"; import { Router } from "@angular/router"; import { PremiumInterestStateService } from "@bitwarden/angular/billing/services/premium-interest/premium-interest-state.service.abstraction"; @@ -63,10 +63,9 @@ export type UnifiedUpgradeDialogParams = { redirectOnCompletion?: boolean; }; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-unified-upgrade-dialog", + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CommonModule, DialogModule, @@ -87,6 +86,7 @@ export class UnifiedUpgradeDialogComponent implements OnInit { protected readonly account = signal(null); protected readonly planSelectionStepTitleOverride = signal(null); protected readonly hideContinueWithoutUpgradingButton = signal(false); + protected readonly hasPremiumInterest = signal(false); protected readonly PaymentStep = UnifiedUpgradeDialogStep.Payment; protected readonly PlanSelectionStep = UnifiedUpgradeDialogStep.PlanSelection; @@ -98,7 +98,7 @@ export class UnifiedUpgradeDialogComponent implements OnInit { private premiumInterestStateService: PremiumInterestStateService, ) {} - ngOnInit(): void { + async ngOnInit(): Promise { this.account.set(this.params.account); this.step.set(this.params.initialStep ?? UnifiedUpgradeDialogStep.PlanSelection); this.selectedPlan.set(this.params.selectedPlan ?? null); @@ -106,6 +106,19 @@ export class UnifiedUpgradeDialogComponent implements OnInit { this.hideContinueWithoutUpgradingButton.set( this.params.hideContinueWithoutUpgradingButton ?? false, ); + + /* + * Check if the user has premium interest at the point we open the dialog. + * If they do, record it on a component-level signal and clear the user's premium interest. + * This prevents us from having to clear it at every dialog conclusion point. + * */ + const hasPremiumInterest = await this.premiumInterestStateService.getPremiumInterest( + this.params.account.id, + ); + if (hasPremiumInterest) { + this.hasPremiumInterest.set(true); + await this.premiumInterestStateService.clearPremiumInterest(this.params.account.id); + } } protected onPlanSelected(planId: PersonalSubscriptionPricingTierId): void { @@ -113,8 +126,6 @@ export class UnifiedUpgradeDialogComponent implements OnInit { this.nextStep(); } protected async onCloseClicked(): Promise { - // Clear premium interest when user closes/abandons modal - await this.premiumInterestStateService.clearPremiumInterest(this.params.account.id); this.close({ status: UnifiedUpgradeDialogStatus.Closed }); } @@ -135,8 +146,6 @@ export class UnifiedUpgradeDialogComponent implements OnInit { this.step.set(UnifiedUpgradeDialogStep.PlanSelection); this.selectedPlan.set(null); } else { - // Clear premium interest when backing out of dialog completely - await this.premiumInterestStateService.clearPremiumInterest(this.params.account.id); this.close({ status: UnifiedUpgradeDialogStatus.Closed }); } } @@ -161,11 +170,7 @@ export class UnifiedUpgradeDialogComponent implements OnInit { // Check premium interest and route to vault for marketing-initiated premium upgrades if (status === UnifiedUpgradeDialogStatus.UpgradedToPremium) { - const hasPremiumInterest = await this.premiumInterestStateService.getPremiumInterest( - this.params.account.id, - ); - if (hasPremiumInterest) { - await this.premiumInterestStateService.clearPremiumInterest(this.params.account.id); + if (this.hasPremiumInterest()) { await this.router.navigate(["/vault"]); return; // Exit early, don't use redirectOnCompletion } diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts index a4089d7a47a..2ac44ff72db 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts @@ -1,15 +1,15 @@ import { CdkTrapFocus } from "@angular/cdk/a11y"; import { CommonModule } from "@angular/common"; -import { Component, DestroyRef, OnInit, computed, input, output, signal } from "@angular/core"; +import { Component, computed, DestroyRef, input, OnInit, output, signal } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { catchError, of } from "rxjs"; +import { SubscriptionPricingCardDetails } from "@bitwarden/angular/billing/types/subscription-pricing-card-details"; import { SubscriptionPricingServiceAbstraction } from "@bitwarden/common/billing/abstractions/subscription-pricing.service.abstraction"; import { PersonalSubscriptionPricingTier, PersonalSubscriptionPricingTierId, PersonalSubscriptionPricingTierIds, - SubscriptionCadence, SubscriptionCadenceIds, } from "@bitwarden/common/billing/types/subscription-pricing-tier"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -32,14 +32,6 @@ export type UpgradeAccountResult = { plan: PersonalSubscriptionPricingTierId | null; }; -type CardDetails = { - title: string; - tagline: string; - price: { amount: number; cadence: SubscriptionCadence }; - button: { text: string; type: ButtonType }; - features: string[]; -}; - // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ @@ -60,8 +52,8 @@ export class UpgradeAccountComponent implements OnInit { planSelected = output(); closeClicked = output(); protected readonly loading = signal(true); - protected premiumCardDetails!: CardDetails; - protected familiesCardDetails!: CardDetails; + protected premiumCardDetails!: SubscriptionPricingCardDetails; + protected familiesCardDetails!: SubscriptionPricingCardDetails; protected familiesPlanType = PersonalSubscriptionPricingTierIds.Families; protected premiumPlanType = PersonalSubscriptionPricingTierIds.Premium; @@ -122,14 +114,16 @@ export class UpgradeAccountComponent implements OnInit { private createCardDetails( tier: PersonalSubscriptionPricingTier, buttonType: ButtonType, - ): CardDetails { + ): SubscriptionPricingCardDetails { return { title: tier.name, tagline: tier.description, - price: { - amount: tier.passwordManager.annualPrice / 12, - cadence: SubscriptionCadenceIds.Monthly, - }, + price: tier.passwordManager.annualPrice + ? { + amount: tier.passwordManager.annualPrice / 12, + cadence: SubscriptionCadenceIds.Monthly, + } + : undefined, button: { text: this.i18nService.t( this.isFamiliesPlan(tier.id) ? "startFreeFamiliesTrial" : "upgradeToPremium", diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts index 94f1c816168..ae18ab4c629 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts @@ -200,7 +200,8 @@ export class UpgradePaymentService { } private getPasswordManagerSeats(planDetails: PlanDetails): number { - return "users" in planDetails.details.passwordManager + return "users" in planDetails.details.passwordManager && + planDetails.details.passwordManager.users ? planDetails.details.passwordManager.users : 0; } 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 0fd7746fc9d..978bb35c5c7 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 @@ -620,7 +620,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get storageGb() { - return this.sub?.maxStorageGb ? this.sub?.maxStorageGb - 1 : 0; + return Math.max( + 0, + (this.sub?.maxStorageGb ?? 0) - this.selectedPlan.PasswordManager.baseStorageGb, + ); } passwordManagerSeatTotal(plan: PlanResponse): number { @@ -644,12 +647,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return 0; } - return ( - plan.PasswordManager.additionalStoragePricePerGb * - // TODO: Eslint upgrade. Please resolve this since the null check does nothing - // eslint-disable-next-line no-constant-binary-expression - Math.abs(this.sub?.maxStorageGb ? this.sub?.maxStorageGb - 1 : 0 || 0) - ); + return plan.PasswordManager.additionalStoragePricePerGb * this.storageGb; } additionalStoragePriceMonthly(selectedPlan: PlanResponse) { 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 6234fc6e6e3..d06604ba29e 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.html +++ b/apps/web/src/app/billing/organizations/organization-plans.component.html @@ -104,7 +104,7 @@
  • {{ "gbEncryptedFileStorage" - | i18n: selectableProduct.PasswordManager.baseStorageGb + "GB" + | i18n: selectableProduct.PasswordManager.baseStorageGb + " GB" }}
  • @@ -239,7 +239,7 @@ {{ "additionalStorageIntervalDesc" | i18n - : "1 GB" + : `${selectedPlan.PasswordManager.baseStorageGb} GB` : (additionalStoragePriceMonthly(selectedPlan) | currency: "$") : ("month" | i18n) }} 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 561a3e03deb..67f6f9b0a6b 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -654,6 +654,14 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { if (this.singleOrgPolicyBlock) { return; } + + // Validate billing form for paid plans during creation + if (this.createOrganization && this.selectedPlan.type !== PlanType.Free) { + this.billingFormGroup.markAllAsTouched(); + if (this.billingFormGroup.invalid) { + return; + } + } const doSubmit = async (): Promise => { let orgId: string; if (this.createOrganization) { @@ -703,11 +711,18 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { return orgId; }; - this.formPromise = doSubmit(); - const organizationId = await this.formPromise; - this.onSuccess.emit({ organizationId: organizationId }); - // TODO: No one actually listening to this message? - this.messagingService.send("organizationCreated", { organizationId }); + try { + this.formPromise = doSubmit(); + const organizationId = await this.formPromise; + this.onSuccess.emit({ organizationId: organizationId }); + // TODO: No one actually listening to this message? + this.messagingService.send("organizationCreated", { organizationId }); + } catch (error: unknown) { + if (error instanceof Error && error.message === "Payment method validation failed") { + return; + } + throw error; + } }; protected get showTaxIdField(): boolean { @@ -826,6 +841,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { return; } const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); + if (!paymentMethod) { + throw new Error("Payment method validation failed"); + } await this.subscriberBillingClient.updatePaymentMethod( { type: "organization", data: this.organization }, paymentMethod, @@ -877,6 +895,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); + if (!paymentMethod) { + throw new Error("Payment method validation failed"); + } const billingAddress = getBillingAddressFromForm( this.billingFormGroup.controls.billingAddress, 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 0666cca2c4b..8b9b98dc390 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 @@ -38,12 +38,7 @@ {{ i.amount | currency: "$" }} - + {{ "freeForOneYear" | i18n }} @@ -52,7 +47,7 @@ {{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }} {{ calculateTotalAppliedDiscount(i.quantity * i.amount) | currency: "$" 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 e0c1a12a80f..de5d71cce5e 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 @@ -403,11 +403,13 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy } isSecretsManagerTrial(): boolean { - return ( + const isSmStandalone = this.sub?.customerDiscount?.id === "sm-standalone"; + const appliesToProduct = this.sub?.subscription?.items?.some((item) => this.sub?.customerDiscount?.appliesTo?.includes(item.productId), - ) ?? false - ); + ) ?? false; + + return isSmStandalone && appliesToProduct; } closeChangePlan() { diff --git a/apps/web/src/app/billing/services/pricing-summary.service.spec.ts b/apps/web/src/app/billing/services/pricing-summary.service.spec.ts new file mode 100644 index 00000000000..4e15d318a03 --- /dev/null +++ b/apps/web/src/app/billing/services/pricing-summary.service.spec.ts @@ -0,0 +1,232 @@ +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { PlanInterval, ProductTierType } from "@bitwarden/common/billing/enums"; +import { + BillingCustomerDiscount, + OrganizationSubscriptionResponse, +} from "@bitwarden/common/billing/models/response/organization-subscription.response"; +import { + PasswordManagerPlanFeaturesResponse, + PlanResponse, + SecretsManagerPlanFeaturesResponse, +} from "@bitwarden/common/billing/models/response/plan.response"; + +import { PricingSummaryData } from "../shared/pricing-summary/pricing-summary.component"; + +import { PricingSummaryService } from "./pricing-summary.service"; + +describe("PricingSummaryService", () => { + let service: PricingSummaryService; + + beforeEach(() => { + service = new PricingSummaryService(); + }); + + describe("getPricingSummaryData", () => { + let mockPlan: PlanResponse; + let mockSub: OrganizationSubscriptionResponse; + let mockOrganization: Organization; + + beforeEach(() => { + // Create mock plan with password manager features + mockPlan = { + productTier: ProductTierType.Teams, + PasswordManager: { + basePrice: 0, + seatPrice: 48, + baseSeats: 0, + hasAdditionalSeatsOption: true, + hasPremiumAccessOption: false, + premiumAccessOptionPrice: 0, + hasAdditionalStorageOption: true, + additionalStoragePricePerGb: 6, + baseStorageGb: 1, + } as PasswordManagerPlanFeaturesResponse, + SecretsManager: { + basePrice: 0, + seatPrice: 72, + baseSeats: 3, + hasAdditionalSeatsOption: true, + hasAdditionalServiceAccountOption: true, + additionalPricePerServiceAccount: 6, + baseServiceAccount: 50, + } as SecretsManagerPlanFeaturesResponse, + } as PlanResponse; + + // Create mock subscription + mockSub = { + seats: 5, + smSeats: 5, + smServiceAccounts: 5, + maxStorageGb: 2, + customerDiscount: null, + } as OrganizationSubscriptionResponse; + + // Create mock organization + mockOrganization = { + useSecretsManager: false, + } as Organization; + }); + + it("should calculate pricing data correctly for password manager only", async () => { + const result = await service.getPricingSummaryData( + mockPlan, + mockSub, + mockOrganization, + PlanInterval.Monthly, + false, + 50, // estimatedTax + ); + + expect(result).toEqual({ + selectedPlanInterval: "month", + passwordManagerSeats: 5, + passwordManagerSeatTotal: 240, // 48 * 5 + secretsManagerSeatTotal: 360, // 72 * 5 + additionalStorageTotal: 6, // 6 * (2 - 1) + additionalStoragePriceMonthly: 6, + additionalServiceAccountTotal: 0, // No additional service accounts (50 base vs 5 used) + totalAppliedDiscount: 0, + secretsManagerSubtotal: 360, // 0 + 360 + 0 + passwordManagerSubtotal: 246, // 0 + 240 + 6 + total: 296, // 246 + 50 (tax) - organization doesn't use secrets manager + organization: mockOrganization, + sub: mockSub, + selectedPlan: mockPlan, + selectedInterval: PlanInterval.Monthly, + discountPercentageFromSub: 0, + discountPercentage: 20, + acceptingSponsorship: false, + additionalServiceAccount: 0, // 50 - 5 = 45, which is > 0, so return 0 + storageGb: 1, + isSecretsManagerTrial: false, + estimatedTax: 50, + }); + }); + + it("should calculate pricing data correctly with secrets manager enabled", async () => { + mockOrganization.useSecretsManager = true; + + const result = await service.getPricingSummaryData( + mockPlan, + mockSub, + mockOrganization, + PlanInterval.Monthly, + false, + 50, + ); + + expect(result.total).toBe(656); // passwordManagerSubtotal (246) + secretsManagerSubtotal (360) + tax (50) + }); + + it("should handle secrets manager trial", async () => { + const result = await service.getPricingSummaryData( + mockPlan, + mockSub, + mockOrganization, + PlanInterval.Monthly, + true, // isSecretsManagerTrial + 50, + ); + + expect(result.passwordManagerSeatTotal).toBe(0); // Should be 0 during trial + expect(result.discountPercentageFromSub).toBe(0); // Should be 0 during trial + }); + + it("should handle premium access option", async () => { + mockPlan.PasswordManager.hasPremiumAccessOption = true; + mockPlan.PasswordManager.premiumAccessOptionPrice = 25; + + const result = await service.getPricingSummaryData( + mockPlan, + mockSub, + mockOrganization, + PlanInterval.Monthly, + false, + 50, + ); + + expect(result.passwordManagerSubtotal).toBe(271); // 0 + 240 + 6 + 25 + }); + + it("should handle customer discount", async () => { + mockSub.customerDiscount = { + id: "discount1", + active: true, + percentOff: 10, + appliesTo: ["subscription"], + } as BillingCustomerDiscount; + + const result = await service.getPricingSummaryData( + mockPlan, + mockSub, + mockOrganization, + PlanInterval.Monthly, + false, + 50, + ); + + expect(result.discountPercentageFromSub).toBe(10); + }); + + it("should handle zero storage calculation", async () => { + mockSub.maxStorageGb = 1; // Same as base storage + + const result = await service.getPricingSummaryData( + mockPlan, + mockSub, + mockOrganization, + PlanInterval.Monthly, + false, + 50, + ); + + expect(result.additionalStorageTotal).toBe(0); + expect(result.storageGb).toBe(0); + }); + }); + + describe("getAdditionalServiceAccount", () => { + let mockPlan: PlanResponse; + let mockSub: OrganizationSubscriptionResponse; + + beforeEach(() => { + mockPlan = { + SecretsManager: { + baseServiceAccount: 50, + } as SecretsManagerPlanFeaturesResponse, + } as PlanResponse; + + mockSub = { + smServiceAccounts: 55, + } as OrganizationSubscriptionResponse; + }); + + it("should return additional service accounts when used exceeds base", () => { + const result = service.getAdditionalServiceAccount(mockPlan, mockSub); + expect(result).toBe(5); // Math.abs(50 - 55) = 5 + }); + + it("should return 0 when used is less than or equal to base", () => { + mockSub.smServiceAccounts = 40; + const result = service.getAdditionalServiceAccount(mockPlan, mockSub); + expect(result).toBe(0); + }); + + it("should return 0 when used equals base", () => { + mockSub.smServiceAccounts = 50; + const result = service.getAdditionalServiceAccount(mockPlan, mockSub); + expect(result).toBe(0); + }); + + it("should return 0 when plan is null", () => { + const result = service.getAdditionalServiceAccount(null, mockSub); + expect(result).toBe(0); + }); + + it("should return 0 when plan has no SecretsManager", () => { + mockPlan.SecretsManager = null; + const result = service.getAdditionalServiceAccount(mockPlan, mockSub); + expect(result).toBe(0); + }); + }); +}); diff --git a/apps/web/src/app/billing/services/pricing-summary.service.ts b/apps/web/src/app/billing/services/pricing-summary.service.ts index b3c071a8b88..da2fe0e8dbb 100644 --- a/apps/web/src/app/billing/services/pricing-summary.service.ts +++ b/apps/web/src/app/billing/services/pricing-summary.service.ts @@ -31,9 +31,10 @@ export class PricingSummaryService { const additionalServiceAccount = this.getAdditionalServiceAccount(plan, sub); + const storageGb = Math.max(0, (sub?.maxStorageGb ?? 0) - plan.PasswordManager.baseStorageGb); + const additionalStorageTotal = plan.PasswordManager?.hasAdditionalStorageOption - ? plan.PasswordManager.additionalStoragePricePerGb * - (sub?.maxStorageGb ? sub.maxStorageGb - 1 : 0) + ? plan.PasswordManager.additionalStoragePricePerGb * storageGb : 0; const additionalStoragePriceMonthly = plan.PasswordManager?.additionalStoragePricePerGb || 0; @@ -66,7 +67,6 @@ export class PricingSummaryService { : (sub?.customerDiscount?.percentOff ?? 0); const discountPercentage = 20; const acceptingSponsorship = false; - const storageGb = sub?.maxStorageGb ? sub?.maxStorageGb - 1 : 0; const total = organization?.useSecretsManager ? passwordManagerSubtotal + secretsManagerSubtotal + estimatedTax diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index c0716d99716..fb42e19f863 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -69,6 +69,7 @@ import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-managemen import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.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 { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout"; import { VaultTimeout, VaultTimeoutStringType, @@ -111,6 +112,7 @@ import { } from "@bitwarden/common/platform/theming/theme-state.service"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { GeneratorServicesModule } from "@bitwarden/generator-components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfigService, @@ -124,7 +126,6 @@ import { import { SerializedMemoryStorageService } from "@bitwarden/storage-core"; import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarden/vault"; import { WebOrganizationInviteService } from "@bitwarden/web-vault/app/auth/core/services/organization-invite/web-organization-invite.service"; -import { WebSessionTimeoutSettingsComponentService } from "@bitwarden/web-vault/app/key-management/session-timeout/services/web-session-timeout-settings-component.service"; import { WebVaultPremiumUpgradePromptService } from "@bitwarden/web-vault/app/vault/services/web-premium-upgrade-prompt.service"; import { flagEnabled } from "../../utils/flags"; @@ -149,6 +150,7 @@ import { WebFileDownloadService } from "../core/web-file-download.service"; import { UserKeyRotationService } from "../key-management/key-rotation/user-key-rotation.service"; import { WebLockComponentService } from "../key-management/lock/services/web-lock-component.service"; import { WebProcessReloadService } from "../key-management/services/web-process-reload.service"; +import { WebSessionTimeoutTypeService } from "../key-management/session-timeout/services/web-session-timeout-type.service"; import { WebBiometricsService } from "../key-management/web-biometric.service"; import { WebIpcService } from "../platform/ipc/web-ipc.service"; import { WebEnvironmentService } from "../platform/web-environment.service"; @@ -469,16 +471,21 @@ const safeProviders: SafeProvider[] = [ useClass: WebSystemService, deps: [], }), + safeProvider({ + provide: SessionTimeoutTypeService, + useClass: WebSessionTimeoutTypeService, + deps: [PlatformUtilsService], + }), safeProvider({ provide: SessionTimeoutSettingsComponentService, - useClass: WebSessionTimeoutSettingsComponentService, - deps: [I18nServiceAbstraction, PlatformUtilsService], + useClass: SessionTimeoutSettingsComponentService, + deps: [I18nServiceAbstraction, SessionTimeoutTypeService, PolicyService], }), ]; @NgModule({ declarations: [], - imports: [CommonModule, JslibServicesModule], + imports: [CommonModule, JslibServicesModule, GeneratorServicesModule], // Do not register your dependency here! Add it to the typesafeProviders array using the helper function providers: safeProviders, }) diff --git a/apps/web/src/app/core/event.service.ts b/apps/web/src/app/core/event.service.ts index 05a7f5aa64c..55d5524c2fa 100644 --- a/apps/web/src/app/core/event.service.ts +++ b/apps/web/src/app/core/event.service.ts @@ -372,7 +372,7 @@ export class EventService { msg = humanReadableMsg = this.i18nService.t("enabledSso"); break; case EventType.Organization_DisabledSso: - msg = humanReadableMsg = this.i18nService.t("disabledSso"); + msg = humanReadableMsg = this.i18nService.t("ssoTurnedOff"); break; case EventType.Organization_EnabledKeyConnector: msg = humanReadableMsg = this.i18nService.t("enabledKeyConnector"); diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 71e3578a7c6..929f1489a61 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -1,5 +1,4 @@ -import { DOCUMENT } from "@angular/common"; -import { Inject, Injectable } from "@angular/core"; +import { Inject, Injectable, DOCUMENT } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; diff --git a/apps/web/src/app/core/web-platform-utils.service.ts b/apps/web/src/app/core/web-platform-utils.service.ts index dea4323bcc4..037ee0f9060 100644 --- a/apps/web/src/app/core/web-platform-utils.service.ts +++ b/apps/web/src/app/core/web-platform-utils.service.ts @@ -219,4 +219,8 @@ export class WebPlatformUtilsService implements PlatformUtilsService { getAutofillKeyboardShortcut(): Promise { return null; } + + packageType(): Promise { + return null; + } } diff --git a/apps/web/src/app/key-management/data-recovery/data-recovery.component.html b/apps/web/src/app/key-management/data-recovery/data-recovery.component.html new file mode 100644 index 00000000000..f357e516115 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/data-recovery.component.html @@ -0,0 +1,75 @@ +

    {{ "dataRecoveryTitle" | i18n }}

    + +
    +

    + {{ "dataRecoveryDescription" | i18n }} +

    + + @if (!diagnosticsCompleted() && !recoveryCompleted()) { + + } + +
    + @for (step of steps(); track $index) { + @if ( + ($index === 0 && hasStarted()) || + ($index > 0 && + (steps()[$index - 1].status === StepStatus.Completed || + steps()[$index - 1].status === StepStatus.Failed)) + ) { +
    +
    + @if (step.status === StepStatus.Failed) { + + } @else if (step.status === StepStatus.Completed) { + + } @else if (step.status === StepStatus.InProgress) { + + } @else { + + } +
    +
    + + {{ step.title }} + +
    +
    + } + } +
    + + @if (diagnosticsCompleted()) { +
    + @if (hasIssues() && !recoveryCompleted()) { + + } + +
    + } +
    diff --git a/apps/web/src/app/key-management/data-recovery/data-recovery.component.spec.ts b/apps/web/src/app/key-management/data-recovery/data-recovery.component.spec.ts new file mode 100644 index 00000000000..1976a8dfe27 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/data-recovery.component.spec.ts @@ -0,0 +1,348 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; +import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; +import { DialogService } from "@bitwarden/components"; +import { KeyService, UserAsymmetricKeysRegenerationService } from "@bitwarden/key-management"; +import { LogService } from "@bitwarden/logging"; + +import { DataRecoveryComponent, StepStatus } from "./data-recovery.component"; +import { RecoveryStep, RecoveryWorkingData } from "./steps"; + +// Mock SdkLoadService +jest.mock("@bitwarden/common/platform/abstractions/sdk/sdk-load.service", () => ({ + SdkLoadService: { + Ready: Promise.resolve(), + }, +})); + +describe("DataRecoveryComponent", () => { + let component: DataRecoveryComponent; + let fixture: ComponentFixture; + + // Mock Services + let mockI18nService: MockProxy; + let mockApiService: MockProxy; + let mockAccountService: FakeAccountService; + let mockKeyService: MockProxy; + let mockFolderApiService: MockProxy; + let mockCipherEncryptService: MockProxy; + let mockDialogService: MockProxy; + let mockPrivateKeyRegenerationService: MockProxy; + let mockLogService: MockProxy; + let mockCryptoFunctionService: MockProxy; + let mockFileDownloadService: MockProxy; + + const mockUserId = "user-id" as UserId; + + beforeEach(async () => { + mockI18nService = mock(); + mockApiService = mock(); + mockAccountService = mockAccountServiceWith(mockUserId); + mockKeyService = mock(); + mockFolderApiService = mock(); + mockCipherEncryptService = mock(); + mockDialogService = mock(); + mockPrivateKeyRegenerationService = mock(); + mockLogService = mock(); + mockCryptoFunctionService = mock(); + mockFileDownloadService = mock(); + + mockI18nService.t.mockImplementation((key) => `${key}_used-i18n`); + + await TestBed.configureTestingModule({ + imports: [DataRecoveryComponent], + providers: [ + { provide: I18nService, useValue: mockI18nService }, + { provide: ApiService, useValue: mockApiService }, + { provide: AccountService, useValue: mockAccountService }, + { provide: KeyService, useValue: mockKeyService }, + { provide: FolderApiServiceAbstraction, useValue: mockFolderApiService }, + { provide: CipherEncryptionService, useValue: mockCipherEncryptService }, + { provide: DialogService, useValue: mockDialogService }, + { + provide: UserAsymmetricKeysRegenerationService, + useValue: mockPrivateKeyRegenerationService, + }, + { provide: LogService, useValue: mockLogService }, + { provide: CryptoFunctionService, useValue: mockCryptoFunctionService }, + { provide: FileDownloadService, useValue: mockFileDownloadService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DataRecoveryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe("Component Initialization", () => { + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should initialize with default signal values", () => { + expect(component.status()).toBe(StepStatus.NotStarted); + expect(component.hasStarted()).toBe(false); + expect(component.diagnosticsCompleted()).toBe(false); + expect(component.recoveryCompleted()).toBe(false); + expect(component.hasIssues()).toBe(false); + }); + + it("should initialize steps in correct order", () => { + const steps = component.steps(); + expect(steps.length).toBe(5); + expect(steps[0].title).toBe("recoveryStepUserInfoTitle_used-i18n"); + expect(steps[1].title).toBe("recoveryStepSyncTitle_used-i18n"); + expect(steps[2].title).toBe("recoveryStepPrivateKeyTitle_used-i18n"); + expect(steps[3].title).toBe("recoveryStepFoldersTitle_used-i18n"); + expect(steps[4].title).toBe("recoveryStepCipherTitle_used-i18n"); + }); + }); + + describe("runDiagnostics", () => { + let mockSteps: MockProxy[]; + + beforeEach(() => { + // Create mock steps + mockSteps = Array(5) + .fill(null) + .map(() => { + const mockStep = mock(); + mockStep.title = "mockStep"; + mockStep.runDiagnostics.mockResolvedValue(true); + mockStep.canRecover.mockReturnValue(false); + return mockStep; + }); + + // Replace recovery steps with mocks + component["recoverySteps"] = mockSteps; + }); + + it("should not run if already running", async () => { + component["status"].set(StepStatus.InProgress); + await component.runDiagnostics(); + + expect(mockSteps[0].runDiagnostics).not.toHaveBeenCalled(); + }); + + it("should set hasStarted, isRunning and initialize workingData", async () => { + await component.runDiagnostics(); + + expect(component.hasStarted()).toBe(true); + expect(component["workingData"]).toBeDefined(); + expect(component["workingData"]?.userId).toBeNull(); + expect(component["workingData"]?.userKey).toBeNull(); + }); + + it("should run diagnostics for all steps", async () => { + await component.runDiagnostics(); + + mockSteps.forEach((step) => { + expect(step.runDiagnostics).toHaveBeenCalledWith( + component["workingData"], + expect.anything(), + ); + }); + }); + + it("should mark steps as completed when diagnostics succeed", async () => { + await component.runDiagnostics(); + + const steps = component.steps(); + steps.forEach((step) => { + expect(step.status).toBe(StepStatus.Completed); + }); + }); + + it("should mark steps as failed when diagnostics return false", async () => { + mockSteps[2].runDiagnostics.mockResolvedValue(false); + + await component.runDiagnostics(); + + const steps = component.steps(); + expect(steps[2].status).toBe(StepStatus.Failed); + }); + + it("should mark steps as failed when diagnostics throw error", async () => { + mockSteps[3].runDiagnostics.mockRejectedValue(new Error("Test error")); + + await component.runDiagnostics(); + + const steps = component.steps(); + expect(steps[3].status).toBe(StepStatus.Failed); + expect(steps[3].message).toBe("Test error"); + }); + + it("should continue diagnostics even if a step fails", async () => { + mockSteps[1].runDiagnostics.mockRejectedValue(new Error("Step 1 failed")); + mockSteps[3].runDiagnostics.mockResolvedValue(false); + + await component.runDiagnostics(); + + // All steps should have been called despite failures + mockSteps.forEach((step) => { + expect(step.runDiagnostics).toHaveBeenCalled(); + }); + }); + + it("should set hasIssues to true when a step can recover", async () => { + mockSteps[2].runDiagnostics.mockResolvedValue(false); + mockSteps[2].canRecover.mockReturnValue(true); + + await component.runDiagnostics(); + + expect(component.hasIssues()).toBe(true); + }); + + it("should set hasIssues to false when no step can recover", async () => { + mockSteps.forEach((step) => { + step.runDiagnostics.mockResolvedValue(true); + step.canRecover.mockReturnValue(false); + }); + + await component.runDiagnostics(); + + expect(component.hasIssues()).toBe(false); + }); + + it("should set diagnosticsCompleted and status to completed when complete", async () => { + await component.runDiagnostics(); + + expect(component.diagnosticsCompleted()).toBe(true); + expect(component.status()).toBe(StepStatus.Completed); + }); + }); + + describe("runRecovery", () => { + let mockSteps: MockProxy[]; + let mockWorkingData: RecoveryWorkingData; + + beforeEach(() => { + mockWorkingData = { + userId: mockUserId, + userKey: null as any, + isPrivateKeyCorrupt: false, + encryptedPrivateKey: null, + ciphers: [], + folders: [], + }; + + mockSteps = Array(5) + .fill(null) + .map(() => { + const mockStep = mock(); + mockStep.title = "mockStep"; + mockStep.canRecover.mockReturnValue(false); + mockStep.runRecovery.mockResolvedValue(); + mockStep.runDiagnostics.mockResolvedValue(true); + return mockStep; + }); + + component["recoverySteps"] = mockSteps; + component["workingData"] = mockWorkingData; + }); + + it("should not run if already running", async () => { + component["status"].set(StepStatus.InProgress); + await component.runRecovery(); + + expect(mockSteps[0].runRecovery).not.toHaveBeenCalled(); + }); + + it("should not run if workingData is null", async () => { + component["workingData"] = null; + await component.runRecovery(); + + expect(mockSteps[0].runRecovery).not.toHaveBeenCalled(); + }); + + it("should only run recovery for steps that can recover", async () => { + mockSteps[1].canRecover.mockReturnValue(true); + mockSteps[3].canRecover.mockReturnValue(true); + + await component.runRecovery(); + + expect(mockSteps[0].runRecovery).not.toHaveBeenCalled(); + expect(mockSteps[1].runRecovery).toHaveBeenCalled(); + expect(mockSteps[2].runRecovery).not.toHaveBeenCalled(); + expect(mockSteps[3].runRecovery).toHaveBeenCalled(); + expect(mockSteps[4].runRecovery).not.toHaveBeenCalled(); + }); + + it("should set recoveryCompleted and status when successful", async () => { + mockSteps[1].canRecover.mockReturnValue(true); + + await component.runRecovery(); + + expect(component.recoveryCompleted()).toBe(true); + expect(component.status()).toBe(StepStatus.Completed); + }); + + it("should set status to failed if recovery is cancelled", async () => { + mockSteps[1].canRecover.mockReturnValue(true); + mockSteps[1].runRecovery.mockRejectedValue(new Error("User cancelled")); + + await component.runRecovery(); + + expect(component.status()).toBe(StepStatus.Failed); + expect(component.recoveryCompleted()).toBe(false); + }); + + it("should re-run diagnostics after recovery completes", async () => { + mockSteps[1].canRecover.mockReturnValue(true); + + await component.runRecovery(); + + // Diagnostics should be called twice: once for initial diagnostic scan + mockSteps.forEach((step) => { + expect(step.runDiagnostics).toHaveBeenCalledWith(mockWorkingData, expect.anything()); + }); + }); + + it("should update hasIssues after re-running diagnostics", async () => { + // Setup initial state with an issue + mockSteps[1].canRecover.mockReturnValue(true); + mockSteps[1].runDiagnostics.mockResolvedValue(false); + + // After recovery completes, the issue should be fixed + mockSteps[1].runRecovery.mockImplementation(() => { + // Simulate recovery fixing the issue + mockSteps[1].canRecover.mockReturnValue(false); + mockSteps[1].runDiagnostics.mockResolvedValue(true); + return Promise.resolve(); + }); + + await component.runRecovery(); + + // Verify hasIssues is updated after re-running diagnostics + expect(component.hasIssues()).toBe(false); + }); + }); + + describe("saveDiagnosticLogs", () => { + it("should call fileDownloadService with log content", () => { + component.saveDiagnosticLogs(); + + expect(mockFileDownloadService.download).toHaveBeenCalledWith({ + fileName: expect.stringContaining("data-recovery-logs-"), + blobData: expect.any(String), + blobOptions: { type: "text/plain" }, + }); + }); + + it("should include timestamp in filename", () => { + component.saveDiagnosticLogs(); + + const downloadCall = mockFileDownloadService.download.mock.calls[0][0]; + expect(downloadCall.fileName).toMatch(/data-recovery-logs-\d{4}-\d{2}-\d{2}T.*\.txt/); + }); + }); +}); diff --git a/apps/web/src/app/key-management/data-recovery/data-recovery.component.ts b/apps/web/src/app/key-management/data-recovery/data-recovery.component.ts new file mode 100644 index 00000000000..31179dfb062 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/data-recovery.component.ts @@ -0,0 +1,208 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, inject, signal } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; +import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; +import { ButtonModule, DialogService } from "@bitwarden/components"; +import { KeyService, UserAsymmetricKeysRegenerationService } from "@bitwarden/key-management"; +import { LogService } from "@bitwarden/logging"; + +import { SharedModule } from "../../shared"; + +import { LogRecorder } from "./log-recorder"; +import { + SyncStep, + UserInfoStep, + RecoveryStep, + PrivateKeyStep, + RecoveryWorkingData, + FolderStep, + CipherStep, +} from "./steps"; + +export const StepStatus = Object.freeze({ + NotStarted: 0, + InProgress: 1, + Completed: 2, + Failed: 3, +} as const); +export type StepStatus = (typeof StepStatus)[keyof typeof StepStatus]; + +interface StepState { + title: string; + status: StepStatus; + message?: string; +} + +@Component({ + selector: "app-data-recovery", + templateUrl: "data-recovery.component.html", + standalone: true, + imports: [JslibModule, ButtonModule, CommonModule, SharedModule], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DataRecoveryComponent { + protected readonly StepStatus = StepStatus; + + private i18nService = inject(I18nService); + private apiService = inject(ApiService); + private accountService = inject(AccountService); + private keyService = inject(KeyService); + private folderApiService = inject(FolderApiServiceAbstraction); + private cipherEncryptService = inject(CipherEncryptionService); + private dialogService = inject(DialogService); + private privateKeyRegenerationService = inject(UserAsymmetricKeysRegenerationService); + private cryptoFunctionService = inject(CryptoFunctionService); + private logService = inject(LogService); + private fileDownloadService = inject(FileDownloadService); + + private logger: LogRecorder = new LogRecorder(this.logService); + private recoverySteps: RecoveryStep[] = [ + new UserInfoStep(this.accountService, this.keyService), + new SyncStep(this.apiService), + new PrivateKeyStep( + this.privateKeyRegenerationService, + this.dialogService, + this.cryptoFunctionService, + ), + new FolderStep(this.folderApiService, this.dialogService), + new CipherStep(this.apiService, this.cipherEncryptService, this.dialogService), + ]; + private workingData: RecoveryWorkingData | null = null; + + readonly status = signal(StepStatus.NotStarted); + readonly hasStarted = signal(false); + readonly diagnosticsCompleted = signal(false); + readonly recoveryCompleted = signal(false); + readonly steps = signal( + this.recoverySteps.map((step) => ({ + title: this.i18nService.t(step.title), + status: StepStatus.NotStarted, + })), + ); + readonly hasIssues = signal(false); + + runDiagnostics = async () => { + if (this.status() === StepStatus.InProgress) { + return; + } + + this.hasStarted.set(true); + this.status.set(StepStatus.InProgress); + this.diagnosticsCompleted.set(false); + + this.logger.record("Starting diagnostics..."); + this.workingData = { + userId: null, + userKey: null, + isPrivateKeyCorrupt: false, + encryptedPrivateKey: null, + ciphers: [], + folders: [], + }; + + await this.runDiagnosticsInternal(); + + this.status.set(StepStatus.Completed); + this.diagnosticsCompleted.set(true); + }; + + private async runDiagnosticsInternal() { + if (!this.workingData) { + this.logger.record("No working data available"); + return; + } + + const currentSteps = this.steps(); + let hasAnyFailures = false; + + for (let i = 0; i < this.recoverySteps.length; i++) { + const step = this.recoverySteps[i]; + currentSteps[i].status = StepStatus.InProgress; + this.steps.set([...currentSteps]); + + this.logger.record(`Running diagnostics for step: ${step.title}`); + try { + const success = await step.runDiagnostics(this.workingData, this.logger); + currentSteps[i].status = success ? StepStatus.Completed : StepStatus.Failed; + if (!success) { + hasAnyFailures = true; + } + this.steps.set([...currentSteps]); + this.logger.record(`Diagnostics completed for step: ${step.title}`); + } catch (error) { + currentSteps[i].status = StepStatus.Failed; + currentSteps[i].message = (error as Error).message; + this.steps.set([...currentSteps]); + this.logger.record( + `Diagnostics failed for step: ${step.title} with error: ${(error as Error).message}`, + ); + hasAnyFailures = true; + } + } + + if (hasAnyFailures) { + this.logger.record("Diagnostics completed with errors"); + } else { + this.logger.record("Diagnostics completed successfully"); + } + + // Check if any recovery can be performed + const canRecoverAnyStep = this.recoverySteps.some((step) => step.canRecover(this.workingData!)); + this.hasIssues.set(canRecoverAnyStep); + } + + runRecovery = async () => { + if (this.status() === StepStatus.InProgress || !this.workingData) { + return; + } + + this.status.set(StepStatus.InProgress); + this.recoveryCompleted.set(false); + + this.logger.record("Starting recovery process..."); + + try { + for (let i = 0; i < this.recoverySteps.length; i++) { + const step = this.recoverySteps[i]; + if (step.canRecover(this.workingData)) { + this.logger.record(`Running recovery for step: ${step.title}`); + await step.runRecovery(this.workingData, this.logger); + } + } + + this.logger.record("Recovery process completed"); + this.recoveryCompleted.set(true); + + // Re-run diagnostics after recovery + this.logger.record("Re-running diagnostics to verify recovery..."); + await this.runDiagnosticsInternal(); + + this.status.set(StepStatus.Completed); + } catch (error) { + this.logger.record(`Recovery process cancelled or failed: ${(error as Error).message}`); + this.status.set(StepStatus.Failed); + } + }; + + saveDiagnosticLogs = () => { + const logs = this.logger.getLogs(); + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + const filename = `data-recovery-logs-${timestamp}.txt`; + + const logContent = logs.join("\n"); + this.fileDownloadService.download({ + fileName: filename, + blobData: logContent, + blobOptions: { type: "text/plain" }, + }); + + this.logger.record("Diagnostic logs saved"); + }; +} diff --git a/apps/web/src/app/key-management/data-recovery/log-recorder.ts b/apps/web/src/app/key-management/data-recovery/log-recorder.ts new file mode 100644 index 00000000000..1bca90de48d --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/log-recorder.ts @@ -0,0 +1,19 @@ +import { LogService } from "@bitwarden/logging"; + +/** + * Record logs during the data recovery process. This only keeps them in memory and does not persist them anywhere. + */ +export class LogRecorder { + private logs: string[] = []; + + constructor(private logService: LogService) {} + + record(message: string) { + this.logs.push(message); + this.logService.info(`[DataRecovery] ${message}`); + } + + getLogs(): string[] { + return [...this.logs]; + } +} diff --git a/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts new file mode 100644 index 00000000000..34e8cbdc9f3 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/cipher-step.ts @@ -0,0 +1,81 @@ +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; +import { DialogService } from "@bitwarden/components"; + +import { LogRecorder } from "../log-recorder"; + +import { RecoveryStep, RecoveryWorkingData } from "./recovery-step"; + +export class CipherStep implements RecoveryStep { + title = "recoveryStepCipherTitle"; + + private undecryptableCipherIds: string[] = []; + + constructor( + private apiService: ApiService, + private cipherService: CipherEncryptionService, + private dialogService: DialogService, + ) {} + + async runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + if (!workingData.userId) { + logger.record("Missing user ID"); + return false; + } + + this.undecryptableCipherIds = []; + for (const cipher of workingData.ciphers) { + try { + await this.cipherService.decrypt(cipher, workingData.userId); + } catch { + logger.record(`Cipher ID ${cipher.id} was undecryptable`); + this.undecryptableCipherIds.push(cipher.id); + } + } + logger.record(`Found ${this.undecryptableCipherIds.length} undecryptable ciphers`); + + return this.undecryptableCipherIds.length == 0; + } + + canRecover(workingData: RecoveryWorkingData): boolean { + return this.undecryptableCipherIds.length > 0; + } + + async runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + // Recovery means deleting the broken ciphers. + if (this.undecryptableCipherIds.length === 0) { + logger.record("No undecryptable ciphers to recover"); + return; + } + + logger.record(`Showing confirmation dialog for ${this.undecryptableCipherIds.length} ciphers`); + + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "recoveryDeleteCiphersTitle" }, + content: { key: "recoveryDeleteCiphersDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: { key: "cancel" }, + type: "danger", + }); + + if (!confirmed) { + logger.record("User cancelled cipher deletion"); + throw new Error("Cipher recovery cancelled by user"); + } + + logger.record(`Deleting ${this.undecryptableCipherIds.length} ciphers`); + + for (const cipherId of this.undecryptableCipherIds) { + try { + await this.apiService.deleteCipher(cipherId); + logger.record(`Deleted cipher ${cipherId}`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.record(`Failed to delete cipher ${cipherId}: ${errorMessage}`); + throw error; + } + } + + logger.record(`Successfully deleted ${this.undecryptableCipherIds.length} ciphers`); + } +} diff --git a/apps/web/src/app/key-management/data-recovery/steps/folder-step.ts b/apps/web/src/app/key-management/data-recovery/steps/folder-step.ts new file mode 100644 index 00000000000..bc0ae31efba --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/folder-step.ts @@ -0,0 +1,97 @@ +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; +import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; +import { DialogService } from "@bitwarden/components"; +import { PureCrypto } from "@bitwarden/sdk-internal"; + +import { LogRecorder } from "../log-recorder"; + +import { RecoveryStep, RecoveryWorkingData } from "./recovery-step"; + +export class FolderStep implements RecoveryStep { + title = "recoveryStepFoldersTitle"; + + private undecryptableFolderIds: string[] = []; + + constructor( + private folderService: FolderApiServiceAbstraction, + private dialogService: DialogService, + ) {} + + async runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + if (!workingData.userKey) { + logger.record("Missing user key"); + return false; + } + + this.undecryptableFolderIds = []; + for (const folder of workingData.folders) { + if (!folder.name?.encryptedString) { + logger.record(`Folder ID ${folder.id} has no name`); + this.undecryptableFolderIds.push(folder.id); + continue; + } + try { + await SdkLoadService.Ready; + PureCrypto.symmetric_decrypt_string( + folder.name.encryptedString, + workingData.userKey.toEncoded(), + ); + } catch { + logger.record(`Folder name for folder ID ${folder.id} was undecryptable`); + this.undecryptableFolderIds.push(folder.id); + } + } + logger.record(`Found ${this.undecryptableFolderIds.length} undecryptable folders`); + + return this.undecryptableFolderIds.length == 0; + } + + canRecover(workingData: RecoveryWorkingData): boolean { + return this.undecryptableFolderIds.length > 0; + } + + async runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + // Recovery means deleting the broken folders. + if (this.undecryptableFolderIds.length === 0) { + logger.record("No undecryptable folders to recover"); + return; + } + + if (!workingData.userId) { + logger.record("Missing user ID"); + throw new Error("Missing user ID"); + } + + logger.record(`Showing confirmation dialog for ${this.undecryptableFolderIds.length} folders`); + + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "recoveryDeleteFoldersTitle" }, + content: { key: "recoveryDeleteFoldersDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: { key: "cancel" }, + type: "danger", + }); + + if (!confirmed) { + logger.record("User cancelled folder deletion"); + throw new Error("Folder recovery cancelled by user"); + } + + logger.record(`Deleting ${this.undecryptableFolderIds.length} folders`); + + for (const folderId of this.undecryptableFolderIds) { + try { + await this.folderService.delete(folderId, workingData.userId); + logger.record(`Deleted folder ${folderId}`); + } catch (error) { + logger.record(`Failed to delete folder ${folderId}: ${error}`); + } + } + + logger.record(`Successfully deleted ${this.undecryptableFolderIds.length} folders`); + } + + getUndecryptableFolderIds(): string[] { + return this.undecryptableFolderIds; + } +} diff --git a/apps/web/src/app/key-management/data-recovery/steps/index.ts b/apps/web/src/app/key-management/data-recovery/steps/index.ts new file mode 100644 index 00000000000..caf3cdb34ef --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/index.ts @@ -0,0 +1,6 @@ +export * from "./sync-step"; +export * from "./user-info-step"; +export * from "./recovery-step"; +export * from "./private-key-step"; +export * from "./folder-step"; +export * from "./cipher-step"; diff --git a/apps/web/src/app/key-management/data-recovery/steps/private-key-step.ts b/apps/web/src/app/key-management/data-recovery/steps/private-key-step.ts new file mode 100644 index 00000000000..82c20c466b8 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/private-key-step.ts @@ -0,0 +1,93 @@ +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; +import { EncryptionType } from "@bitwarden/common/platform/enums"; +import { DialogService } from "@bitwarden/components"; +import { UserAsymmetricKeysRegenerationService } from "@bitwarden/key-management"; +import { PureCrypto } from "@bitwarden/sdk-internal"; + +import { LogRecorder } from "../log-recorder"; + +import { RecoveryStep, RecoveryWorkingData } from "./recovery-step"; + +export class PrivateKeyStep implements RecoveryStep { + title = "recoveryStepPrivateKeyTitle"; + + constructor( + private privateKeyRegenerationService: UserAsymmetricKeysRegenerationService, + private dialogService: DialogService, + private cryptoFunctionService: CryptoFunctionService, + ) {} + + async runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + if (!workingData.userId || !workingData.userKey) { + logger.record("Missing user ID or user key"); + return false; + } + + // Make sure the private key decrypts properly and is not somehow encrypted by a different user key / broken during key rotation. + const encryptedPrivateKey = workingData.encryptedPrivateKey; + if (!encryptedPrivateKey) { + logger.record("No encrypted private key found"); + return false; + } + logger.record("Private key length: " + encryptedPrivateKey.length); + let privateKey: Uint8Array; + try { + await SdkLoadService.Ready; + privateKey = PureCrypto.unwrap_decapsulation_key( + encryptedPrivateKey, + workingData.userKey.toEncoded(), + ); + } catch { + logger.record("Private key was un-decryptable"); + workingData.isPrivateKeyCorrupt = true; + return false; + } + + // Make sure the contained private key can be parsed and the public key can be derived. If not, then the private key may be corrupt / generated with an incompatible ASN.1 representation / with incompatible padding. + try { + const publicKey = await this.cryptoFunctionService.rsaExtractPublicKey(privateKey); + logger.record("Public key length: " + publicKey.length); + } catch { + logger.record("Public key could not be derived; private key is corrupt"); + workingData.isPrivateKeyCorrupt = true; + return false; + } + + return true; + } + + canRecover(workingData: RecoveryWorkingData): boolean { + // Only support recovery on V1 users. + return ( + workingData.isPrivateKeyCorrupt && + workingData.userKey !== null && + workingData.userKey.inner().type === EncryptionType.AesCbc256_HmacSha256_B64 + ); + } + + async runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + // The recovery step is to replace the key pair. Currently, this only works if the user is not using emergency access or is part of an organization. + // This is because this will break emergency access enrollments / organization memberships / provider memberships. + logger.record("Showing confirmation dialog for private key replacement"); + + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "recoveryReplacePrivateKeyTitle" }, + content: { key: "recoveryReplacePrivateKeyDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: { key: "cancel" }, + type: "danger", + }); + + if (!confirmed) { + logger.record("User cancelled private key replacement"); + throw new Error("Private key recovery cancelled by user"); + } + + logger.record("Replacing private key"); + await this.privateKeyRegenerationService.regenerateUserPublicKeyEncryptionKeyPair( + workingData.userId!, + ); + logger.record("Private key replaced successfully"); + } +} diff --git a/apps/web/src/app/key-management/data-recovery/steps/recovery-step.ts b/apps/web/src/app/key-management/data-recovery/steps/recovery-step.ts new file mode 100644 index 00000000000..265d7c68284 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/recovery-step.ts @@ -0,0 +1,43 @@ +import { WrappedPrivateKey } from "@bitwarden/common/key-management/types"; +import { UserKey } from "@bitwarden/common/types/key"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { Folder } from "@bitwarden/common/vault/models/domain/folder"; +import { UserId } from "@bitwarden/user-core"; + +import { LogRecorder } from "../log-recorder"; + +/** + * A recovery step performs diagnostics and recovery actions on a specific domain, such as ciphers. + */ +export abstract class RecoveryStep { + /** Title of the recovery step, as an i18n key. */ + abstract title: string; + + /** + * Runs diagnostics on the provided working data. + * Returns true if no issues were found, false otherwise. + */ + abstract runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise; + + /** + * Returns whether recovery can be performed + */ + abstract canRecover(workingData: RecoveryWorkingData): boolean; + + /** + * Performs recovery on the provided working data. + */ + abstract runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise; +} + +/** + * Data used during the recovery process, passed between steps. + */ +export type RecoveryWorkingData = { + userId: UserId | null; + userKey: UserKey | null; + encryptedPrivateKey: WrappedPrivateKey | null; + isPrivateKeyCorrupt: boolean; + ciphers: Cipher[]; + folders: Folder[]; +}; diff --git a/apps/web/src/app/key-management/data-recovery/steps/sync-step.ts b/apps/web/src/app/key-management/data-recovery/steps/sync-step.ts new file mode 100644 index 00000000000..f0adb1e0b46 --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/sync-step.ts @@ -0,0 +1,43 @@ +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; +import { FolderData } from "@bitwarden/common/vault/models/data/folder.data"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { Folder } from "@bitwarden/common/vault/models/domain/folder"; + +import { LogRecorder } from "../log-recorder"; + +import { RecoveryStep, RecoveryWorkingData } from "./recovery-step"; + +export class SyncStep implements RecoveryStep { + title = "recoveryStepSyncTitle"; + + constructor(private apiService: ApiService) {} + + async runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + // The intent of this step is to fetch the latest data from the server. Diagnostics does not + // ever run on local data but only remote data that is recent. + const response = await this.apiService.getSync(); + + workingData.ciphers = response.ciphers.map((c) => new Cipher(new CipherData(c))); + logger.record(`Fetched ${workingData.ciphers.length} ciphers from server`); + + workingData.folders = response.folders.map((f) => new Folder(new FolderData(f))); + logger.record(`Fetched ${workingData.folders.length} folders from server`); + + workingData.encryptedPrivateKey = + response.profile?.accountKeys?.publicKeyEncryptionKeyPair?.wrappedPrivateKey ?? null; + logger.record( + `Fetched encrypted private key of length ${workingData.encryptedPrivateKey?.length ?? 0} from server`, + ); + + return true; + } + + canRecover(workingData: RecoveryWorkingData): boolean { + return false; + } + + runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + return Promise.resolve(); + } +} diff --git a/apps/web/src/app/key-management/data-recovery/steps/user-info-step.ts b/apps/web/src/app/key-management/data-recovery/steps/user-info-step.ts new file mode 100644 index 00000000000..9565b1da73b --- /dev/null +++ b/apps/web/src/app/key-management/data-recovery/steps/user-info-step.ts @@ -0,0 +1,49 @@ +import { firstValueFrom } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { EncryptionType } from "@bitwarden/common/platform/enums"; +import { KeyService } from "@bitwarden/key-management"; + +import { LogRecorder } from "../log-recorder"; + +import { RecoveryStep, RecoveryWorkingData } from "./recovery-step"; + +export class UserInfoStep implements RecoveryStep { + title = "recoveryStepUserInfoTitle"; + + constructor( + private accountService: AccountService, + private keyService: KeyService, + ) {} + + async runDiagnostics(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + if (!activeAccount) { + logger.record("No active account found"); + return false; + } + const userId = activeAccount.id; + workingData.userId = userId; + logger.record(`User ID: ${userId}`); + + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + if (!userKey) { + logger.record("No user key found"); + return false; + } + workingData.userKey = userKey; + logger.record( + `User encryption type: ${userKey.inner().type === EncryptionType.AesCbc256_HmacSha256_B64 ? "V1" : userKey.inner().type === EncryptionType.CoseEncrypt0 ? "Cose" : "Unknown"}`, + ); + + return true; + } + + canRecover(workingData: RecoveryWorkingData): boolean { + return false; + } + + runRecovery(workingData: RecoveryWorkingData, logger: LogRecorder): Promise { + return Promise.resolve(); + } +} diff --git a/apps/web/src/app/key-management/key-connector/remove-password.component.html b/apps/web/src/app/key-management/key-connector/remove-password.component.html deleted file mode 100644 index aae660ce504..00000000000 --- a/apps/web/src/app/key-management/key-connector/remove-password.component.html +++ /dev/null @@ -1,37 +0,0 @@ -
    - - {{ "loading" | i18n }} -
    - -
    -

    {{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}

    -

    {{ "organizationName" | i18n }}:

    -

    {{ organization.name }}

    -

    {{ "keyConnectorDomain" | i18n }}:

    -

    {{ organization.keyConnectorUrl }}

    - - - -
    diff --git a/apps/web/src/app/key-management/key-connector/remove-password.component.ts b/apps/web/src/app/key-management/key-connector/remove-password.component.ts deleted file mode 100644 index d9fea9409f8..00000000000 --- a/apps/web/src/app/key-management/key-connector/remove-password.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from "@angular/core"; - -import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitwarden/key-management-ui"; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "app-remove-password", - templateUrl: "remove-password.component.html", - standalone: false, -}) -export class RemovePasswordComponent extends BaseRemovePasswordComponent {} 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 b790fb8409a..f4b50b4a772 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 @@ -1096,6 +1096,9 @@ describe("KeyRotationService", () => { mockKeyService.userSigningKey$.mockReturnValue( new BehaviorSubject(TEST_VECTOR_SIGNING_KEY_V2 as WrappedSigningKey), ); + mockKeyService.userSignedPublicKey$.mockReturnValue( + new BehaviorSubject(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2 as SignedPublicKey), + ); mockSecurityStateService.accountSecurityState$.mockReturnValue( new BehaviorSubject(TEST_VECTOR_SECURITY_STATE_V2 as SignedSecurityState), ); @@ -1140,6 +1143,7 @@ describe("KeyRotationService", () => { publicKeyEncryptionKeyPair: { wrappedPrivateKey: TEST_VECTOR_PRIVATE_KEY_V2, publicKey: Utils.fromB64ToArray(TEST_VECTOR_PUBLIC_KEY_V2) as UnsignedPublicKey, + signedPublicKey: TEST_VECTOR_SIGNED_PUBLIC_KEY_V2 as SignedPublicKey, }, signingKey: TEST_VECTOR_SIGNING_KEY_V2 as WrappedSigningKey, securityState: TEST_VECTOR_SECURITY_STATE_V2 as SignedSecurityState, 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 168dbe7442e..b9bd23b12de 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 @@ -10,6 +10,7 @@ import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-st import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service"; import { + SignedPublicKey, SignedSecurityState, UnsignedPublicKey, WrappedPrivateKey, @@ -308,9 +309,11 @@ export class UserKeyRotationService { userId: asUuid(userId), kdfParams: kdfConfig.toSdkConfig(), email: email, - privateKey: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, - signingKey: undefined, - securityState: undefined, + accountCryptographicState: { + V1: { + private_key: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, + }, + }, method: { decryptedKey: { decrypted_user_key: cryptographicStateParameters.userKey.toBase64() }, }, @@ -334,9 +337,15 @@ export class UserKeyRotationService { userId: asUuid(userId), kdfParams: kdfConfig.toSdkConfig(), email: email, - privateKey: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, - signingKey: cryptographicStateParameters.signingKey, - securityState: cryptographicStateParameters.securityState, + accountCryptographicState: { + V2: { + private_key: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, + signing_key: cryptographicStateParameters.signingKey, + security_state: cryptographicStateParameters.securityState, + signed_public_key: + cryptographicStateParameters.publicKeyEncryptionKeyPair.signedPublicKey, + }, + }, method: { decryptedKey: { decrypted_user_key: cryptographicStateParameters.userKey.toBase64() }, }, @@ -632,6 +641,10 @@ export class UserKeyRotationService { this.securityStateService.accountSecurityState$(user.id), "User security state", ); + const signedPublicKey = await this.firstValueFromOrThrow( + this.keyService.userSignedPublicKey$(user.id), + "User signed public key", + ); return { masterKeyKdfConfig, @@ -642,6 +655,7 @@ export class UserKeyRotationService { publicKeyEncryptionKeyPair: { wrappedPrivateKey: currentUserKeyWrappedPrivateKey, publicKey: publicKey, + signedPublicKey: signedPublicKey!, }, signingKey: signingKey!, securityState: securityState!, @@ -679,6 +693,7 @@ export type V2CryptographicStateParameters = { publicKeyEncryptionKeyPair: { wrappedPrivateKey: WrappedPrivateKey; publicKey: UnsignedPublicKey; + signedPublicKey: SignedPublicKey; }; signingKey: WrappedSigningKey; securityState: SignedSecurityState; diff --git a/apps/web/src/app/key-management/session-timeout/services/web-session-timeout-settings-component.service.ts b/apps/web/src/app/key-management/session-timeout/services/web-session-timeout-settings-component.service.ts deleted file mode 100644 index 61836c98252..00000000000 --- a/apps/web/src/app/key-management/session-timeout/services/web-session-timeout-settings-component.service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { defer, Observable, of } from "rxjs"; - -import { - VaultTimeout, - VaultTimeoutOption, - VaultTimeoutStringType, -} from "@bitwarden/common/key-management/vault-timeout"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SessionTimeoutSettingsComponentService } from "@bitwarden/key-management-ui"; - -export class WebSessionTimeoutSettingsComponentService - implements SessionTimeoutSettingsComponentService -{ - availableTimeoutOptions$: Observable = defer(() => { - const options: VaultTimeoutOption[] = [ - { name: this.i18nService.t("oneMinute"), value: 1 }, - { name: this.i18nService.t("fiveMinutes"), value: 5 }, - { name: this.i18nService.t("fifteenMinutes"), value: 15 }, - { name: this.i18nService.t("thirtyMinutes"), value: 30 }, - { name: this.i18nService.t("oneHour"), value: 60 }, - { name: this.i18nService.t("fourHours"), value: 240 }, - { name: this.i18nService.t("onRefresh"), value: VaultTimeoutStringType.OnRestart }, - ]; - - if (this.platformUtilsService.isDev()) { - options.push({ name: this.i18nService.t("never"), value: VaultTimeoutStringType.Never }); - } - - return of(options); - }); - - constructor( - private readonly i18nService: I18nService, - private readonly platformUtilsService: PlatformUtilsService, - ) {} - - onTimeoutSave(_: VaultTimeout): void {} -} diff --git a/apps/web/src/app/key-management/session-timeout/services/web-session-timeout-type.service.spec.ts b/apps/web/src/app/key-management/session-timeout/services/web-session-timeout-type.service.spec.ts new file mode 100644 index 00000000000..40eb3e77d43 --- /dev/null +++ b/apps/web/src/app/key-management/session-timeout/services/web-session-timeout-type.service.spec.ts @@ -0,0 +1,115 @@ +import { mock } from "jest-mock-extended"; + +import { + VaultTimeoutNumberType, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { WebSessionTimeoutTypeService } from "./web-session-timeout-type.service"; + +describe("WebSessionTimeoutTypeService", () => { + let service: WebSessionTimeoutTypeService; + let mockPlatformUtilsService: jest.Mocked; + + beforeEach(() => { + mockPlatformUtilsService = mock(); + service = new WebSessionTimeoutTypeService(mockPlatformUtilsService); + }); + + describe("isAvailable", () => { + it("should return false for Immediately", async () => { + const result = await service.isAvailable(VaultTimeoutNumberType.Immediately); + + expect(result).toBe(false); + }); + + it.each([VaultTimeoutStringType.OnRestart, VaultTimeoutStringType.Custom])( + "should return true for always available type: %s", + async (timeoutType) => { + const result = await service.isAvailable(timeoutType); + + expect(result).toBe(true); + }, + ); + + it.each([VaultTimeoutNumberType.OnMinute, VaultTimeoutNumberType.EightHours])( + "should return true for numeric timeout type: %s", + async (timeoutType) => { + const result = await service.isAvailable(timeoutType); + + expect(result).toBe(true); + }, + ); + + it.each([ + VaultTimeoutStringType.OnIdle, + VaultTimeoutStringType.OnSleep, + VaultTimeoutStringType.OnLocked, + ])("should return false for unavailable timeout type: %s", async (timeoutType) => { + const result = await service.isAvailable(timeoutType); + + expect(result).toBe(false); + }); + + describe("Never availability", () => { + it("should return true when in dev mode", async () => { + mockPlatformUtilsService.isDev.mockReturnValue(true); + + const result = await service.isAvailable(VaultTimeoutStringType.Never); + + expect(result).toBe(true); + expect(mockPlatformUtilsService.isDev).toHaveBeenCalled(); + }); + + it("should return false when not in dev mode", async () => { + mockPlatformUtilsService.isDev.mockReturnValue(false); + + const result = await service.isAvailable(VaultTimeoutStringType.Never); + + expect(result).toBe(false); + expect(mockPlatformUtilsService.isDev).toHaveBeenCalled(); + }); + }); + }); + + describe("getOrPromoteToAvailable", () => { + it.each([ + VaultTimeoutNumberType.OnMinute, + VaultTimeoutNumberType.EightHours, + VaultTimeoutStringType.OnRestart, + VaultTimeoutStringType.Never, + VaultTimeoutStringType.Custom, + ])("should return the original type when it is available: %s", async (timeoutType) => { + jest.spyOn(service, "isAvailable").mockResolvedValue(true); + + const result = await service.getOrPromoteToAvailable(timeoutType); + + expect(result).toBe(timeoutType); + expect(service.isAvailable).toHaveBeenCalledWith(timeoutType); + }); + + it("should return OnMinute when Immediately is not available", async () => { + jest.spyOn(service, "isAvailable").mockResolvedValue(false); + + const result = await service.getOrPromoteToAvailable(VaultTimeoutNumberType.Immediately); + + expect(result).toBe(VaultTimeoutNumberType.OnMinute); + expect(service.isAvailable).toHaveBeenCalledWith(VaultTimeoutNumberType.Immediately); + }); + + it.each([ + VaultTimeoutStringType.OnIdle, + VaultTimeoutStringType.OnSleep, + VaultTimeoutStringType.OnLocked, + VaultTimeoutStringType.Never, + ])("should return OnRestart when type is not available: %s", async (timeoutType) => { + jest.spyOn(service, "isAvailable").mockResolvedValue(false); + + const result = await service.getOrPromoteToAvailable(timeoutType); + + expect(result).toBe(VaultTimeoutStringType.OnRestart); + expect(service.isAvailable).toHaveBeenCalledWith(timeoutType); + }); + }); +}); diff --git a/apps/web/src/app/key-management/session-timeout/services/web-session-timeout-type.service.ts b/apps/web/src/app/key-management/session-timeout/services/web-session-timeout-type.service.ts new file mode 100644 index 00000000000..458befc29a7 --- /dev/null +++ b/apps/web/src/app/key-management/session-timeout/services/web-session-timeout-type.service.ts @@ -0,0 +1,44 @@ +import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout"; +import { + isVaultTimeoutTypeNumeric, + VaultTimeout, + VaultTimeoutNumberType, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +export class WebSessionTimeoutTypeService implements SessionTimeoutTypeService { + constructor(private readonly platformUtilsService: PlatformUtilsService) {} + + async isAvailable(type: VaultTimeout): Promise { + switch (type) { + case VaultTimeoutNumberType.Immediately: + return false; + case VaultTimeoutStringType.OnRestart: + case VaultTimeoutStringType.Custom: + return true; + case VaultTimeoutStringType.Never: + return this.platformUtilsService.isDev(); + default: + if (isVaultTimeoutTypeNumeric(type)) { + return true; + } + break; + } + + return false; + } + + async getOrPromoteToAvailable(type: VaultTimeout): Promise { + const available = await this.isAvailable(type); + if (!available) { + switch (type) { + case VaultTimeoutNumberType.Immediately: + return VaultTimeoutNumberType.OnMinute; + default: + return VaultTimeoutStringType.OnRestart; + } + } + return type; + } +} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index b40b9143991..e3c9da635f9 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -51,7 +51,7 @@ import { import { canAccessEmergencyAccess } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; -import { LockComponent } from "@bitwarden/key-management-ui"; +import { LockComponent, RemovePasswordComponent } from "@bitwarden/key-management-ui"; import { premiumInterestRedirectGuard } from "@bitwarden/web-vault/app/vault/guards/premium-interest-redirect/premium-interest-redirect.guard"; import { flagEnabled, Flags } from "../utils/flags"; @@ -78,8 +78,8 @@ import { freeTrialTextResolver } from "./billing/trial-initiation/complete-trial import { EnvironmentSelectorComponent } from "./components/environment-selector/environment-selector.component"; import { RouteDataProperties } from "./core"; import { ReportsModule } from "./dirt/reports"; +import { DataRecoveryComponent } from "./key-management/data-recovery/data-recovery.component"; import { ConfirmKeyConnectorDomainComponent } from "./key-management/key-connector/confirm-key-connector-domain.component"; -import { RemovePasswordComponent } from "./key-management/key-connector/remove-password.component"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; import { UserLayoutComponent } from "./layouts/user-layout.component"; import { RequestSMAccessComponent } from "./secrets-manager/secrets-manager-landing/request-sm-access.component"; @@ -544,9 +544,9 @@ const routes: Routes = [ canActivate: [authGuard], data: { pageTitle: { - key: "removeMasterPassword", + key: "verifyYourOrganization", }, - titleId: "removeMasterPassword", + titleId: "verifyYourOrganization", pageIcon: LockIcon, } satisfies RouteDataProperties & AnonLayoutWrapperData, }, @@ -556,9 +556,9 @@ const routes: Routes = [ canActivate: [], data: { pageTitle: { - key: "confirmKeyConnectorDomain", + key: "verifyYourOrganization", }, - titleId: "confirmKeyConnectorDomain", + titleId: "verifyYourOrganization", pageIcon: DomainIcon, } satisfies RouteDataProperties & AnonLayoutWrapperData, }, @@ -696,6 +696,12 @@ const routes: Routes = [ path: "security", loadChildren: () => SecurityRoutingModule, }, + { + path: "data-recovery", + component: DataRecoveryComponent, + canActivate: [canAccessFeature(FeatureFlag.DataRecoveryTool)], + data: { titleId: "dataRecovery" } satisfies RouteDataProperties, + }, { path: "domain-rules", component: DomainRulesComponent, diff --git a/apps/web/src/app/settings/preferences.component.html b/apps/web/src/app/settings/preferences.component.html index a2e90dd5889..cdcb8973602 100644 --- a/apps/web/src/app/settings/preferences.component.html +++ b/apps/web/src/app/settings/preferences.component.html @@ -17,12 +17,12 @@ {{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
    - - + {{ send.file.fileName }}

    +

    {{ send.file.fileName }}

-
+
{{ "name" | i18n }} - {{ "deletionDate" | i18n }} + + {{ "deletionDate" | i18n }} + {{ "options" | i18n }} @@ -148,8 +150,14 @@
- - {{ s.deletionDate | date: "medium" }} + + + {{ s.deletionDate | date: "medium" }} + + @if (userCanArchive) { + + } + @if (!userCanArchive) { + + } } @if (showUnArchiveButton) { diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.spec.ts b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.spec.ts index d5f7b54f37a..9378ee54e51 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.spec.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.spec.ts @@ -72,6 +72,7 @@ describe("VaultCipherRowComponent", () => { fixture = TestBed.createComponent(VaultCipherRowComponent); component = fixture.componentInstance; + fixture.componentRef.setInput("archiveEnabled", false); overlayContainer = TestBed.inject(OverlayContainer); }); diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts index 4ea062db8d1..92c49ac218a 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts @@ -8,6 +8,7 @@ import { OnInit, Output, ViewChild, + input, } from "@angular/core"; import { CollectionView } from "@bitwarden/admin-console/common"; @@ -101,8 +102,10 @@ export class VaultCipherRowComponent implements OnInit // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals @Input() userCanArchive: boolean; + /** Archive feature is enabled */ + readonly archiveEnabled = input.required(); /** - * Enforge Org Data Ownership Policy Status + * Enforce Org Data Ownership Policy Status */ // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals @@ -142,16 +145,21 @@ export class VaultCipherRowComponent implements OnInit } protected get showArchiveButton() { + if (!this.archiveEnabled()) { + return false; + } + return ( - this.userCanArchive && - !CipherViewLikeUtils.isArchived(this.cipher) && - !CipherViewLikeUtils.isDeleted(this.cipher) && - !this.cipher.organizationId + !CipherViewLikeUtils.isArchived(this.cipher) && !CipherViewLikeUtils.isDeleted(this.cipher) ); } // If item is archived always show unarchive button, even if user is not premium protected get showUnArchiveButton() { + if (!this.archiveEnabled()) { + return false; + } + return CipherViewLikeUtils.isArchived(this.cipher); } diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.html b/apps/web/src/app/vault/components/vault-items/vault-items.component.html index cb2af9a64e5..70c44e80a39 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.html @@ -179,6 +179,7 @@ (onEvent)="event($event)" [userCanArchive]="userCanArchive" [enforceOrgDataOwnershipPolicy]="enforceOrgDataOwnershipPolicy" + [archiveEnabled]="archiveFeatureEnabled$ | async" > diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.spec.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.spec.ts index 902fc2eb5a2..1eccb4c49ce 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.spec.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.spec.ts @@ -4,6 +4,7 @@ import { of } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; @@ -54,6 +55,12 @@ describe("VaultItemsComponent", () => { t: (key: string) => key, }, }, + { + provide: CipherArchiveService, + useValue: { + hasArchiveFlagEnabled$: of(true), + }, + }, ], }); diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts index 3ab643927f1..a935314eb3a 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts @@ -7,6 +7,7 @@ import { Observable, combineLatest, map, of, startWith, switchMap } from "rxjs"; import { CollectionView, Unassigned, CollectionAdminView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { RestrictedCipherType, @@ -145,9 +146,12 @@ export class VaultItemsComponent { protected disableMenu$: Observable; private restrictedTypes: RestrictedCipherType[] = []; + protected archiveFeatureEnabled$ = this.cipherArchiveService.hasArchiveFlagEnabled$; + constructor( protected cipherAuthorizationService: CipherAuthorizationService, protected restrictedItemTypesService: RestrictedItemTypesService, + protected cipherArchiveService: CipherArchiveService, ) { this.canDeleteSelected$ = this.selection.changed.pipe( startWith(null), diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.module.ts b/apps/web/src/app/vault/components/vault-items/vault-items.module.ts index a3a92559878..a7c264114b9 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.module.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.module.ts @@ -3,6 +3,7 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { RouterModule } from "@angular/router"; +import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; import { ScrollLayoutDirective, TableModule } from "@bitwarden/components"; import { CopyCipherFieldDirective } from "@bitwarden/vault"; @@ -29,6 +30,7 @@ import { VaultItemsComponent } from "./vault-items.component"; PipesModule, CopyCipherFieldDirective, ScrollLayoutDirective, + PremiumBadgeComponent, ], declarations: [VaultItemsComponent, VaultCipherRowComponent, VaultCollectionRowComponent], exports: [VaultItemsComponent], diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts index 043ae900b40..d973fbcbbc7 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts @@ -30,6 +30,7 @@ import { import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -143,6 +144,12 @@ export default { isCipherRestricted: () => false, // No restrictions for this story }, }, + { + provide: CipherArchiveService, + useValue: { + hasArchiveFlagEnabled$: of(true), + }, + }, ], }), applicationConfig({ diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts index 0326f8455a6..8839fa5039d 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts @@ -19,8 +19,10 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; @@ -170,6 +172,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { protected restrictedItemTypesService: RestrictedItemTypesService, protected cipherService: CipherService, protected cipherArchiveService: CipherArchiveService, + private premiumUpgradePromptService: PremiumUpgradePromptService, ) {} async ngOnInit(): Promise { @@ -252,14 +255,20 @@ export class VaultFilterComponent implements OnInit, OnDestroy { }; async buildAllFilters(): Promise { - const hasArchiveFlag = await firstValueFrom(this.cipherArchiveService.hasArchiveFlagEnabled$()); + const [userId, showArchive] = await firstValueFrom( + combineLatest([ + this.accountService.activeAccount$.pipe(getUserId), + this.cipherArchiveService.hasArchiveFlagEnabled$, + ]), + ); + const builderFilter = {} as VaultFilterList; builderFilter.organizationFilter = await this.addOrganizationFilter(); builderFilter.typeFilter = await this.addTypeFilter(); builderFilter.folderFilter = await this.addFolderFilter(); builderFilter.collectionFilter = await this.addCollectionFilter(); - if (hasArchiveFlag) { - builderFilter.archiveFilter = await this.addArchiveFilter(); + if (showArchive) { + builderFilter.archiveFilter = await this.addArchiveFilter(userId); } builderFilter.trashFilter = await this.addTrashFilter(); return builderFilter; @@ -313,7 +322,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { const data$ = combineLatest([ this.restrictedItemTypesService.restricted$, - this.cipherService.cipherViews$(userId), + this.cipherService.cipherListViews$(userId), ]).pipe( map(([restrictedTypes, ciphers]) => { const restrictedForUser = restrictedTypes @@ -419,7 +428,18 @@ export class VaultFilterComponent implements OnInit, OnDestroy { return trashFilterSection; } - protected async addArchiveFilter(): Promise { + protected async addArchiveFilter(userId: UserId): Promise { + const [hasArchivedCiphers, userHasPremium] = await firstValueFrom( + combineLatest([ + this.cipherArchiveService + .archivedCiphers$(userId) + .pipe(map((archivedCiphers) => archivedCiphers.length > 0)), + this.cipherArchiveService.userHasPremium$(userId), + ]), + ); + + const promptForPremiumOnFilter = !userHasPremium && !hasArchivedCiphers; + const archiveFilterSection: VaultFilterSection = { data$: this.vaultFilterService.buildTypeTree( { @@ -442,6 +462,12 @@ export class VaultFilterComponent implements OnInit, OnDestroy { isSelectable: true, }, action: this.applyTypeFilter as (filterNode: TreeNode) => Promise, + premiumOptions: { + showBadgeForNonPremium: true, + blockFilterAction: promptForPremiumOnFilter + ? async () => await this.premiumUpgradePromptService.promptForPremium() + : undefined, + }, }; return archiveFilterSection; } 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 f7078d2a67a..66f14dcf2f6 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 @@ -105,6 +105,9 @@ *ngComponentOutlet="optionsInfo.component; injector: createInjector(f.node)" > + + +
    ) { + if (this.section?.premiumOptions?.blockFilterAction) { + await this.section.premiumOptions.blockFilterAction(); + return; + } + await this.section?.action(filterNode); } @@ -123,6 +128,10 @@ export class VaultFilterSectionComponent implements OnInit, OnDestroy { return this.section?.options; } + get premiumFeature() { + return this.section?.premiumOptions?.showBadgeForNonPremium; + } + get divider() { return this.section?.divider; } 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 f1e6222b57a..d275b1251e9 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 @@ -47,6 +47,16 @@ export type VaultFilterSection = { component: any; }; divider?: boolean; + premiumOptions?: { + /** When true, the premium badge will show on the filter for non-premium users. */ + showBadgeForNonPremium?: true; + /** + * Action to be called instead of applying the filter. + * Useful when the user does not have access to a filter (e.g., premium feature) + * and custom behavior is needed when invoking the filter. + */ + blockFilterAction?: () => Promise; + }; }; export type VaultFilterList = { diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/vault-filter-shared.module.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/vault-filter-shared.module.ts index c8becac8ef5..190ace6db63 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/vault-filter-shared.module.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/vault-filter-shared.module.ts @@ -1,5 +1,6 @@ import { NgModule } from "@angular/core"; +import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; import { SearchModule } from "@bitwarden/components"; import { SharedModule } from "../../../../shared"; @@ -7,7 +8,7 @@ import { SharedModule } from "../../../../shared"; import { VaultFilterSectionComponent } from "./components/vault-filter-section.component"; @NgModule({ - imports: [SharedModule, SearchModule], + imports: [SharedModule, SearchModule, PremiumBadgeComponent], declarations: [VaultFilterSectionComponent], exports: [SharedModule, VaultFilterSectionComponent, SearchModule], }) 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 711a34413b5..522b63c21fd 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.html +++ b/apps/web/src/app/vault/individual-vault/vault.component.html @@ -34,6 +34,16 @@ {{ trashCleanupWarning }} + +

    {{ "premiumSubscriptionEndedDesc" | i18n }}

    + {{ + "restartPremium" | i18n + }} +
    ; RoutedVaultFilterService, RoutedVaultFilterBridgeService, DefaultCipherFormConfigService, + { provide: VaultItemsTransferService, useClass: DefaultVaultItemsTransferService }, ], }) export class VaultComponent implements OnInit, OnDestroy { @@ -230,13 +233,6 @@ export class VaultComponent implements OnInit, OnDestr .pipe(map((a) => a?.id)) .pipe(switchMap((id) => this.organizationService.organizations$(id))); - protected userCanArchive$ = this.accountService.activeAccount$.pipe( - getUserId, - switchMap((userId) => { - return this.cipherArchiveService.userCanArchive$(userId); - }), - ); - emptyState$ = combineLatest([ this.currentSearchText$, this.routedVaultFilterService.filter$, @@ -295,14 +291,28 @@ export class VaultComponent implements OnInit, OnDestr }), ); - protected enforceOrgDataOwnershipPolicy$ = this.accountService.activeAccount$.pipe( - getUserId, + private userId$ = this.accountService.activeAccount$.pipe(getUserId); + + protected enforceOrgDataOwnershipPolicy$ = this.userId$.pipe( switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId), ), ); - private userId$ = this.accountService.activeAccount$.pipe(getUserId); + protected userCanArchive$ = this.userId$.pipe( + switchMap((userId) => { + return this.cipherArchiveService.userCanArchive$(userId); + }), + ); + + protected showSubscriptionEndedMessaging$ = this.userId$.pipe( + switchMap((userId) => + combineLatest([ + this.routedVaultFilterBridgeService.activeFilter$, + this.cipherArchiveService.showSubscriptionEndedMessaging$(userId), + ]).pipe(map(([activeFilter, showMessaging]) => activeFilter.isArchived && showMessaging)), + ), + ); constructor( private syncService: SyncService, @@ -341,6 +351,7 @@ export class VaultComponent implements OnInit, OnDestr private premiumUpgradePromptService: PremiumUpgradePromptService, private autoConfirmService: AutomaticUserConfirmationService, private configService: ConfigService, + private vaultItemTransferService: VaultItemsTransferService, ) {} async ngOnInit() { @@ -438,13 +449,13 @@ export class VaultComponent implements OnInit, OnDestr allowedCiphers$, filter$, this.currentSearchText$, - this.cipherArchiveService.hasArchiveFlagEnabled$(), + this.cipherArchiveService.hasArchiveFlagEnabled$, ]).pipe( filter(([ciphers, filter]) => ciphers != undefined && filter != undefined), - concatMap(async ([ciphers, filter, searchText, archiveEnabled]) => { + concatMap(async ([ciphers, filter, searchText, showArchiveVault]) => { const failedCiphers = (await firstValueFrom(this.cipherService.failedToDecryptCiphers$(activeUserId))) ?? []; - const filterFunction = createFilterFunction(filter, archiveEnabled); + const filterFunction = createFilterFunction(filter, showArchiveVault); // Append any failed to decrypt ciphers to the top of the cipher list const allCiphers = [...failedCiphers, ...ciphers]; @@ -636,6 +647,8 @@ export class VaultComponent implements OnInit, OnDestr void this.unifiedUpgradePromptService.displayUpgradePromptConditionally(); this.setupAutoConfirm(); + + void this.vaultItemTransferService.enforceOrganizationDataOwnership(activeUserId); } ngOnDestroy() { diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 0aa86168472..923e65fbf99 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GG geënkripteerde berging vir lêeraanhegsels." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Bykomende berging (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Leer meer" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 9b8dde78dd6..4999534a5f6 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "تخزين إضافي (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "معرفة المزيد" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 5181906dacc..409d2a4edde 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş saxlama sahəsi." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "YubiKey və Duo kimi mülkiyyətçi iki addımlı giriş seçimləri." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Əlavə saxlama sahəsi (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Daha ətraflı" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Güncəlləmə ayarları" + }, "deleteRecoverDesc": { "message": "Hesabınızı geri qaytarmaq və silmək üçün e-poçtunuzu aşağıda daxil edin." }, @@ -9820,6 +9856,14 @@ "message": "Çox bahadır", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "1 illik ödənişsiz" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden uzantısı quraşdırıldı!" }, - "openTheBitwardenExtension": { - "message": "Bitwarden uzantısını aç" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Bitwarden uzantısı quraşdırılıb! Giriş etmək və avto-doldurmanı başlatmaq üçün uzantını aç." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden uzantısı quraşdırıldı!" }, "openExtensionToAutofill": { "message": "Giriş etmək üçün uzantını aç və avto-doldurmağa başla." @@ -11687,6 +11720,14 @@ "message": "Öyrənmə Mərkəzi", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Uzantı açılmasa, alət çubuğundakı ikondan Bitwarden-i açmağınız ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " lazımdır.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Kömək Mərkəzi", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Şifrələmə ayarlarınızı güncəlləyin" }, - "updateSettings": { - "message": "Güncəlləmə ayarları" - }, "algorithm": { "message": "Alqoritm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Davam etmək istədiyinizə əminsiniz?" + }, + "userVerificationFailed": { + "message": "İstifadəçi doğrulaması uğursuz oldu." } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 59d5d49ab1d..d0de973f30f 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 ГБ зашыфраванага сховішча для далучаных файлаў." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Прапрыетарныя варыянты двухэтапнага ўваходу, такія як YubiKey, FIDO U2F і Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Дадатковая сховішча (ГБ)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Даведацца больш" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Увядзіце адрас сваёй электроннай ніжэй, каб аднавіць і выдаліць ваш уліковы запіс." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index eb926015996..e058bdee1bd 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB пространство за файлове, които се шифрират." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ пространство за файлове, които се шифрират.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Частно двустепенно удостоверяване чрез YubiKey и Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Вашият абонамент за платения план е приключил" + }, + "premiumSubscriptionEndedDesc": { + "message": "Ако искате отново да получите достъп до архива си, трябва да подновите платения си абонамент. Ако редактирате данните за архивиран елемент преди подновяването, той ще бъде върнат в трезора." + }, + "restartPremium": { + "message": "Подновяване на платения абонамент" + }, "additionalStorageGb": { "message": "Допълнително място [GB]" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Научете повече" }, + "migrationsFailed": { + "message": "Възникна грешка при обновяването на настройките за шифроване." + }, + "updateEncryptionSettingsTitle": { + "message": "Обновете настройките си за шифроване" + }, + "updateEncryptionSettingsDesc": { + "message": "Новите препоръчани настройки за шифроване ще подобрят сигурността на акаунта Ви. Въведете главната си парола, за да ги обновите сега." + }, + "confirmIdentityToContinue": { + "message": "Потвърдете самоличността си, за да продължите" + }, + "enterYourMasterPassword": { + "message": "Въведете главната си парола" + }, + "updateSettings": { + "message": "Обновяване на настройките" + }, "deleteRecoverDesc": { "message": "Въведете адреса на електронната си поща, за да възстановите и да изтриете абонамента си." }, @@ -5810,7 +5846,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Всички елементи ще се притежават от и съхраняват в организацията, което ще означава, че управлението, видимостта и докладите ще се извършват от организацията. Когато това и включено, всеки член ще може да съхранява елементите си в стандартна колекция. Научете повече относно управлението на ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -9820,6 +9856,14 @@ "message": "Твърде скъпо", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Превключване към безплатен план", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Превключване към безплатна организация", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Безплатно за 1 година" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Добавката на Битуорден е инсталирана!" }, - "openTheBitwardenExtension": { - "message": "Отварете добавката на Битуорден" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Добавката на Битуорден е инсталирана! Отворете я, за да се впишете и да можете да се вписвате автоматично." + "bitwardenExtensionIsInstalled": { + "message": "Добавката на Битуорден е инсталирана!" }, "openExtensionToAutofill": { "message": "Отворете добавката, за да се впишете и да можете да използвате автоматичното попълване." @@ -11687,6 +11720,14 @@ "message": "Центъра за обучения", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Ако добавката на Битуорден не се е отворила, може да трябва да отворите Битуорден от иконката ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " в лентата с инструменти.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Помощния център", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Обновете настройките си за шифроване" }, - "updateSettings": { - "message": "Обновяване на настройките" - }, "algorithm": { "message": "Алгоритъм" }, @@ -12119,13 +12157,13 @@ "message": "Започнете безплатния пробен период на Семейния план" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Блокиране на създаването на акаунти за присвоени домейни" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "Предотвратяване на създаването на потребителски акаунти извън Вашата организация чрез адреси на е-поща от присвоени домейни." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "Трябва да има присвоен домейн преди активирането на тази политика." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Задайте метод за отключване, за да може да промените действието при изтичане на времето за достъп до трезора." @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Наистина ли искате да продължите?" + }, + "userVerificationFailed": { + "message": "Проверката на потребителя беше неуспешна." } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 300f0bcd8f3..915f2bff970 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "ফাইল সংযুক্তির জন্য ১ জিবি এনক্রিপ্টেড স্থান।" }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "আরও জানুন" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 3dc0ab7494c..4c3005bda33 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 35db86e2b28..8b3e85e936e 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB d'emmagatzematge xifrat per als fitxers adjunts." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Opcions propietàries de doble factor com ara YubiKey i Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Emmagatzematge suplementari (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Més informació" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Introduïu la vostra adreça de correu electrònic a continuació per recuperar i eliminar el vostre compte." }, @@ -9820,6 +9856,14 @@ "message": "Massa car", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Gratuït durant 1 any" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 5693a5fc824..aceb40291df 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB šifrovaného úložiště pro přílohy." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ šifrovaného úložiště pro přílohy.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Volby proprietálních dvoufázových přihlášení jako je YubiKey a Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Vaše předplatné Premium skončilo" + }, + "premiumSubscriptionEndedDesc": { + "message": "Chcete-li získat přístup k Vašemu archivu, restartujte předplatné Premium. Pokud upravíte detaily archivované položky před restartováním, bude přesunuta zpět do Vašeho trezoru." + }, + "restartPremium": { + "message": "Restartovat Premium" + }, "additionalStorageGb": { "message": "Další úložiště (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Dozvědět se více" }, + "migrationsFailed": { + "message": "Došlo k chybě při aktualizaci nastavení šifrování." + }, + "updateEncryptionSettingsTitle": { + "message": "Aktualizovat nastavení šifrování" + }, + "updateEncryptionSettingsDesc": { + "message": "Nové doporučené nastavení šifrování zlepší bezpečnost Vašeho účtu. Pokud chcete aktualizovat nyní, zadejte hlavní heslo." + }, + "confirmIdentityToContinue": { + "message": "Pro pokračování potvrďte svou identitu" + }, + "enterYourMasterPassword": { + "message": "Zadejte své hlavní heslo" + }, + "updateSettings": { + "message": "Aktualizovat nastavení" + }, "deleteRecoverDesc": { "message": "Zadejte svou e-mailovou adresu níže pro obnovení a smazání účtu." }, @@ -9820,6 +9856,14 @@ "message": "Příliš drahé", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Přepínání na bezplatný tarif", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Přepínání na bezplatnou organizaci", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Zdarma na 1 rok" }, @@ -11339,14 +11383,6 @@ "openedExtensionViewAtRiskPasswords": { "message": "Rozšíření Bitwarden pro prohlížeč bylo úspěšně otevřeno. Nyní můžete zkontrolovat Vaše ohrožená 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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Rozšíření Bitwarden nainstalováno!" }, - "openTheBitwardenExtension": { - "message": "Otevřít rozšíření Bitwarden" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Rozšíření Bitwarden je nainstalováno! Otevřete rozšíření pro přihlášení a začněte používat automatické vyplňování." + "bitwardenExtensionIsInstalled": { + "message": "Rozšíření Bitwarden je nainstalováno!" }, "openExtensionToAutofill": { "message": "Otevřete rozšíření pro příhlášení a spuštění automatického vyplňování." @@ -11687,6 +11720,14 @@ "message": "Centrum výuky", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Pokud není rozšíření otevřeno, možná budete muset otevřít Bitwarden z ikony ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Centrum nápovědy", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Aktualizujte nastavení šifrování" }, - "updateSettings": { - "message": "Aktualizovat nastavení" - }, "algorithm": { "message": "Algoritmus" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Opravdu chcete pokračovat?" + }, + "userVerificationFailed": { + "message": "Ověření uživatele se nezdařilo." } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 2b0222d7096..108b1ebe23a 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index dee490c39e9..904dc69a0e3 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB krypteret lager til vedhæftede filer." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietære totrins-login muligheder, såsom YubiKey og Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Ekstra lagerplads (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Få mere at vide" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Indtast din e-mailadresse nedenfor for at gendanne og slette din konto." }, @@ -9820,6 +9856,14 @@ "message": "For dyrt", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Gratis i 1 år" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 4a83f8becc8..6c354cd9064 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -21,13 +21,13 @@ "message": "Passwort-Risiko" }, "noEditPermissions": { - "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" + "message": "Du hast keine Berechtigung, diesen Eintrag zu bearbeiten" }, "reviewAtRiskPasswords": { "message": "Überprüfe gefährdete Passwörter (schwach, kompromittiert oder wiederverwendet) in allen Anwendungen. Wähle deine kritischsten Anwendungen aus, um die Sicherheitsmaßnahmen für deine Benutzer zu priorisieren, um gefährdete Passwörter zu beseitigen." }, "reviewAtRiskLoginsPrompt": { - "message": "Überprüfung gefährdeter Zugangsdaten" + "message": "Gefährdete Zugangsdaten überprüfen" }, "dataLastUpdated": { "message": "Daten zuletzt aktualisiert: $DATE$", @@ -94,7 +94,7 @@ "message": "Mitglieder Aufgaben zuweisen, um den Fortschritt zu überwachen" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "Sobald du Anwendungen überprüft und als kritisch markiert hast, weise deinen Mitgliedern Aufgaben zu, um sie für das automatische Ausfüllen von Passwörtern zu aktivieren." }, "sendReminders": { "message": "Erinnerungen senden" @@ -179,34 +179,34 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "Keine Daten gefunden" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Importiere die Zugangsdaten deiner Organisation, um mit der Zugangsintelligenz zu beginnen. Sobald du dies getan hast, kannst du:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Anwendungen als kritisch markieren" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "Dadurch kannst du Risiken für deine wichtigsten Anwendungen zuerst beseitigen." }, "feature2Title": { - "message": "Help members improve their security" + "message": "Helfe Mitgliedern, ihre Sicherheit zu verbessern" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Weise gefährdeten Mitgliedern gezielte Sicherheitsaufgaben zu, um Zugangsdaten zu aktualisieren." }, "feature3Title": { "message": "Fortschritt überwachen" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Änderungen im Laufe der Zeit verfolgen, um Sicherheitsverbesserungen anzuzeigen." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Bericht generieren" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Du bist so weit, Berichte zu generieren. Sobald du sie generierst, kannst du folgendes tun:" }, "noCriticalApplicationsTitle": { "message": "Du hast keine Anwendung als kritisch markiert" @@ -266,13 +266,13 @@ "message": "Gefährdete Mitglieder" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Diese Mitglieder haben Zugriff auf sensible Einträge für kritische Anwendungen." }, "membersWithAtRiskPasswords": { "message": "Mitglieder mit gefährdeten Passwörtern" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Den Mitgliedern deiner Organisation wird die Aufgabe zugewiesen, sensible Passwörter zu ändern. Sie erhalten eine Benachrichtigung innerhalb ihrer Bitwarden Browser-Erweiterung." }, "membersAtRiskCount": { "message": "$COUNT$ gefährdete Mitglieder", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Diese Mitglieder melden sich bei kritischen Anwendungen mit schwachen, kompromittierten oder wiederverwendeten Passwörtern an." }, "atRiskMembersDescriptionNone": { "message": "Dies sind keine Mitglieder, die sich in Anwendungen mit schwachen, kompromittierten oder wiederverwendeten Passwörtern anmelden." @@ -386,13 +386,13 @@ "message": "Kritische Anwendungen priorisieren" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Wähle die für deine Organisation kritischsten Anwendungen aus. Danach kannst du Mitgliedern Sicherheitsaufgaben zuweisen, um Risiken zu beseitigen." }, "reviewNewApplications": { "message": "Neue Anwendungen überprüfen" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Überprüfe neue Anwendungen mit sensiblen Einträgen und kennzeichne diejenigen, die du genau überwachen möchtest, als kritisch. Anschließend kannst du Mitgliedern Sicherheitsaufgaben zuweisen, um Risiken zu beseitigen." }, "clickIconToMarkAppAsCritical": { "message": "Klicke auf das Sternsymbol, um eine App als kritisch zu markieren" @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB verschlüsselter Speicherplatz für Dateianhänge." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietäre Optionen für die Zwei-Faktor Authentifizierung wie YubiKey und Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Zusätzlicher Speicher (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Erfahre mehr" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Einstellungen aktualisieren" + }, "deleteRecoverDesc": { "message": "Geben Sie hier Ihre E-Mail-Adresse ein, um Ihr Konto wiederherzustellen und zu löschen." }, @@ -5810,7 +5846,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Alle Einträge sind Eigentum der Organisation und werden dort gespeichert, was organisationsweite Kontrollen, Transparenz und Berichterstellung ermöglicht. Wenn diese Funktion aktiviert ist, steht jedem Mitglied eine Standard-Sammlung zur Speicherung von Einträgen zur Verfügung. Erfahre mehr über die Verwaltung des ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -9820,6 +9856,14 @@ "message": "Zu teuer", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Wechseln zum kostenlosen Tarif", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Wechseln zur kostenlosen Organisation", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Kostenlos für 1 Jahr" }, @@ -9851,7 +9895,7 @@ "message": "Aufgaben zuweisen" }, "assignSecurityTasksToMembers": { - "message": "Sende Benachrichtigungen, um Passwörter zu ändern" + "message": "Benachrichtigungen zum Ändern von Passwörtern senden" }, "assignToCollections": { "message": "Sammlungen zuweisen" @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden-Erweiterung installiert!" }, - "openTheBitwardenExtension": { - "message": "Die Bitwarden-Erweiterung öffnen" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Die Bitwarden-Erweiterung ist installiert! Öffne die Erweiterung, um dich anzumelden und das automatische Ausfüllen zu starten." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden-Erweiterung wurde installiert!" }, "openExtensionToAutofill": { "message": "Öffne die Erweiterung, um dich anzumelden und Auto-Ausfüllen zu nutzen." @@ -11687,6 +11720,14 @@ "message": "Lernzentrum", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Falls sich die Erweiterung nicht geöffnet hat, musst du möglicherweise Bitwarden über das Symbol ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " in der Symbolleiste öffnen.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Hilfezentrum", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Aktualisiere deine Verschlüsselungseinstellungen" }, - "updateSettings": { - "message": "Einstellungen aktualisieren" - }, "algorithm": { "message": "Algorithmus" }, @@ -12119,13 +12157,13 @@ "message": "Kostenlose Families-Testversion starten" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Erstellung von Konten für beanspruchte Domains blockieren" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "Verhindere, dass Benutzer außerhalb deiner Organisation Konten mit E-Mail-Adressen aus beanspruchten Domains erstellen." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "Vor der Aktivierung dieser Richtlinie muss eine Domain beansprucht werden." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Richte eine Entsperrmethode ein, um deine Aktion bei Tresor-Timeout zu ändern." @@ -12149,7 +12187,7 @@ "message": "Aussehen" }, "vaultTimeoutPolicyMaximumError": { - "message": "Das Timeout überschreitet die von deiner Organisation festgelegte Beschränkung: Maximal $HOURS$ Stunde(n) und $MINUTES$ Minute(n)", + "message": "Das maximale Sitzungs-Timeout, das durch die Richtlinie deiner Organisation erlaubt ist, beträgt $HOURS$ Stunde(n) und $MINUTES$ Minute(n)", "placeholders": { "hours": { "content": "$1", @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Bist du sicher, dass du fortfahren möchtest?" + }, + "userVerificationFailed": { + "message": "Benutzerverifizierung fehlgeschlagen." } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 5e937df4a21..f6e8abfd612 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB κρυπτογραφημένο αποθηκευτικό χώρο για συνημμένα αρχεία." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Επιπλέον χώρος αποθήκευσης (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Μάθετε περισσότερα" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Εισάγετε την παρακάτω διεύθυνση email για να ανακτήσετε και να διαγράψετε το λογαριασμό σας." }, @@ -9820,6 +9856,14 @@ "message": "Πολύ ακριβό", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Δωρεάν για 1 χρόνο" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index a6e5c47733f..3b0554547c5 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -5155,9 +5173,21 @@ "message": "Fix", "description": "This is a verb. ex. 'Fix The Car'" }, + "fixEncryption": { + "message": "Fix encryption" + }, + "fixEncryptionTooltip": { + "message": "This file is using an outdated encryption method." + }, + "attachmentUpdated": { + "message": "Attachment updated" + }, "oldAttachmentsNeedFixDesc": { "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." }, + "itemsTransferred": { + "message": "Items transferred" + }, "yourAccountsFingerprint": { "message": "Your account's fingerprint phrase", "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." @@ -6439,6 +6469,32 @@ "bulkReinviteMessage": { "message": "Reinvited successfully" }, + "bulkReinviteSuccessToast": { + "message": "$COUNT$ users re-invited", + "placeholders": { + "count": { + "content": "$1", + "example": "12" + } + } + }, + "bulkReinviteLimitedSuccessToast": { + "message": "$LIMIT$ of $SELECTEDCOUNT$ users re-invited. $EXCLUDEDCOUNT$ were not invited due to the $LIMIT$ invite limit.", + "placeholders": { + "limit": { + "content": "$1", + "example": "4,000" + }, + "selectedCount": { + "content": "$2", + "example": "4,005" + }, + "excludedCount": { + "content": "$3", + "example": "5" + } + } + }, "bulkRemovedMessage": { "message": "Removed successfully" }, @@ -6759,8 +6815,8 @@ "vaultTimeoutRangeError": { "message": "Vault timeout is not within allowed range." }, - "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "disableExport": { + "message": "Remove export" }, "disablePersonalVaultExportDescription": { "message": "Do not allow members to export data from their individual vault." @@ -7080,9 +7136,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -7140,8 +7193,8 @@ "enabledSso": { "message": "SSO turned on" }, - "disabledSso": { - "message": "SSO turned on" + "ssoTurnedOff": { + "message": "SSO turned off" }, "emailMustLoginWithSso": { "message": "$EMAIL$ must login with Single Sign-on", @@ -9434,6 +9487,9 @@ "ssoLoginIsRequired": { "message": "SSO login is required" }, + "emailRequiredForSsoLogin": { + "message": "Email is required for SSO" + }, "selectedRegionFlag": { "message": "Selected region flag" }, @@ -11947,8 +12003,8 @@ "familiesMembership": { "message": "Families membership" }, - "planDescPremium": { - "message": "Complete online security" + "advancedOnlineSecurity": { + "message": "Advanced online security" }, "planDescFamiliesV2": { "message": "Premium security for your family" @@ -12188,6 +12244,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector":{ + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified":{ + "message": "Organization verified" + }, + "domainVerified":{ + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, @@ -12196,5 +12291,139 @@ }, "userVerificationFailed": { "message": "User verification failed." + }, + "recoveryDeleteCiphersTitle": { + "message": "Delete unrecoverable vault items" + }, + "recoveryDeleteCiphersDesc": { + "message": "Some of your vault items could not be recovered. Do you want to delete these unrecoverable items from your vault?" + }, + "recoveryDeleteFoldersTitle": { + "message": "Delete unrecoverable folders" + }, + "recoveryDeleteFoldersDesc": { + "message": "Some of your folders could not be recovered. Do you want to delete these unrecoverable folders from your vault?" + }, + "recoveryReplacePrivateKeyTitle": { + "message": "Replace encryption key" + }, + "recoveryReplacePrivateKeyDesc": { + "message": "Your public-key encryption key pair could not be recovered. Do you want to replace your encryption key with a new key pair? This will require you to set up existing emergency-access and organization memberships again." + }, + "recoveryStepSyncTitle": { + "message": "Synchronizing data" + }, + "recoveryStepPrivateKeyTitle": { + "message": "Verifying encryption key integrity" + }, + "recoveryStepUserInfoTitle": { + "message": "Verifying user information" + }, + "recoveryStepCipherTitle": { + "message": "Verifying vault item integrity" + }, + "recoveryStepFoldersTitle": { + "message": "Verifying folder integrity" + }, + "dataRecoveryTitle": { + "message": "Data Recovery and Diagnostics" + }, + "dataRecoveryDescription": { + "message": "Use the data recovery tool to diagnose and repair issues with your account. After running diagnostics you have the option to save diagnostic logs for support and the option to repair any detected issues." + }, + "runDiagnostics": { + "message": "Run Diagnostics" + }, + "repairIssues": { + "message": "Repair Issues" + }, + "saveDiagnosticLogs": { + "message": "Save Diagnostic Logs" + }, + "sessionTimeoutSettingsManagedByOrganization": { + "message": "This setting is managed by your organization." + }, + "sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": { + "message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "8" + }, + "minutes": { + "content": "$2", + "example": "2" + } + } + }, + "sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": { + "message": "Your organization has set the default session timeout to On browser refresh." + }, + "sessionTimeoutSettingsPolicyMaximumError": { + "message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "sessionTimeoutOnRestart": { + "message": "On browser refresh" + }, + "sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": { + "message": "Set an unlock method to change your timeout action" + }, + "leaveConfirmationDialogTitle": { + "message": "Are you sure you want to leave?" + }, + "leaveConfirmationDialogContentOne": { + "message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features." + }, + "leaveConfirmationDialogContentTwo": { + "message": "Contact your admin to regain access." + }, + "leaveConfirmationDialogConfirmButton": { + "message": "Leave $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "howToManageMyVault": { + "message": "How do I manage my vault?" + }, + "transferItemsToOrganizationTitle": { + "message": "Transfer items to $ORGANIZATION$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "transferItemsToOrganizationContent": { + "message": "$ORGANIZATION$ is requiring all items to be owned by the organization for security and compliance. Click accept to transfer ownership of your items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "acceptTransfer": { + "message": "Accept transfer" + }, + "declineAndLeave": { + "message": "Decline and leave" + }, + "whyAmISeeingThis": { + "message": "Why am I seeing this?" } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 7e0f79f9a11..be621369c77 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organisation", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Centre", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Centre", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 864f66ade8e..aafb9a01f3e 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organisation", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Centre", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Centre", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 3ec660dc5b2..220d9492c67 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB ĉifrita stokado por dosieraj aldonaĵoj." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Aldona Stokado (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Lernu pli" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enigu vian retpoŝtan adreson sube por rekuperi kaj forigi vian konton." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": "el la ilarbreto", - "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index b69c4ebf822..b0a3716cf60 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB de almacenamiento de archivos cifrados." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Opciones de inicio de sesión con autenticación de dos pasos propietarios como YubiKey y Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Almacenamiento adicional (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Más información" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Introduce tu correo electrónico debajo para recuperar y eliminar tu cuenta." }, @@ -9820,6 +9856,14 @@ "message": "Demasiado caro", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Gratis durante 1 año" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index f96af492352..1388e25bc13 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB ulatuses krüpteeritud salvestusruum." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Eraomanduses kaheastmelise logimise valikud, nagu näiteks YubiKey ja Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Lisaruum (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Rohkem teavet" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Sisesta e-posti aadress oma konto taastamiseks ja kustutamiseks." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 20aec32c796..c2fa69d56b8 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "Eranskinentzako 1GB-eko zifratutako biltegia." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Biltegi gehigarria (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Gehiago ikasi" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Sartu zure emaila jarraian, zure kontua berreskuratu eta ezabatzeko." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 56aa148b77a..c8623050394 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -3,7 +3,7 @@ "message": "همه‌ برنامه‌ها" }, "activity": { - "message": "Activity" + "message": "فعالیت" }, "appLogoLabel": { "message": "لوگو Bitwarden" @@ -21,13 +21,13 @@ "message": "خطر کلمه عبور" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "شما مجاز به ویرایش این مورد نیستید" }, "reviewAtRiskPasswords": { "message": "کلمات عبور در معرض خطر (ضعیف، افشا شده یا تکراری) را در برنامه‌ها بررسی کنید. برنامه‌های حیاتی خود را انتخاب کنید تا اقدامات امنیتی را برای کاربران‌تان اولویت‌بندی کنید و به کلمات عبور در معرض خطر رسیدگی کنید." }, "reviewAtRiskLoginsPrompt": { - "message": "Review at-risk logins" + "message": "بررسی ورودهای پرخطر" }, "dataLastUpdated": { "message": "آخرین به‌روزرسانی داده‌ها: $DATE$", @@ -39,7 +39,7 @@ } }, "noReportRan": { - "message": "You have not created a report yet" + "message": "شما هنوز گزارشی ایجاد نکرده‌اید" }, "notifiedMembers": { "message": "اعضای مطلع شده" @@ -66,7 +66,7 @@ "message": "ایجاد مورد ورود جدید" }, "percentageCompleted": { - "message": "$PERCENT$% complete", + "message": "$PERCENT$ درصد انجام شده", "placeholders": { "percent": { "content": "$1", @@ -75,7 +75,7 @@ } }, "securityTasksCompleted": { - "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "message": "$COUNT$ مورد از $TOTAL$ کار امنیتی انجام شده است", "placeholders": { "count": { "content": "$1", @@ -88,28 +88,28 @@ } }, "passwordChangeProgress": { - "message": "Password change progress" + "message": "وضعیت تغییر رمزهای عبور" }, "assignMembersTasksToMonitorProgress": { - "message": "Assign members tasks to monitor progress" + "message": "به اعضا وظیفه بدهید تا روند کار را رصد کنند" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "وقتی برنامه‌ها را بررسی کردید و آنها را به عنوان مهم علامت زدید، به اعضای تیم خود وظیفه دهید که رمزهایشان را تغییر دهند." }, "sendReminders": { - "message": "Send reminders" + "message": "ارسال يادآوری" }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here." + "message": "وقتی برنامه‌ای را مهم علامت بزنید، اینجا نمایش داده می‌شود." }, "viewAtRiskMembers": { - "message": "View at-risk members" + "message": "مشاهده اعضای در خطر" }, "viewAtRiskApplications": { - "message": "View at-risk applications" + "message": "مشاهده برنامه‌های در خطر" }, "criticalApplicationsAreAtRisk": { - "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "message": "$COUNT$ مورد از $TOTAL$ برنامه مهم به دلیل رمزهای ضعیف در خطر هستند", "placeholders": { "count": { "content": "$1", @@ -131,10 +131,10 @@ } }, "criticalApplicationsMarked": { - "message": "critical applications marked" + "message": "برنامه‌های مهم علامت‌گذاری شده" }, "countOfCriticalApplications": { - "message": "$COUNT$ critical applications", + "message": "$COUNT$ برنامه مهم", "placeholders": { "count": { "content": "$1", @@ -143,7 +143,7 @@ } }, "countOfApplicationsAtRisk": { - "message": "$COUNT$ applications at-risk", + "message": "$COUNT$ رمز عبور در خطر", "placeholders": { "count": { "content": "$1", @@ -152,7 +152,7 @@ } }, "countOfAtRiskPasswords": { - "message": "$COUNT$ passwords at-risk", + "message": "$COUNT$ رمز عبور جدید در خطر", "placeholders": { "count": { "content": "$1", @@ -161,7 +161,7 @@ } }, "newPasswordsAtRisk": { - "message": "$COUNT$ new passwords at-risk", + "message": "$COUNT$ رمز عبور جدید در خطر", "placeholders": { "count": { "content": "$1", @@ -179,64 +179,64 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "اطلاعاتی وجود ندارد" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "اطلاعات ورود سازمان خود را وارد کنید تا با هوش دسترسی شروع کنید. بعد از این کار می‌توانید:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "علامت‌گذاری برنامه‌ها به عنوان مهم" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "این کار به شما کمک می‌کند ابتدا خطرات برنامه‌های مهم خود را برطرف کنید." }, "feature2Title": { - "message": "Help members improve their security" + "message": "کمک به اعضا برای بهبود امنیتشان" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "به اعضای در خطر وظایف امنیتی راهنما شده بدهید تا اطلاعات ورودشان را به‌روز کنند." }, "feature3Title": { - "message": "Monitor progress" + "message": "نظارت بر پیشرفت" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "تغییرات را در طول زمان دنبال کنید تا بهبود امنیت را نشان دهید." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "ایجاد گزارش" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "شما آماده ایجاد گزارش هستید. بعد از ایجاد گزارش می‌توانید:" }, "noCriticalApplicationsTitle": { - "message": "You haven’t marked any applications as critical" + "message": "شما هیچ برنامه‌ای را به عنوان مهم علامت نزده‌اید" }, "noCriticalApplicationsDescription": { - "message": "Select your most critical applications to prioritize security actions for your users to address at-risk passwords." + "message": "مهم‌ترین برنامه‌های خود را انتخاب کنید تا اقدامات امنیتی برای کاربران در مورد رمزهای ضعیف اولویت‌بندی شود." }, "markCriticalApplications": { - "message": "Select critical applications" + "message": "انتخاب برنامه‌های مهم" }, "markAppAsCritical": { "message": "برنامه را به عنوان حیاتی علامت‌گذاری کنید" }, "markAsCritical": { - "message": "Mark as critical" + "message": "علامت‌گذاری به عنوان مهم" }, "applicationsSelected": { - "message": "applications selected" + "message": "برنامه انتخاب شده" }, "selectApplication": { - "message": "Select application" + "message": "برنامه را انتخاب کنید" }, "unselectApplication": { - "message": "Unselect application" + "message": "لغو انتخاب برنامه" }, "applicationsMarkedAsCriticalSuccess": { "message": "برنامه‌های علامت گذاری شده به عنوان حیاتی" }, "criticalApplicationsMarkedSuccess": { - "message": "$COUNT$ applications marked as critical", + "message": "$COUNT$ برنامه به عنوان مهم علامت زده شد", "placeholders": { "count": { "content": "$1", @@ -245,7 +245,7 @@ } }, "applicationsMarkedAsCriticalFail": { - "message": "Failed to mark applications as critical" + "message": "علامت‌گذاری برنامه‌ها به عنوان مهم ناموفق بود" }, "application": { "message": "برنامه" @@ -266,16 +266,16 @@ "message": "اعضای در معرض خطر" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "این اعضا به موارد آسیب‌پذیر برنامه‌های مهم دسترسی دارند." }, "membersWithAtRiskPasswords": { - "message": "Members with at-risk passwords" + "message": "اعضایی با رمزهای عبور ضعیف" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "به اعضای سازمان شما وظیفه تغییر رمزهای آسیب‌پذیر داده می‌شود. آن‌ها در افزونه مرورگر Bitwarden خود اعلان دریافت می‌کنند." }, "membersAtRiskCount": { - "message": "$COUNT$ members at-risk", + "message": "$COUNT$ عضو در خطر", "placeholders": { "count": { "content": "$1", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "این اعضا با رمزهای ضعیف، افشا شده یا تکراری وارد برنامه‌های مهم می‌شوند." }, "atRiskMembersDescriptionNone": { "message": "هیچ عضوی با کلمات عبور ضعیف، افشا شده یا تکراری وارد برنامه‌ها نمی‌شود." @@ -341,13 +341,13 @@ "message": "کل برنامه‌ها" }, "applicationsNeedingReview": { - "message": "Applications needing review" + "message": "برنامه‌هایی که نیاز به بررسی دارند" }, "newApplicationsCardTitle": { - "message": "Review new applications" + "message": "بررسی برنامه‌های جدید" }, "newApplicationsWithCount": { - "message": "$COUNT$ new applications", + "message": "$COUNT$ برنامه جدید", "placeholders": { "count": { "content": "$1", @@ -356,16 +356,16 @@ } }, "newApplicationsDescription": { - "message": "Review new applications to mark as critical and keep your organization secure" + "message": "برنامه‌های جدید را بررسی کنید، آنها را به عنوان مهم علامت بزنید و سازمان خود را ایمن نگه دارید" }, "reviewNow": { - "message": "Review now" + "message": "همین حالا بررسی کنید" }, "allCaughtUp": { - "message": "All caught up!" + "message": "همه چیز انجام شد!" }, "noNewApplicationsToReviewAtThisTime": { - "message": "No new applications to review at this time" + "message": "در حال حاضر برنامه جدیدی برای بررسی وجود ندارد" }, "organizationHasItemsSavedForApplications": { "message": "Your organization has items saved for $COUNT$ applications", @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "۱ گیگابایت فضای ذخیره‌سازی رمزنگاری شده برای پرونده‌های پیوست." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "گزینه های ورود اضافی دو مرحله ای مانند YubiKey و Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "ذخیره‌سازی های اضافی (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "بیشتر بدانید" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "نشانی ایمیل خود را در زیر برای بازیابی و حذف حساب خود وارد کنید." }, @@ -9820,6 +9856,14 @@ "message": "خیلی گران است", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "رایگان برای یک سال" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "افزونه Bitwarden نصب شد!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "افزونه را باز کنید تا وارد شوید و پر کردن خودکار را آغاز کنید." @@ -11687,6 +11720,14 @@ "message": "مرکز آموزش", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "مرکز راهنمایی", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index a1332427b09..181c8d4f125 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 Gt salattua tallennustilaa tiedostoliitteille." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Kaksivaiheisen kirjautumisen erikoisvaihtoehdot, kuten YubiKey ja Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Lisää tallennustilaa (Gt)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Lue lisää" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Kirjoita sähköpostiosoitteesi alle palauttaaksesi ja poistaaksesi tilisi." }, @@ -9820,6 +9856,14 @@ "message": "Liian kallis", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Ilmainen 1 vuoden ajan" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 9935386d012..f3542368044 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1GB na naka-encrypt na storage para sa mga file attachment." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Karagdagang storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Matuto nang higit pa" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Ipasok ang iyong email address sa ibaba upang mabawi at tanggalin ang iyong account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index db3aa3090b1..e5fcf929f0d 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 Go de stockage chiffré pour les fichiers joints." }, + "premiumSignUpStorageV2": { + "message": "Stockage chiffré de $SIZE$ pour les pièces jointes.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Options de connexion propriétaires à deux facteurs telles que YubiKey et Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Votre abonnement Premium est terminé" + }, + "premiumSubscriptionEndedDesc": { + "message": "Pour récupérer l'accès à vos archives, redémarrez votre abonnement Premium. Si vous modifiez les détails d'un élément archivé avant de le redémarrer, il sera déplacé dans votre coffre." + }, + "restartPremium": { + "message": "Redémarrer Premium" + }, "additionalStorageGb": { "message": "Stockage additionnel (Go)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "En savoir plus" }, + "migrationsFailed": { + "message": "Une erreur s'est produite lors de la mise à jour des paramètres de chiffrement." + }, + "updateEncryptionSettingsTitle": { + "message": "Mettre à jour vos paramètres de chiffrement" + }, + "updateEncryptionSettingsDesc": { + "message": "Les nouveaux paramètres de chiffrement recommandés amélioreront la sécurité de votre compte. Entrez votre mot de passe principal pour faire la mise à jour maintenant." + }, + "confirmIdentityToContinue": { + "message": "Confirmez votre identité pour continuer" + }, + "enterYourMasterPassword": { + "message": "Entrez votre mot de passe principal" + }, + "updateSettings": { + "message": "Mettre à jour les paramètres" + }, "deleteRecoverDesc": { "message": "Saisissez votre adresse électronique ci-dessous pour récupérer et supprimer votre compte." }, @@ -9820,6 +9856,14 @@ "message": "Trop cher", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Passage à l'abonnement gratuit", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Passage à l'organisation gratuite", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Gratuit pour 1 an" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "L'extension Bitwarden est installée !" }, - "openTheBitwardenExtension": { - "message": "Ouvrir l'extension Bitwarden" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "L'extension Bitwarden est installée! Ouvrez l'extension pour vous connecter et démarrer le remplissage automatique." + "bitwardenExtensionIsInstalled": { + "message": "L'extension Bitwarden est installée !" }, "openExtensionToAutofill": { "message": "Ouvrez l'extension pour vous connecter et démarrer le remplissage automatique." @@ -11687,6 +11720,14 @@ "message": "Centre d'apprentissage", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Si l'extension n'est pas ouverte, vous devrez peut-être ouvrir Bitwarden à partir de l'icône ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " sur 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Centre d’aide", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Mettre à jour vos paramètres de chiffrement" }, - "updateSettings": { - "message": "Mettre à jour les paramètres" - }, "algorithm": { "message": "Algorithme" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Êtes-vous sûr de vouloir continuer ?" + }, + "userVerificationFailed": { + "message": "La vérification de l'utilisateur a échoué." } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 5ff4117909b..f7a848bd025 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index f5f8bfd3c78..d723cb42140 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 ג'יגה של מקום אחסון מוצפן עבור קבצים מצורפים." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "אפשרויות כניסה דו־שלבית קנייניות כגון YubiKey ו־Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "אחסון נוסף (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "למידע נוסף" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "עדכן הגדרות" + }, "deleteRecoverDesc": { "message": "הזן את כתובת האימייל שלך כדי לשחזר ולמחוק את החשבון שלך." }, @@ -9820,6 +9856,14 @@ "message": "יקר מדי", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "חינם לשנה אחת" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "הרחבת Bitwarden הותקנה!" }, - "openTheBitwardenExtension": { - "message": "פתח את ההרחבה של Bitwarden" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "ההרחבה של Bitwarden הותקנה! פתח את ההרחבה כדי להיכנס ולהתחיל למלא אוטומטית." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "פתח את ההרחבה כדי להיכנס ולהתחיל למלא אוטומטית." @@ -11687,6 +11720,14 @@ "message": "מרכז הלמידה", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "מרכז העזרה", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "עדכן את הגדרות ההצפנה שלך" }, - "updateSettings": { - "message": "עדכן הגדרות" - }, "algorithm": { "message": "אלגוריתם" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index b3ecb676b4d..1c5a211aaca 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 26a784acf5c..f357718bab0 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -94,7 +94,7 @@ "message": "Dodijeli članovima zadatke za praćenje napretka" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "Nakon što pregledaš aplikacije i označiš ih kao kritične, dodijeli svojim članovima zadatke za promjenu lozinki." }, "sendReminders": { "message": "Pošalji podsjetnike" @@ -179,34 +179,34 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "Nisu pronađeni podaci" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Uvezi podatke za prijavu svoje organizacije za korištenje usluge Access Intelligence. Nakon toga, moći ćeš:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Označi aplikacije kao kritične" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "To će ti pomoći prvo ukloniti rizike za tvoje najvažnije aplikacije." }, "feature2Title": { - "message": "Help members improve their security" + "message": "Pomoći povećati sigurnost članova" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Dodijeli vođene sigurnosne zadatke članovima u riziku za ažuriranje vjerodajnica." }, "feature3Title": { - "message": "Monitor progress" + "message": "Pratiti napredak" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Praćenje promjena tijekom vremena za prikaz sigurnosnih poboljšanja." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Stvarati izvješća" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Počni generirati izvješća. Nakon što ih generiraš, moći ćeš:" }, "noCriticalApplicationsTitle": { "message": "Niti jedna aplikacija nije označena kao kritična" @@ -266,13 +266,13 @@ "message": "Rizični korisnici" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Ovi članovi imaju pristup ranjivim stavkama za kritične aplikacije." }, "membersWithAtRiskPasswords": { "message": "Članovi s rizičnim lozinkama" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Članovima tvoje organizacije biti će dodijeljen zadatak promjene ranjivih lozinki. Primiti će obavijest unutar Bitwarden proširenja preglednika." }, "membersAtRiskCount": { "message": "Rizičnih članova: $COUNT$", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Ovi članovi se prijavljuju u kritične aplikacije slabim, otkrivenim ili ponovno korištenim lozinkama." }, "atRiskMembersDescriptionNone": { "message": "Nema članova koji se prijavljuju slabim, izloženim ili ponovno korištenim lozinkama." @@ -386,13 +386,13 @@ "message": "Daj prioritet kritičnim aplikacijama" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Odaberi koje su aplikacije najvažnije za tvoju organizaciju. Zatim ćeš moći dodijeliti sigurnosne zadatke članovima kako bi se uklonili rizici." }, "reviewNewApplications": { "message": "Pregledaj nove prijave" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Pregledaj nove aplikacije s ranjivim stavkama i označi one koje želiš pomno pratiti kao kritične. Zatim ćeš moći dodijeliti sigurnosne zadatke članovima kako bi se uklonili rizici." }, "clickIconToMarkAppAsCritical": { "message": "Klikni ikonu zvjezdice za označavanje aplikacije kao kritične" @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB šifriranog prostora za pohranu podataka." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Mogućnosti za prijavu u dva koraka kao što su YubiKey i Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Dodatni prostor za pohranu (GB)" }, @@ -3188,7 +3206,7 @@ "message": "Sve neplaćene pretplate naplatiti će se na tvoj način plaćanja." }, "paymentChargedWithTrial": { - "message": "Plan dolazi s besplatnom probnom verzijom od 7 dana. Tvoj način plaćanja neće biti terećen dok ne završi probno razdoblje. Možeš otkazati u bilo kojem trenutku." + "message": "Paket dolazi s besplatnom probnom verzijom od 7 dana. Tvoj način plaćanja neće biti terećen dok ne završi probno razdoblje. Možeš otkazati u bilo kojem trenutku." }, "paymentInformation": { "message": "Podaci o plaćanju" @@ -3419,7 +3437,7 @@ "message": "Naziv tvrtke" }, "chooseYourPlan": { - "message": "Odaberi svoj plan" + "message": "Odaberi svoj paket" }, "users": { "message": "Korisnici" @@ -3469,7 +3487,7 @@ "message": "Za privatnu upotrebu, za dijeljenje s obitelji i prijateljima." }, "planNameTeams": { - "message": "Timovi" + "message": "Teams" }, "planDescTeams": { "message": "Za male tvtke ili druge manje organizacije." @@ -3598,7 +3616,7 @@ } }, "trialPaidInfoMessage": { - "message": "Tvoj plan $PLAN$ će biti pretvoren u plaćenu pretplatu nakon isteka besplatnog probnog razdoblja od 7 dana.", + "message": "Tvoj paket $PLAN$ će biti pretvoren u plaćenu pretplatu nakon isteka besplatnog probnog razdoblja od 7 dana.", "placeholders": { "plan": { "content": "$1", @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Saznaj više" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Ažuriraj postavke" + }, "deleteRecoverDesc": { "message": "Unesi svoju e-poštu za oporavak i brisanje svojeg računa." }, @@ -4813,7 +4849,7 @@ } }, "subscriptionUserSeatsWithoutAdditionalSeatsOption": { - "message": "Možeš pozvati do $COUNT$ članova bez dodatnih troškova. Kontaktiraj korisničku podršku za nadogradnju plana i pozivanje više članova.", + "message": "Možeš pozvati do $COUNT$ članova bez dodatnih troškova. Kontaktiraj korisničku podršku za nadogradnju paketa i pozivanje više članova.", "placeholders": { "count": { "content": "$1", @@ -4822,7 +4858,7 @@ } }, "subscriptionFreePlan": { - "message": "Nije moguće pozvati više od $COUNT$ korisnika bez nadogradnje plana.", + "message": "Nije moguće pozvati više od $COUNT$ korisnika bez nadogradnje paketa.", "placeholders": { "count": { "content": "$1", @@ -4831,7 +4867,7 @@ } }, "subscriptionUpgrade": { - "message": "Nije moguće pozvati više od $COUNT$ korisnika bez nadogradnje plana.", + "message": "Nije moguće pozvati više od $COUNT$ korisnika bez nadogradnje paketa.", "placeholders": { "count": { "content": "$1", @@ -5810,7 +5846,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Sve će stavke biti u vlasništvu i spremljene u organizaciji, što će omogućiti kontrolu, vidljivost i izvještavanje na razini cijele organizacije. Kada je uključeno, svakom će članu biti dostupna zadana kolekcija za pohranu stavki. Saznaj više o upravljanju ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -5994,7 +6030,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customNonEnterpriseError": { - "message": "Za uključivanje prilagođenih uloga, organizacija mora biti na planu Enterprise 2020." + "message": "Za uključivanje prilagođenih uloga organizacija mora biti na paketu Enterprise 2020." }, "permissions": { "message": "Dozvole" @@ -6891,7 +6927,7 @@ "message": "Nema sponzoriranog Families paketa" }, "nosponsoredFamiliesDetails": { - "message": "Sponzorirani ne-obiteljski paketi biti će prikazani ovdje" + "message": "Sponzorirani ne-obiteljski paketi će biti prikazani ovdje" }, "sponsorshipFreeBitwardenFamilies": { "message": "Članovi tvoje organizacije ispunjavaju uvjete za besplatni Bitwarden Families paket. Možeš sponzorirati besplatni Bitwarden Families paket za zaposlenike koji nisu članovi tvoje Bitwarden organizacije. Sponzoriranje ne-člana zahtijeva dostupno mjesto unutar vaše organizacije." @@ -6939,7 +6975,7 @@ "message": "Veza više nije valjana. Zamoli sponzora da pošalje novu ponudu." }, "reclaimedFreePlan": { - "message": "Obnovljeni besplatni plan" + "message": "Obnovljeni besplatni paket" }, "redeem": { "message": "Iskoristi" @@ -7242,7 +7278,7 @@ "message": "Vlastiti poslužitelj" }, "selfHostingEnterpriseOrganizationSectionCopy": { - "message": "Za postavljanje svoje organizacije na vlastitom poslužitelju, morat ćeš prenijeti datoteku licence. Kako bi podržali besplatne Families pakete i napredne mogućnosti naplate za svoju samostalnu organizaciju, morat ćeš postaviti sinkronizaciju naplate." + "message": "Za postavljanje svoje organizacije na vlastitom poslužitelju, morat ćeš prenijeti datoteku licence. Kako bi besplatni Families paketi i napredne mogućnosti naplate bili podržani u tvojoj samostalnoj organizaciji, morat ćeš postaviti sinkronizaciju naplate." }, "billingSyncApiKeyRotated": { "message": "Token rotiran" @@ -8626,7 +8662,7 @@ "message": "Zamijeni proizvode" }, "freeOrgInvLimitReachedManageBilling": { - "message": "Besplatne organizacije mogu imati do $SEATCOUNT$ članova. Nadogradi na plaćeni plan za pozivanje više članova.", + "message": "Besplatne organizacije mogu imati do $SEATCOUNT$ članova. Nadogradi na plaćeni paket za pozivanje više članova.", "placeholders": { "seatcount": { "content": "$1", @@ -8644,7 +8680,7 @@ } }, "teamsStarterPlanInvLimitReachedManageBilling": { - "message": "Teams Starter plan može imati do $SEATCOUNT$ članova. Nadogradi svoj plan za pozivanje više članova.", + "message": "Teams Starter paket može imati do $SEATCOUNT$ članova. Nadogradi svoj paket za pozivanje više članova.", "placeholders": { "seatcount": { "content": "$1", @@ -8653,7 +8689,7 @@ } }, "teamsStarterPlanInvLimitReachedNoManageBilling": { - "message": "Teams Starter plan može imati do $SEATCOUNT$ članova. Kontaktiraj vlasnika svoje organizacije za nadogradnju plana i pozivanje više članova.", + "message": "Teams Starter paket može imati do $SEATCOUNT$ članova. Kontaktiraj vlasnika svoje organizacije za nadogradnju paketa i pozivanje više članova.", "placeholders": { "seatcount": { "content": "$1", @@ -8662,7 +8698,7 @@ } }, "freeOrgMaxCollectionReachedManageBilling": { - "message": "Besplatne organizacije mogu imati do $COLLECTIONCOUNT$ zbirki. Nadogradi na plaćeni plan za dodavanje više zbirki.", + "message": "Besplatne organizacije mogu imati do $COLLECTIONCOUNT$ zbirki. Nadogradi na plaćeni paket za dodavanje više zbirki.", "placeholders": { "COLLECTIONCOUNT": { "content": "$1", @@ -9494,13 +9530,13 @@ "message": "Predbilježi se za Secrets Manager" }, "addSecretsManagerUpgradeDesc": { - "message": "Dodajte Secrets Manager svojem nadograđenom planu za zadržavanje pristupa svim tajnama stvorenim sa svojim prethodnim planom." + "message": "Dodajte Secrets Manager svojem nadograđenom paketu za zadržavanje pristupa svim tajnama stvorenim sa svojim prethodnim planom." }, "additionalServiceAccounts": { "message": "Dodani servisni računi" }, "includedServiceAccounts": { - "message": "Plan uključuje $COUNT$ servisnih računa.", + "message": "Paket uključuje $COUNT$ servisnih računa.", "placeholders": { "count": { "content": "$1", @@ -9544,7 +9580,7 @@ "message": "Ažurirane postavke upravljanja zbirkama" }, "passwordManagerPlanPrice": { - "message": "Cijena plana za Upravitelj lozinkama" + "message": "Cijena paketa za Upravitelj lozinkama" }, "secretsManagerPlanPrice": { "message": "Cjenik Secrets Managera" @@ -9820,6 +9856,14 @@ "message": "Preskupo", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Prebacivanje na besplatni paket", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Prebacivanje na besplatnu organizaciju", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Besplatno godinu dana" }, @@ -9851,7 +9895,7 @@ "message": "Dodijeli zadatke" }, "assignSecurityTasksToMembers": { - "message": "Send notifications to change passwords" + "message": "Pošalji podsjetnike za promjenu lozinki" }, "assignToCollections": { "message": "Dodijeli zbirkama" @@ -11339,14 +11383,6 @@ "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": { @@ -11506,7 +11542,7 @@ "message": "Pregled događaja" }, "cannotCreateCollection": { - "message": "Besplatne organizacije mogu imati do 2 zbirke. Nadogradi na plaćeni plan za dodavanje više zbirki." + "message": "Besplatne organizacije mogu imati do 2 zbirke. Nadogradi na plaćeni paket za dodavanje više zbirki." }, "searchArchive": { "message": "Pretraži arhivu" @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden proširenje je instalirano!" }, - "openTheBitwardenExtension": { - "message": "Otvori Bitwarden proširenje" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Instalirano je Bitwarden proširenje! Otvori ga za prijavu i početak auto-ispune." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Otvori proširenje i prijavi se za početak korištenja auto-ispune." @@ -11687,6 +11720,14 @@ "message": "centar za učenje", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "centar za pomoć", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -11814,7 +11855,7 @@ } }, "subscribeEnterpriseSubtitle": { - "message": "Tvoj 7-dnevni probni rok za plan $PLAN$ počinje danas. Dodaj način plaćanja za nastavak korištenja značajki nakon završetka probnog roka: ", + "message": "Tvoj 7-dnevni probni rok za paket $PLAN$ počinje danas. Dodaj način plaćanja za nastavak korištenja značajki nakon završetka probnog roka: ", "placeholders": { "plan": { "content": "$1", @@ -11939,7 +11980,7 @@ "message": "Napredne mogućnosti za bilo koju organizaciju" }, "planNameCustom": { - "message": "Vlastiti plan" + "message": "Vlastiti paket" }, "planDescCustom": { "message": "Bitwarden se prilagođava tvrtkama svih veličina kako bi osigurao lozinke i osjetljive podatke. Ako si dio velike tvrtke, zatraži ponudu od odjela prodaje." @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Ažuriraj svoje postakve šifriranja" }, - "updateSettings": { - "message": "Ažuriraj postavke" - }, "algorithm": { "message": "Algoritam" }, @@ -12119,13 +12157,13 @@ "message": "Započni besplatno probno razdoblje za Families" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Blokiraj stvaranje računa za potvrđene domene" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "Spriječi korisnike da stvaraju račune izvan organizacije koristeći adrese e-pošte s potvrđenim domenama." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "Domena mora biti potvrđena prije aktiviranja ovog pravila." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Postavi metodu otključavanja za promjenu radnje nakon isteka vremenskog ograničenja trezora." @@ -12162,9 +12200,12 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Nisu odabrane kritične aplikacije" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Sigurno želiš nastaviti?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 82a892eb094..3d360541b93 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB titkosított tárhely a fájlmellékleteknek." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ titkosított tárhely a fájlmellékletekhez.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Saját kétlépcsős bejelentkezési lehetőségek mint a YubiKey és a Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "A Prémium előfizetés véget ért." + }, + "premiumSubscriptionEndedDesc": { + "message": "Az archívumhoz hozzáférés visszaszerzéséhez indítsuk újra a Prémium előfizetést. Ha az újraindítás előtt szerkesztjük egy archivált elem adatait, akkor az visszakerül a széfbe." + }, + "restartPremium": { + "message": "Prémium előfizetés újraindítása" + }, "additionalStorageGb": { "message": "Kiegészítő tárhely (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "További információ" }, + "migrationsFailed": { + "message": "Hiba történt a titkosítási beállítások frissítésekor." + }, + "updateEncryptionSettingsTitle": { + "message": "A titkosítási beállítások frissítése" + }, + "updateEncryptionSettingsDesc": { + "message": "Az új ajánlott titkosítási beállítások javítják a fiók biztonságát. Adjuk meg a mesterjelszót a frissítéshez most." + }, + "confirmIdentityToContinue": { + "message": "A folytatáshoz meg kell erősíteni a személyazonosságot." + }, + "enterYourMasterPassword": { + "message": "Mesterjelszó megadása" + }, + "updateSettings": { + "message": "Beállítások frissítése" + }, "deleteRecoverDesc": { "message": "Az email cím megadása lentebb a fiók helyreállításához és törléséhez." }, @@ -9820,6 +9856,14 @@ "message": "Túl drága", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Váltás ingyenes díkcsomagra", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Váltás ingyenes szervezetre", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Díjmentes 1 évre" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "A Bitwarden bővítmény megnyitása" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "A Bitwarden bővítmény telepítve van! Nyissuk meg a bővítményt a bejelentkezéshez és az automatikus kitöltés megkezdéséhez." + "bitwardenExtensionIsInstalled": { + "message": "A Bitwarden kiterjesztés telepítésre került!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Ha a bővítmény nem nyílt meg, előfordulhat, hogy meg kell nyitni a Bitwardent az ikonról ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Frissítsük a titkosítási beállításokat." }, - "updateSettings": { - "message": "Beállítások frissítése" - }, "algorithm": { "message": "Algoritmus" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Biztos folytatni szeretnénk?" + }, + "userVerificationFailed": { + "message": "A felhasználó ellenőrzése sikertelen volt." } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 86b9356ac08..4dc41e8f928 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "Penyimpanan terenkripsi 1 GB untuk lampiran file." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Opsi login dua langkah eksklusif seperti YubiKey dan Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Tambahan Penyimpanan (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Pelajari lebih lanjut" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Masukkan alamat email Anda di bawah ini untuk memulihkan dan menghapus akun Anda." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index ffc82cfcb4f..459f1dfd3a4 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -21,13 +21,13 @@ "message": "Rischio password" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Non hai i permessi per modificare questo elemento" }, "reviewAtRiskPasswords": { "message": "Controlla le password a rischio (deboli, esposte o riutilizzate). Seleziona le applicazioni critiche per determinare la priorità delle azioni di sicurezza." }, "reviewAtRiskLoginsPrompt": { - "message": "Review at-risk logins" + "message": "Rivedi i login a rischio" }, "dataLastUpdated": { "message": "Ultimo aggiornamento dei dati: $DATE$", @@ -94,7 +94,7 @@ "message": "Assegna compiti ai membri per monitorarne i progressi" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "Dopo aver esaminato le applicazioni e contrassegnato quelle critiche, comunica ai membri dell'organizzazione di cambiare le loro password." }, "sendReminders": { "message": "Invia promemoria" @@ -131,7 +131,7 @@ } }, "criticalApplicationsMarked": { - "message": "critical applications marked" + "message": "applicazioni critiche contrassegnate" }, "countOfCriticalApplications": { "message": "$COUNT$ applicazioni critiche", @@ -179,34 +179,34 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "Nessun dato disponibile" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Importa i dati di accesso della tua organizzazione per configurare Access Intelligence. Una volta fatto, sarai in grado di:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Contrassegna l'applicazione come critica" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "Ti sarà utile per rimuovere i rischi innanzi tutto per le tue applicazioni più importanti." }, "feature2Title": { - "message": "Help members improve their security" + "message": "Aiuta i membri a migliorare la loro sicurezza" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Assegna ai membri a rischio attività di sicurezza guidate per aggiornare, migliorare o rafforzare le loro credenziali di accesso." }, "feature3Title": { - "message": "Monitor progress" + "message": "Tieni sotto controllo i progressi" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Traccia i cambiamenti per monitorare i miglioramenti di sicurezza." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Genera un report" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Sei pronto per iniziare a generare report. Una volta generati, sarai in grado di:" }, "noCriticalApplicationsTitle": { "message": "Non hai contrassegnato nessuna applicazione come critica" @@ -224,7 +224,7 @@ "message": "Contrassegna l'elemento come critico" }, "applicationsSelected": { - "message": "Applicazioni selezionate" + "message": "applicazioni selezionate" }, "selectApplication": { "message": "Seleziona applicazione" @@ -236,7 +236,7 @@ "message": "Applicazioni contrassegnate come critiche" }, "criticalApplicationsMarkedSuccess": { - "message": "$COUNT$ applications marked as critical", + "message": "$COUNT$ applicazioni contrassegnate come critiche", "placeholders": { "count": { "content": "$1", @@ -266,13 +266,13 @@ "message": "Membri a rischio" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Questi membri hanno accesso a elementi vulnerabili per applicazioni critiche." }, "membersWithAtRiskPasswords": { - "message": "Members with at-risk passwords" + "message": "Membri con password a rischio" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "I membri della tua organizzazione dovranno cambiare le password vulnerabili. Riceveranno una notifica all'interno dell'estensione Bitwarden del loro browser." }, "membersAtRiskCount": { "message": "$COUNT$ membri a rischio", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Questi membri accedono ad applicazioni con password deboli, esposte, o riutilizzate." }, "atRiskMembersDescriptionNone": { "message": "Non ci sono utenti connessi con password deboli, esposte o riutilizzate." @@ -344,7 +344,7 @@ "message": "Applicazioni in attesa di revisione" }, "newApplicationsCardTitle": { - "message": "Review new applications" + "message": "Verifica le nuove applicazioni" }, "newApplicationsWithCount": { "message": "$COUNT$ nuove applicazioni", @@ -362,13 +362,13 @@ "message": "Controlla adesso" }, "allCaughtUp": { - "message": "All caught up!" + "message": "Nessuna notifica" }, "noNewApplicationsToReviewAtThisTime": { - "message": "No new applications to review at this time" + "message": "Nessuna nuova applicazione da verificare" }, "organizationHasItemsSavedForApplications": { - "message": "Your organization has items saved for $COUNT$ applications", + "message": "La tua organizzazione ha elementi salvati per $COUNT$ applicazioni", "placeholders": { "count": { "content": "$1", @@ -377,40 +377,40 @@ } }, "reviewApplicationsToSecureItems": { - "message": "Review applications to secure the items most critical to your organization's security" + "message": "Controlla le applicazioni e proteggi gli elementi critici per la sicurezza della tua organizzazione" }, "reviewApplications": { - "message": "Review applications" + "message": "Verifica le nuove applicazioni" }, "prioritizeCriticalApplications": { "message": "Priorità alle applicazioni critiche" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Indica quali sono le applicazioni più importanti la tua organizzazione. Potrai assegnare ai membri delle attività da completare per mettere in sicurezza le loro credenziali." }, "reviewNewApplications": { - "message": "Review new applications" + "message": "Verifica le nuove applicazioni" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Rivedi le nuove applicazioni con elementi vulnerabili e contrassegna come critiche quelle che desideri monitorare da vicino. Potrai assegnare ai membri delle attività da completare per mettere in sicurezza le loro credenziali." }, "clickIconToMarkAppAsCritical": { - "message": "Click the star icon to mark an app as critical" + "message": "Clicca sull'icona stella per contrassegnare un'app come critica" }, "markAsCriticalPlaceholder": { "message": "La funzionalità per contrassegnare gli elementi critici sarà implementata in un aggiornamento futuro" }, "applicationReviewSaved": { - "message": "Application review saved" + "message": "Applicazione verificata" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Nuove applicazioni verificate" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Errore nell'aggiornamento dello stato di verifica" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Riprova" }, "unmarkAsCritical": { "message": "Contrassegna l'elemento come non critico" @@ -875,7 +875,7 @@ "message": "Preferiti" }, "taskSummary": { - "message": "Task summary" + "message": "Riepilogo attività" }, "types": { "message": "Tipi" @@ -1402,7 +1402,7 @@ "message": "Usa login unificato (SSO)" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "La tua organizzazione richiede un accesso Single Sign-On (SSO)." }, "welcomeBack": { "message": "Bentornato/a" @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB di spazio di archiviazione crittografato per gli allegati." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Opzioni di verifica in due passaggi proprietarie come YubiKey e Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Spazio di archiviazione aggiuntivo (GB)" }, @@ -3245,16 +3263,16 @@ "message": "Prossimo addebito" }, "nextChargeHeader": { - "message": "Next Charge" + "message": "Prossimo addebito" }, "plan": { - "message": "Plan" + "message": "Piano" }, "details": { "message": "Dettagli" }, "discount": { - "message": "discount" + "message": "sconto" }, "downloadLicense": { "message": "Scarica licenza" @@ -4461,31 +4479,31 @@ "message": "Aggiorna browser" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Generazione del tuo Access Intelligence..." }, "fetchingMemberData": { - "message": "Fetching member data..." + "message": "Recupero dei dati dei membri..." }, "analyzingPasswordHealth": { - "message": "Analyzing password health..." + "message": "Analisi della salute della password..." }, "calculatingRiskScores": { - "message": "Calculating risk scores..." + "message": "Calcolo dei punteggi di rischio..." }, "generatingReportData": { - "message": "Generating report data..." + "message": "Generazione dati del rapporto..." }, "savingReport": { - "message": "Saving report..." + "message": "Salvataggio..." }, "compilingInsights": { - "message": "Compiling insights..." + "message": "Compilazione dei dati..." }, "loadingProgress": { - "message": "Loading progress" + "message": "Caricamento in corso" }, "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "message": "Attendi..." }, "riskInsightsRunReport": { "message": "Avvia report" @@ -4573,7 +4591,7 @@ "message": "Invito accettato" }, "invitationAcceptedDesc": { - "message": "Successfully accepted your invitation." + "message": "Hai accettato l'invito." }, "inviteInitAcceptedDesc": { "message": "Ora puoi accedere a questa organizzazione." @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Ulteriori informazioni" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Aggiorna le impostazioni" + }, "deleteRecoverDesc": { "message": "Inserisci la tua email per recuperare ed eliminare il tuo account." }, @@ -5810,7 +5846,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Tutti gli elementi saranno posseduti e memorizzati dall'organizzazione, e saranno disponibili controlli, visibilità e reporting. Quando attivato, è disponibile una raccolta predefinita per ogni utente. Per sapere di più sulla gestione del ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -5837,63 +5873,63 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about the credential lifecycle.'" }, "availableNow": { - "message": "Available now" + "message": "Disponibile ora" }, "autoConfirm": { - "message": "Automatic confirmation of new users" + "message": "Conferma automatica dei nuovi utenti" }, "autoConfirmDescription": { - "message": "New users invited to the organization will be automatically confirmed when an admin’s device is unlocked.", + "message": "I nuovi utenti invitati ad accedere all'organizzazione saranno automaticamente confermati quando il dispositivo di un amministratore è sbloccato.", "description": "This is the description of the policy as it appears in the 'Policies' page" }, "howToTurnOnAutoConfirm": { - "message": "How to turn on automatic user confirmation" + "message": "Come attivare la conferma automatica dell'utente" }, "autoConfirmExtension1": { - "message": "Open your Bitwarden extension" + "message": "Apri l'estensione di Bitwarden" }, "autoConfirmExtension2": { - "message": "Select", + "message": "Seleziona", "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtension3": { - "message": " Turn on", + "message": " Attiva", "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { - "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." + "message": "L'estensione del browser Bitwarden è stata aperta con successo. Ora è possibile attivare l'impostazione di conferma automatica dell'utente." }, "autoConfirmPolicyEditDescription": { - "message": "New users invited to the organization will be automatically confirmed when an admin’s device is unlocked. Before turning on this policy, please review and agree to the following: ", + "message": "I nuovi utenti invitati ad accedere all'organizzazione saranno automaticamente confermati quando il dispositivo di un amministratore è sbloccato. Prima di attivare questa funzione, leggi attentamente e conferma di accettare quanto segue: ", "description": "This is the description of the policy as it appears inside the policy edit dialog" }, "autoConfirmAcceptSecurityRiskTitle": { - "message": "Potential security risk. " + "message": "Potenziale rischio per la sicurezza. " }, "autoConfirmAcceptSecurityRiskDescription": { - "message": "Automatic user confirmation could pose a security risk to your organization’s data." + "message": "La conferma automatica dell'utente potrebbe rappresentare un rischio per la sicurezza dei dati della tua organizzazione." }, "autoConfirmAcceptSecurityRiskLearnMore": { - "message": "Learn about the risks", + "message": "Scopri i rischi", "description": "The is the link copy for the first check box option in the edit policy dialog" }, "autoConfirmSingleOrgRequired": { - "message": "Single organization policy required. " + "message": "È richiesto il Single Sign-On (SSO)." }, "autoConfirmSingleOrgRequiredDesc": { - "message": "All members must only belong to this organization to activate this automation." + "message": "Tutti i membri devono appartenere esclusivamente a questa organizzazione per attivare questa automazione." }, "autoConfirmSingleOrgExemption": { - "message": "Single organization policy will extend to all roles. " + "message": "La politica di organizzazione unica si estenderà a tutti i ruoli. " }, "autoConfirmNoEmergencyAccess": { - "message": "No emergency access. " + "message": "Non è stato configurato l'accesso d'emergenza. " }, "autoConfirmNoEmergencyAccessDescription": { - "message": "Emergency Access will be removed." + "message": "L'accesso d'emergenza sarà rimosso." }, "autoConfirmCheckBoxLabel": { - "message": "I accept these risks and policy updates" + "message": "Accetto questi rischi e aggiornamenti sulle politiche" }, "personalOwnership": { "message": "Rimuovi cassaforte individuale" @@ -5948,16 +5984,16 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "uriMatchDetectionPolicy": { - "message": "Default URI match detection" + "message": "Metodo di corrispondenza degli URL predefinito" }, "uriMatchDetectionPolicyDesc": { - "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + "message": "Determina con quale logica i login sono suggeriti in base all'URL visitato. Amministratori e proprietari possono modificare questa impostazione." }, "uriMatchDetectionOptionsLabel": { - "message": "Default URI match detection" + "message": "Metodo di corrispondenza predefinito" }, "invalidUriMatchDefaultPolicySetting": { - "message": "Please select a valid URI match detection option.", + "message": "Scegli un metodo di corrispondenza valido.", "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." }, "modifiedPolicyId": { @@ -6614,10 +6650,10 @@ "message": "La tua password principale non soddisfa uno o più politiche della tua organizzazione. Per accedere alla cassaforte, aggiornala ora. Procedere ti farà uscire dalla sessione corrente, richiedendoti di accedere di nuovo. Le sessioni attive su altri dispositivi potrebbero continuare a rimanere attive per un massimo di un'ora." }, "automaticAppLoginWithSSO": { - "message": "Automatic login with SSO" + "message": "Accesso automatico con SSO" }, "automaticAppLoginWithSSODesc": { - "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." + "message": "Estendi la sicurezza e la comodità del Single Sign-On (SSO) alle app non gestite. Quando gli utenti lanciano un'app collegata, i dati di accesso sono inseriti e inviati automaticamente: si tratta di un flusso a singolo clic, velocissimo e sicuro, che unisce il provider d'identità all'applicazione." }, "automaticAppLoginIdpHostLabel": { "message": "Host Identity provider" @@ -6629,31 +6665,31 @@ "message": "La tua organizzazione ha aggiornato le tue opzioni di decrittazione. Per favore, imposta una master password per accedere alla tua cassaforte." }, "sessionTimeoutPolicyTitle": { - "message": "Session timeout" + "message": "Timeout della sessione" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "Imposta un timeout di sessione massimo per tutti i membri tranne i proprietari." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "Timeout massimo consentito" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "È necessario impostare un limite massimo di timeout." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "Valore non valido: prova ad inserirlo nuovamente." }, "sessionTimeoutAction": { - "message": "Session timeout action" + "message": "Azione al timeout della sessione" }, "immediately": { - "message": "Immediately" + "message": "Subito" }, "onSystemLock": { - "message": "On system lock" + "message": "Al blocco del dispositivo" }, "onAppRestart": { - "message": "On app restart" + "message": "Al riavvio dell'app" }, "hours": { "message": "Ore" @@ -6662,19 +6698,19 @@ "message": "Minuti" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Sei sicuro di voler disabilitare il valore massimo di timeout per tutti i membri?" }, "sessionTimeoutConfirmationNeverDescription": { - "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." + "message": "Questa opzione salverà localmente la chiave crittografica di ogni membro sul suo dispositivo. Se scegli questa opzione, assicurati che i loro dispositivi siano adeguatamente protetti." }, "learnMoreAboutDeviceProtection": { - "message": "Learn more about device protection" + "message": "Ulteriori informazioni sulla protezione dei dispositivi" }, "sessionTimeoutConfirmationOnSystemLockTitle": { - "message": "\"System lock\" will only apply to the browser and desktop app" + "message": "'Blocco di sistema' si applica solo all'estensione del browser e all'app desktop" }, "sessionTimeoutConfirmationOnSystemLockDescription": { - "message": "The mobile and web app will use \"on app restart\" as their maximum allowed timeout, since the option is not supported." + "message": "Le versioni mobile e Web imposteranno un timeout al riavvio dell'app, perché l'opzione non è supportata." }, "vaultTimeoutPolicyInEffect": { "message": "Le politiche della tua organizzazione hanno impostato il timeout massimo consentito della tua cassaforte su $HOURS$ ore e $MINUTES$ minuti.", @@ -7218,7 +7254,7 @@ "message": "Devi aggiungere lo URL del server di base o almeno un ambiente personalizzato." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "Gli indirizzi devono usare il protocollo HTTPS." }, "apiUrl": { "message": "URL del server API" @@ -7393,7 +7429,7 @@ "message": "Accesso negato. Non hai i permessi necessari per visualizzare questa pagina." }, "noPageAccess": { - "message": "You do not have access to this page" + "message": "Non hai accesso a questa pagina" }, "masterPassword": { "message": "Password principale" @@ -9820,6 +9856,14 @@ "message": "Troppo costoso", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Passaggio al piano gratuito", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Passaggio all'organizzazione gratuita", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Gratis per un anno" }, @@ -9851,7 +9895,7 @@ "message": "Assegna attività" }, "assignSecurityTasksToMembers": { - "message": "Send notifications to change passwords" + "message": "Invia notifiche ai membri per invitarli a modificare le loro password" }, "assignToCollections": { "message": "Assegna alle raccolte" @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Estensione di Bitwarden installata!" }, - "openTheBitwardenExtension": { - "message": "Apri l'estensione Bitwarden" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "L'estensione Bitwarden è installata! Clicca sul pulsante dell'estensione sulla barra del browser per accedere e avviare l'auto-riempimento." + "bitwardenExtensionIsInstalled": { + "message": "Estensione di Bitwarden installata!" }, "openExtensionToAutofill": { "message": "Apri l'estensione cliccando sul tasto della barra degli strumenti e accedi con i tuoi dati per attivare il riempimento automatico." @@ -11687,6 +11720,14 @@ "message": "Guida introduttiva", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Se l'estensione non s'è avviata, potrebbe essere necessario cliccare 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " nella barra degli strumenti del browser.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "il Manuale di Bitwarden", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12083,73 +12124,70 @@ "message": "Vedi i piani Business" }, "updateEncryptionSettings": { - "message": "Update encryption settings" + "message": "Aggiorna le impostazioni di crittografia" }, "updateYourEncryptionSettings": { - "message": "Update your encryption settings" - }, - "updateSettings": { - "message": "Update settings" + "message": "Aggiorna le impostazioni di crittografia" }, "algorithm": { - "message": "Algorithm" + "message": "Algoritmo" }, "encryptionKeySettingsHowShouldWeEncryptYourData": { - "message": "Choose how Bitwarden should encrypt your vault data. All options are secure, but stronger methods offer better protection - especially against brute-force attacks. Bitwarden recommends the default setting for most users." + "message": "Scegli la modalità di crittografia per la tua cassaforte. Tutte le opzioni sono assolutamente sicure, ma alcuni metodi avanzati offrono una protezione superiore. Bitwarden consiglia l'impostazione predefinita per la maggior parte degli utenti." }, "encryptionKeySettingsIncreaseImproveSecurity": { - "message": "Increasing the values above the default will improve security, but your vault may take longer to unlock as a result." + "message": "Scegliere un valore superiore al predefinito aumenta il livello di sicurezza, ma la cassaforte potrebbe impiegare più tempo a sbloccarsi." }, "encryptionKeySettingsAlgorithmPopoverTitle": { - "message": "About encryption algorithms" + "message": "Informazioni sugli algoritmi di crittografia" }, "encryptionKeySettingsAlgorithmPopoverPBKDF2": { - "message": "PBKDF2-SHA256 is a well-tested encryption method that balances security and performance. Good for all users." + "message": "PBKDF2-SHA256 è un metodo di crittografia ben testato che bilancia sicurezza e prestazioni. Consigliato per tutti gli utenti." }, "encryptionKeySettingsAlgorithmPopoverArgon2Id": { - "message": "Argon2id offers stronger protection against modern attacks. Best for advanced users with powerful devices." + "message": "Argon2id offre una protezione più forte contro gli attacchi moderni. Consigliato per gli utenti avanzati con dispositivi potenti." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "CAP / codice postale" }, "cardNumberLabel": { - "message": "Card number" + "message": "Numero di carta" }, "startFreeFamiliesTrial": { - "message": "Start free Families trial" + "message": "Inizia la prova gratuita Famiglie" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Blocca la creazione di account per domini rivendicati" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "Impedisci agli utenti di creare account al di fuori della tua organizzazione utilizzando indirizzi email da domini rivendicati." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "Prima di attivare questa politica è necessario attivare un dominio." }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "Imposta un metodo di sblocco per modificare l'azione al timeout della cassaforte." }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "Le impostazioni di timeout sono state impostate secondo le politiche aziendali" }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "Il timeout della tua cassaforte supera i limiti impostati dalla tua organizzazione." }, "neverLockWarning": { - "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + "message": "Vuoi davvero impostare il blocco su 'Mai'? In questa modalità, la chiave di decrittazione della cassaforte sarà conservata localmente sul tuo dispositivo: assicurati di mantenerlo protetto e al sicuro." }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Azione al timeout" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Timeout della sessione" }, "appearance": { - "message": "Appearance" + "message": "Aspetto" }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "Il timeout supera la restrizione imposta dalla tua organizzazione: massimo $HOURS$ ora/e e $MINUTES$ minuto/i", "placeholders": { "hours": { "content": "$1", @@ -12162,9 +12200,12 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Non ci sono applicazioni contrassegnate come critiche" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Vuoi davvero continuare?" + }, + "userVerificationFailed": { + "message": "Verifica dell'utente non riuscita." } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 66f30f93f96..33535634bc8 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1GB の暗号化されたファイルストレージ" }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "YubiKey、Duo などのプロプライエタリな2段階認証オプション。" }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "追加ストレージ容量(GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "詳細" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "メールアドレスを入力してアカウントを削除します。" }, @@ -9820,6 +9856,14 @@ "message": "料金が高すぎる", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "1年間無料" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index c4069973d90..47a46367feb 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index b23e2e3cbac..608215f8155 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 49d834e9cae..2f0759ff939 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "ಫೈಲ್ ಲಗತ್ತುಗಳಿಗಾಗಿ 1 ಜಿಬಿ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಸಂಗ್ರಹ." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "ಹೆಚ್ಚುವರಿ ಸಂಗ್ರಹಣೆ (ಜಿಬಿ)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "ನಿಮ್ಮ ಖಾತೆಯನ್ನು ಮರುಪಡೆಯಲು ಮತ್ತು ಅಳಿಸಲು ನಿಮ್ಮ ಇಮೇಲ್ ವಿಳಾಸವನ್ನು ಕೆಳಗೆ ನಮೂದಿಸಿ." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 33d812d1537..aaef9d378e9 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1GB의 암호화된 파일 저장소." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "YubiKey, Duo와 같은 \b상용 2단계 로그인 방법." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "추가 저장소 용량 (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "더 알아보기" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "계정을 복구하거나 삭제하려면 아래에 이메일 주소를 입력하십시오." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 9b1b54cf0b5..d627835b1f4 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -94,7 +94,7 @@ "message": "Piešķirt dalībniekiem uzdevumus, lai pārraudzītu virzību" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "Tiklīdz lietotnes ir izskatītas un atzīmētas kā būtiskas, piešķir uzdevumus dalībniekiem nomainīt savas paroles!" }, "sendReminders": { "message": "Nosūtīt atgādinājumus" @@ -179,13 +179,13 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "Dati netika atrasti" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Ievieto savas apvienības pieteikšanās datus, lai uzsāktu Access Intelligence izmantošanu. Tiklīdz to izdarīsi, varēsi:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Atzīmēt lietotnes kā būtiskas" }, "feature1Description": { "message": "This will help you remove risks to your most important applications first." @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB šifrētas krātuves datņu pielikumiem." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ šifrētas krātuves datņu pielikumiem.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Tādas slēgtā pirmavota divpakāpju pieteikšanās iespējas kā YubiKey un Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Tavs Premium abonements beidzās" + }, + "premiumSubscriptionEndedDesc": { + "message": "Lai atgūtu piekļuvi savam arhīvam, jāatsāk Premium abonements. Ja labosi arhivēta vienuma informāciju pirms atsākšanas, tas tiks pārvietots atpakaļ Tavā glabātavā." + }, + "restartPremium": { + "message": "Atsākt Premium" + }, "additionalStorageGb": { "message": "Papildu krātuve (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Uzzināt vairāk" }, + "migrationsFailed": { + "message": "Atgadījās kļūda šifrēšanas iestatījumu atjaunināšanā." + }, + "updateEncryptionSettingsTitle": { + "message": "Atjaunini savus šifrēšanas iestatījumus" + }, + "updateEncryptionSettingsDesc": { + "message": "Jaunie ieteicamie šifrēšanas iestatījumi uzlabos Tava konta drošību. Jāievada sava galvenā parole, lai atjauninātu tagad." + }, + "confirmIdentityToContinue": { + "message": "Jāapliecina sava identitāte, lai turpinātu" + }, + "enterYourMasterPassword": { + "message": "Jāievada sava galvenā parole" + }, + "updateSettings": { + "message": "Atjaunināt Iestatījumus" + }, "deleteRecoverDesc": { "message": "Jāievada e-pasta adrese zemāk esošajā laukā, lai atkoptu un izdzēstu savu kontu." }, @@ -5810,7 +5846,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Visi vienumi piederēs apvienībai un tiks tajā saglabāti, iespējojot visaptverošu pārvaldību, redzamību un ziņošanu apvienībā. Kad ieslēgta, noklusējuma krājums, kurā glabātat vienumus, būs pieejams katram dalībniekam. Uzzināt vairāk par ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -9820,6 +9856,14 @@ "message": "Pārāk dārgi", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Pārslēdzas uz bezmaksas plānu", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Pārslēdzas uz bezmaksas apvienību", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "1 gadu bez maksas" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden paplašinājums uzstādīts." }, - "openTheBitwardenExtension": { - "message": "Atvērt Bitwarden paplašinājumu" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Bitwarden paplašinājums ir uzstādīts. Atver paplašinājumu, lai pieteiktos un sāktu automātsko aizpildīšanu!" + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden paplašinājums ir uzstādīts." }, "openExtensionToAutofill": { "message": "Jāatver paplašinājums, lai pieteiktos un uzsāktu automātisko aizpildi." @@ -11687,6 +11720,14 @@ "message": "Mācīšanāš centrs", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Ja paplašinājums neatvērās, var būt nepieciešams atvērt Bitwarden ar ikonu ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Palīdzības centrs", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Atjaunini savus šifrēšanas iestatījumus" }, - "updateSettings": { - "message": "Atjaunināt Iestatījumus" - }, "algorithm": { "message": "Algoritms" }, @@ -12119,13 +12157,13 @@ "message": "Uzsākt ģimenes plāna bezmaksas izmēģinājumu" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Liegt konta izveidošanu pieteiktajiem domēniem" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "Neļaut lietotājiem kontu izveidošanu ārpus apvienības ar e-pasta adresēm no pieteikajiem domēniem." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "Domēnam jābūt pieteiktam pirms šīs pamatnostādnes aktivēšanas." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Jāuzstāda atslēgšanas veids, lai mainītu glabātavas noildzes darbību." @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Vai tiešām turpināt?" + }, + "userVerificationFailed": { + "message": "Lietotāja apliecināšana neizdevās." } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 1632226b9ce..8d49e35690e 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "ഫയൽ അറ്റാച്ചുമെന്റുകൾക്കായി 1 GB എൻക്രിപ്റ്റുചെയ്‌ത സ്റ്റോറേജ്." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional Storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "കൂടുതൽ അറിയുക" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "നിങ്ങളുടെ അക്കൗണ്ട് വീണ്ടെടുക്കുന്നതിനും ഇല്ലാതാക്കുന്നതിനും ചുവടെ നിങ്ങളുടെ ഇമെയിൽ വിലാസം നൽകുക." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index b71d94fe31e..6ba6661c478 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index b23e2e3cbac..608215f8155 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 33ff6b1fb58..042444814e6 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB med kryptert fillagring." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Ytterligere lagringsplass (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Lær mer" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Skriv inn din E-postadresse nedenfor for å få tilbake tilgangen til og å slette din konto." }, @@ -9820,6 +9856,14 @@ "message": "For dyrt", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Gratis i 1 år" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Hjelpesenter", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index d87792ef4b5..31367740728 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 0d8840a1889..333df664f77 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB versleutelde opslag voor bijlagen." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ versleutelde opslag voor bijlagen.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Gepatenteerde 2 stap login opties zoals YubiKey en Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Je Premium-abonnement is afgelopen" + }, + "premiumSubscriptionEndedDesc": { + "message": "Herstart je Premium-abonnement om toegang tot je archief te krijgen. Als je de details wijzigt voor een gearchiveerd item voor het opnieuw opstarten, zal het terug naar je kluis worden verplaatst." + }, + "restartPremium": { + "message": "Premium herstarten" + }, "additionalStorageGb": { "message": "Extra opslagruimte (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Meer informatie" }, + "migrationsFailed": { + "message": "Er is een fout opgetreden bij het bijwerken van de versleutelingsinstellingen." + }, + "updateEncryptionSettingsTitle": { + "message": "Je versleutelingsinstellingen bijwerken" + }, + "updateEncryptionSettingsDesc": { + "message": "De nieuwe aanbevolen versleutelingsinstellingen verbeteren de beveiliging van je account. Voer je hoofdwachtwoord in om nu bij te werken." + }, + "confirmIdentityToContinue": { + "message": "Bevestig je identiteit om door te gaan" + }, + "enterYourMasterPassword": { + "message": "Voer je hoofdwachtwoord in" + }, + "updateSettings": { + "message": "Instellingen bijwerken" + }, "deleteRecoverDesc": { "message": "Voer hieronder je e-mailadres in om je account te herstellen en te verwijderen." }, @@ -9820,6 +9856,14 @@ "message": "Te duur", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Overstappen naar gratis abonnement", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Overstappen naar gratis organisatie", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "1 jaar gratis" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extensie geïnstalleerd!" }, - "openTheBitwardenExtension": { - "message": "De Bitwarden-extensie openen" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "De Bitwarden-extensie is geïnstalleerd! Open de extensie om in te loggen en te beginnen met automatisch invullen." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden-extensie geïnstalleerd!" }, "openExtensionToAutofill": { "message": "Open de extensie om in te loggen en te beginnen met automatisch invullen." @@ -11687,6 +11720,14 @@ "message": "Leercentrum", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Als de extensie niet is geopend, moet je misschien Bitwarden via het pictogram ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " op de werkbalk openen.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Helpcentrum", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Je versleutelingsinstellingen bijwerken" }, - "updateSettings": { - "message": "Instellingen bijwerken" - }, "algorithm": { "message": "Algoritme" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Weet je zeker dat je wilt doorgaan?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 2777a14d5c7..32c452cf657 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index b23e2e3cbac..608215f8155 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 38b7fb51de3..f5e6b898bff 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB przestrzeni na zaszyfrowane załączniki." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Własnościowe opcje logowania dwuetapowego, takie jak YubiKey i Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Dodatkowa przestrzeń (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Dowiedz się więcej" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Wpisz adres e-mail poniżej, aby odzyskać i usunąć konto." }, @@ -9820,6 +9856,14 @@ "message": "Zbyt drogo", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Za darmo na 1 rok" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Rozszerzenie Bitwarden zainstalowane!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Otwórz rozszerzenie, aby zalogować się i rozpocząć autouzupełnianie." @@ -11687,6 +11720,14 @@ "message": "Centrum Szkoleniowe", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Centrum Pomocy", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 09dd1b14c4a..ff5686cbba8 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -21,13 +21,13 @@ "message": "Risco de senhas" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Você não tem permissão para editar este item" }, "reviewAtRiskPasswords": { - "message": "Revise senhas em risco (fracas, expostas, ou reutilizadas) em todos os aplicativos. Selecione seus aplicativos mais críticos para priorizar ações de segurança para que seus usuários resolvem senhas em risco." + "message": "Revise senhas em risco (fracas, expostas, ou reutilizadas) entre aplicativos. Selecione seus aplicativos mais críticos para priorizar ações de segurança para que seus usuários resolvem senhas em risco." }, "reviewAtRiskLoginsPrompt": { - "message": "Review at-risk logins" + "message": "Revisar credenciais em risco" }, "dataLastUpdated": { "message": "Última atualização dos dados: $DATE$", @@ -45,7 +45,7 @@ "message": "Membros notificados" }, "revokeMembers": { - "message": "Revogar membro" + "message": "Revogar membros" }, "restoreMembers": { "message": "Restaurar membros" @@ -63,7 +63,7 @@ } }, "createNewLoginItem": { - "message": "Criar credencial" + "message": "Criar item de credencial" }, "percentageCompleted": { "message": "$PERCENT$% concluído", @@ -91,10 +91,10 @@ "message": "Progresso de alteração de senha" }, "assignMembersTasksToMonitorProgress": { - "message": "Atribuir tarefas a membros para monitorar progresso" + "message": "Atribua tarefas a membros para monitorar o progresso" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "Depois que você revisar aplicativos e os marcar como críticos, atribua tarefas aos seus membros para alterar suas senhas." }, "sendReminders": { "message": "Enviar lembretes" @@ -103,10 +103,10 @@ "message": "Ao marcar aplicativos como críticos, eles aparecerão aqui." }, "viewAtRiskMembers": { - "message": "Visualizar membros em risco" + "message": "Ver membros em risco" }, "viewAtRiskApplications": { - "message": "Visualizar aplicativos em risco" + "message": "Ver aplicativos em risco" }, "criticalApplicationsAreAtRisk": { "message": "$COUNT$ aplicativos críticos dos $TOTAL$ estão em risco devido a senhas em risco", @@ -131,7 +131,7 @@ } }, "criticalApplicationsMarked": { - "message": "critical applications marked" + "message": "aplicativos críticos marcados" }, "countOfCriticalApplications": { "message": "$COUNT$ aplicativos críticos", @@ -161,7 +161,7 @@ } }, "newPasswordsAtRisk": { - "message": "$COUNT$ new passwords at-risk", + "message": "$COUNT$ novas senhas em risco", "placeholders": { "count": { "content": "$1", @@ -179,40 +179,40 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "Nenhum dado encontrado" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Importe os dados de autenticação da sua organização para começar com a Inteligência de Acesso. Depois de fazer isso, você poderá:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Marque aplicativos como críticos" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "Isso te ajudará a remover riscos dos seus aplicativos mais importantes primeiro." }, "feature2Title": { - "message": "Help members improve their security" + "message": "Ajude os membros a melhorar sua segurança" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Atribua membros em risco tarefas guiadas de segurança para atualizar as credenciais." }, "feature3Title": { - "message": "Monitor progress" + "message": "Monitore o progresso" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Rastreie alterações ao longo do tempo para mostrar melhorias de segurança." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Gerar relatório" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Você está pronto para começar a gerar relatórios. Depois de gerar, você poderá:" }, "noCriticalApplicationsTitle": { "message": "Você não marcou nenhum aplicativo como crítico" }, "noCriticalApplicationsDescription": { - "message": "Selecione os seus aplicativos mais críticos para priorizar ações de segurança de correção de senhas em risco dos seus usuários." + "message": "Selecione os seus aplicativos mais críticos para priorizar ações de segurança dos seus usuários de correção de senhas em risco." }, "markCriticalApplications": { "message": "Selecionar aplicativos críticos" @@ -236,7 +236,7 @@ "message": "Aplicativos marcados como críticos" }, "criticalApplicationsMarkedSuccess": { - "message": "$COUNT$ applications marked as critical", + "message": "$COUNT$ aplicativos marcados como críticos", "placeholders": { "count": { "content": "$1", @@ -260,19 +260,19 @@ "message": "Total de senhas" }, "searchApps": { - "message": "Procurar aplicativos" + "message": "Buscar aplicativos" }, "atRiskMembers": { "message": "Membros em risco" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Esses membros têm acesso a itens vulneráveis de aplicativos críticos." }, "membersWithAtRiskPasswords": { - "message": "Members with at-risk passwords" + "message": "Membros com senhas em risco" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Membros da sua organização serão atribuídos a uma tarefa para alterar senhas vulneráveis. Eles receberão uma notificação dentro da sua extensão de navegador do Bitwarden." }, "membersAtRiskCount": { "message": "$COUNT$ membros em risco", @@ -302,19 +302,19 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Esses membros estão se conectando a aplicativos críticos com senhas fracas, expostas, ou reutilizadas." }, "atRiskMembersDescriptionNone": { - "message": "Não há nenhum membro se conectando em aplicativos com senhas fracas, expostas, ou reutilizadas." + "message": "Não há nenhum membro se conectando a aplicativos com senhas fracas, expostas, ou reutilizadas." }, "atRiskApplicationsDescription": { "message": "Esses aplicativos têm senhas fracas, expostas, ou reutilizadas." }, "atRiskApplicationsDescriptionNone": { - "message": "Não há aplicativos com senhas fracas, expostas ou reutilizadas." + "message": "Não há aplicativos com senhas fracas, expostas, ou reutilizadas." }, "atRiskMembersDescriptionWithApp": { - "message": "Esses membros estão conectando-se no $APPNAME$ com senhas fracas, expostas, ou reutilizadas.", + "message": "Esses membros estão se conectando a $APPNAME$ com senhas fracas, expostas, ou reutilizadas.", "placeholders": { "appname": { "content": "$1", @@ -344,7 +344,7 @@ "message": "Aplicativos que precisam de revisão" }, "newApplicationsCardTitle": { - "message": "Review new applications" + "message": "Revisar aplicativos novos" }, "newApplicationsWithCount": { "message": "$COUNT$ aplicativos novos", @@ -362,13 +362,13 @@ "message": "Revisar agora" }, "allCaughtUp": { - "message": "All caught up!" + "message": "Tudo pronto!" }, "noNewApplicationsToReviewAtThisTime": { - "message": "No new applications to review at this time" + "message": "Não há aplicativos novos para revisar neste momento" }, "organizationHasItemsSavedForApplications": { - "message": "Your organization has items saved for $COUNT$ applications", + "message": "A sua organização tem itens salvos para $COUNT$ aplicativos", "placeholders": { "count": { "content": "$1", @@ -377,46 +377,46 @@ } }, "reviewApplicationsToSecureItems": { - "message": "Review applications to secure the items most critical to your organization's security" + "message": "Revise os aplicativos para proteger os itens mais críticos para a segurança da sua organização" }, "reviewApplications": { - "message": "Review applications" + "message": "Revisar aplicativos" }, "prioritizeCriticalApplications": { "message": "Priorizar aplicativos críticos" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Selecione quais aplicativos são os mais críticos para a sua organização. E então, você poderá atribuir tarefas de segurança aos membros, para se livrar dos riscos." }, "reviewNewApplications": { - "message": "Review new applications" + "message": "Revisar aplicativos novos" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Revise aplicativos novos com itens vulneráveis e marque aqueles que você gostaria de monitorar com atenção, como crítico. E então, você poderá atribuir tarefas de segurança aos membros, para se livrar de riscos." }, "clickIconToMarkAppAsCritical": { - "message": "Click the star icon to mark an app as critical" + "message": "Clique no ícone de estrela para marcar um aplicativo como crítico" }, "markAsCriticalPlaceholder": { - "message": "A funcionalidade de marcar como crítico será implementada em uma atualização futura" + "message": "O recurso de marcar como crítico será implementado em uma atualização futura" }, "applicationReviewSaved": { - "message": "Application review saved" + "message": "Revisão de aplicativo salva" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Novos aplicativos revisados" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Erro ao salvar o estado da revisão" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Tente novamente" }, "unmarkAsCritical": { "message": "Desmarcar como crítico" }, "criticalApplicationUnmarkedSuccessfully": { - "message": "Aplicativo desmarcado com sucesso como crítico" + "message": "Aplicativo desmarcado como crítico com sucesso" }, "whatTypeOfItem": { "message": "Que tipo de item é este?" @@ -441,19 +441,19 @@ "message": "Novo URI" }, "username": { - "message": "Nome de Usuário" + "message": "Nome de usuário" }, "password": { "message": "Senha" }, "newPassword": { - "message": "Nova senha" + "message": "Senha nova" }, "passphrase": { - "message": "Frase Secreta" + "message": "Frase secreta" }, "notes": { - "message": "Notas" + "message": "Anotações" }, "privateNote": { "message": "Anotação privada" @@ -468,13 +468,13 @@ "message": "Nome do titular do cartão" }, "loginCredentials": { - "message": "Credenciais de login" + "message": "Credenciais de acesso" }, "personalDetails": { "message": "Detalhes pessoais" }, "identification": { - "message": "Identidade" + "message": "Identificação" }, "contactInfo": { "message": "Informação de contato" @@ -483,7 +483,7 @@ "message": "Detalhes do cartão" }, "cardBrandDetails": { - "message": "Detalhes $BRAND$", + "message": "Detalhes da $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -504,7 +504,7 @@ "message": "Site (URI)" }, "websiteUriCount": { - "message": "$COUNT$° site (URI)", + "message": "Site (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": { @@ -569,13 +569,13 @@ "message": "Código de segurança / CVV" }, "identityName": { - "message": "Nome na identidade" + "message": "Nome da identidade" }, "company": { "message": "Empresa" }, "ssn": { - "message": "Cadastro de Pessoas Físicas (CPF)" + "message": "Número de CPF" }, "passportNumber": { "message": "Número do passaporte" @@ -659,13 +659,13 @@ "message": "Chave do autenticador (TOTP)" }, "totpHelperTitle": { - "message": "Tornar a verificação em duas etapas fácil" + "message": "Torne a verificação em 2 etapas mais simples" }, "totpHelper": { "message": "O Bitwarden pode armazenar e preencher códigos de verificação de duas etapas. Copie e cole a chave neste campo." }, "totpHelperWithCapture": { - "message": "O Bitwarden pode armazenar e preencher códigos de verificação de duas etapas. Selecione o ícone de câmera para tirar uma captura da tela do código QR de autenticador deste site, ou copie e cole a chave neste campo." + "message": "O Bitwarden pode armazenar e preencher códigos de verificação de 2 etapas. Selecione o ícone da câmera e tire uma captura de tela do código QR do autenticador nesse site ou copie e cole a chave nesse campo." }, "learnMoreAboutAuthenticators": { "message": "Saiba mais sobre os autenticadores" @@ -680,7 +680,7 @@ "message": "Texto" }, "cfTypeHidden": { - "message": "Ocultado" + "message": "Oculto" }, "cfTypeBoolean": { "message": "Booleano" @@ -784,10 +784,10 @@ "description": "Toggling an expand/collapse state." }, "checkPassword": { - "message": "Verifique se a senha foi exposta." + "message": "Confira se a senha foi exposta." }, "passwordExposed": { - "message": "Esta senha foi exposta $VALUE$ vez(es) em violações de dados. Você deve alterá-la.", + "message": "Esta senha foi exposta $VALUE$ vez(es) em vazamentos de dados. Você deve alterá-la.", "placeholders": { "value": { "content": "$1", @@ -796,7 +796,7 @@ } }, "passwordSafe": { - "message": "Esta senha não foi encontrada em violações de dados conhecidos. Deve ser seguro de usar." + "message": "Esta senha não foi encontrada em vazamentos de dados conhecidos. Deve ser segura de usar." }, "save": { "message": "Salvar" @@ -814,10 +814,10 @@ "message": "Fechar" }, "delete": { - "message": "Excluir" + "message": "Apagar" }, "favorite": { - "message": "Favorito" + "message": "Favoritar" }, "unfavorite": { "message": "Desfavoritar" @@ -826,47 +826,47 @@ "message": "Editar" }, "searchCollection": { - "message": "Pesquisar coleção" + "message": "Buscar no conjunto" }, "searchFolder": { - "message": "Pesquisar pasta" + "message": "Buscar na pasta" }, "searchFavorites": { - "message": "Pesquisar favoritos" + "message": "Buscar nos favoritos" }, "searchLogin": { - "message": "Pesquisar credenciais", + "message": "Buscar nas credenciais", "description": "Search Login type" }, "searchCard": { - "message": "Buscar cartões", + "message": "Buscar nos cartões", "description": "Search Card type" }, "searchIdentity": { - "message": "Pesquisar identidades", + "message": "Buscar nas identidades", "description": "Search Identity type" }, "searchSecureNote": { - "message": "Pesquisar anotações seguras", + "message": "Buscar nas anotações seguras", "description": "Search Secure Note type" }, "searchVault": { - "message": "Pesquisar cofre" + "message": "Buscar no cofre" }, "searchMyVault": { - "message": "Pesquisar meu cofre" + "message": "Buscar no meu cofre" }, "searchOrganization": { - "message": "Pesquisar organização" + "message": "Buscar na organização" }, "searchMembers": { - "message": "Pesquisar membros" + "message": "Buscar nos membros" }, "searchGroups": { - "message": "Buscar grupos" + "message": "Buscar nos grupos" }, "resetSearch": { - "message": "Apagar pesquisa" + "message": "Apagar busca" }, "allItems": { "message": "Todos os itens" @@ -875,7 +875,7 @@ "message": "Favoritos" }, "taskSummary": { - "message": "Task summary" + "message": "Resumo de tarefas" }, "types": { "message": "Tipos" @@ -914,7 +914,7 @@ "message": "Pastas" }, "collections": { - "message": "Coleções" + "message": "Conjuntos" }, "firstName": { "message": "Primeiro nome" @@ -971,7 +971,7 @@ "message": "Editar item" }, "viewItem": { - "message": "Visualizar item" + "message": "Ver item" }, "newItemHeaderLogin": { "message": "Nova credencial", @@ -1030,23 +1030,23 @@ "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "Visualizar credencial", + "message": "Ver credencial", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "Visualizar cartão", + "message": "Ver cartão", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { - "message": "Visualizar identidade", + "message": "Ver identidade", "description": "Header for view identity item type" }, "viewItemHeaderNote": { - "message": "Visualizar anotação", + "message": "Ver anotação", "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "Visualizar chave SSH", + "message": "Ver chave SSH", "description": "Header for view SSH key item type" }, "new": { @@ -1063,7 +1063,7 @@ "message": "Nome do item" }, "ex": { - "message": "ex.", + "message": "p. ex.", "description": "Short abbreviation for 'example'." }, "other": { @@ -1215,22 +1215,22 @@ "message": "Mover selecionados" }, "selectAll": { - "message": "Marcar tudo" + "message": "Selecionar tudo" }, "unselectAll": { - "message": "Desmarcar tudo" + "message": "Deselecionar tudo" }, "launch": { "message": "Abrir" }, "newAttachment": { - "message": "Adicionar Novo Anexo" + "message": "Adicionar novo anexo" }, "deletedAttachment": { - "message": "Anexo excluído" + "message": "Anexo apagado" }, "deleteAttachmentConfirmation": { - "message": "Tem certeza que deseja excluir esse anexo?" + "message": "Tem certeza que quer apagar este anexo?" }, "attachmentSaved": { "message": "Anexo salvo" @@ -1291,7 +1291,7 @@ "message": "Apagar anexo" }, "deleteItemConfirmation": { - "message": "Você tem certeza que deseja enviar este item para a lixeira?" + "message": "Tem certeza que quer enviar este item para a lixeira?" }, "deletedItem": { "message": "Item enviado para a lixeira" @@ -1303,7 +1303,7 @@ "message": "Itens movidos" }, "overwritePasswordConfirmation": { - "message": "Você tem certeza que deseja substituir a senha atual?" + "message": "Tem certeza que quer substituir a senha atual?" }, "editedFolder": { "message": "Pasta salva" @@ -1312,7 +1312,7 @@ "message": "Pasta adicionada" }, "deleteFolderConfirmation": { - "message": "Você tem certeza que deseja excluir esta pasta?" + "message": "Você tem certeza que deseja apagar esta pasta?" }, "deletedFolder": { "message": "Pasta apagada" @@ -1330,7 +1330,7 @@ "message": "Acessando" }, "loggedOut": { - "message": "Sessão encerrada" + "message": "Desconectado" }, "loggedOutDesc": { "message": "Você foi desconectado de sua conta." @@ -1342,7 +1342,7 @@ "message": "Reiniciar cadastro" }, "expiredLink": { - "message": "Link expirado" + "message": "Link vencido" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Reinicie o cadastro ou tente conectar-se." @@ -1351,10 +1351,10 @@ "message": "Você pode já ter uma conta" }, "logOutConfirmation": { - "message": "Você tem certeza que deseja sair?" + "message": "Tem certeza que quer se desconectar?" }, "logOut": { - "message": "Encerrar sessão" + "message": "Desconectar" }, "ok": { "message": "Ok" @@ -1369,19 +1369,19 @@ "message": "Localização" }, "loginOrCreateNewAccount": { - "message": "Inicie a sessão ou crie uma nova conta para acessar seu cofre seguro." + "message": "Conecte-se ou crie uma conta para acessar seu cofre seguro." }, "loginWithDevice": { "message": "Conectar-se com dispositivo" }, "loginWithDeviceEnabledNote": { - "message": "O login com dispositivo deve estar ativado nas configurações do aplicativo do Bitwarden. Precisa de outra opção?" + "message": "A autenticação com dispositivo deve ser configurada nas configurações do aplicativo móvel do Bitwarden. Precisa de outra opção?" }, "needAnotherOptionV1": { "message": "Precisa de outra opção?" }, "loginWithMasterPassword": { - "message": "Entrar com senha principal" + "message": "Conectar-se com senha principal" }, "readingPasskeyLoading": { "message": "Lendo a chave de acesso..." @@ -1393,16 +1393,16 @@ "message": "A autenticação da chave de acesso falhou. Tente novamente." }, "useADifferentLogInMethod": { - "message": "Usar um método de entrada diferente" + "message": "Usar um método de autenticação diferente" }, "logInWithPasskey": { - "message": "Entrar com chave de acesso" + "message": "Conectar-se com chave de acesso" }, "useSingleSignOn": { "message": "Usar autenticação única" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "A sua organização requer o uso da autenticação única." }, "welcomeBack": { "message": "Boas-vindas de volta" @@ -1411,16 +1411,16 @@ "message": "Chave de acesso inválida. Tente novamente." }, "twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn": { - "message": "A 2FA não é suportada para chaves de acesso. Atualize o app para entrar." + "message": "A 2FA não é suportada para chaves de acesso. Atualize o aplicativo para se conectar." }, "loginWithPasskeyInfo": { - "message": "Use uma chave de acesso gerada que te conectará automaticamente sem uma senha. Biometrias como reconhecimento facial ou impressão digital, ou outro método de segurança FIDO2 verificarão sua identidade." + "message": "Use uma chave de acesso gerada que te conectará automaticamente sem uma senha. A biometria, como a reconhecimento facial ou impressão digital, ou outro método de segurança do FIDO2 verificarão sua identidade." }, "newPasskey": { "message": "Nova chave de acesso" }, "learnMoreAboutPasswordless": { - "message": "Saiba mais sobre acesso sem senha" + "message": "Saiba mais sobre o passwordless" }, "creatingPasskeyLoading": { "message": "Criando chave de acesso..." @@ -1459,10 +1459,10 @@ "message": "Usada para criptografia" }, "loginWithPasskeyEnabled": { - "message": "Entrar com chave de acesso ativado" + "message": "Autenticação com chave de acesso ativada" }, "passkeySaved": { - "message": "$NAME$ salvo", + "message": "$NAME$ salva", "placeholders": { "name": { "content": "$1", @@ -1477,7 +1477,7 @@ "message": "Remover chave de acesso" }, "removePasskeyInfo": { - "message": "Se todas as chaves de acesso forem removidas, não será mais possível entrar em novos dispositivos sem sua senha principal." + "message": "Se todas as chaves de acesso forem removidas, não será mais possível se conectar em novos dispositivos sem sua senha principal." }, "passkeyLimitReachedInfo": { "message": "Limite de chaves de acesso atingido. Remova uma chave de acesso para adicionar outra." @@ -1492,10 +1492,10 @@ "message": "Novo no Bitwarden?" }, "setAStrongPassword": { - "message": "Defina uma senha forte" + "message": "Configure uma senha forte" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Termine de criar a sua conta definindo uma senha" + "message": "Termine de criar a sua conta configurando uma senha" }, "newAroundHere": { "message": "Novo por aqui?" @@ -1504,25 +1504,25 @@ "message": "Iniciar período de teste" }, "logIn": { - "message": "Entrar" + "message": "Conectar-se" }, "logInToBitwarden": { - "message": "Entrar no Bitwarden" + "message": "Conecte-se ao Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Digite o código enviado por e-mail" + "message": "Digite o código enviado ao seu e-mail" }, "enterTheCodeFromYourAuthenticatorApp": { "message": "Digite o código do seu aplicativo autenticador" }, "pressYourYubiKeyToAuthenticate": { - "message": "Pressione sua YubiKey para autenticar" + "message": "Pressione sua YubiKey para autenticar-se" }, "authenticationTimeout": { - "message": "Tempo de autenticação esgotado" + "message": "Tempo limite da autenticação atingido" }, "authenticationSessionTimedOut": { - "message": "A sessão de autenticação expirou. Reinicie o processo de entrada." + "message": "A sessão de autenticação expirou. Reinicie o processo de autenticação." }, "verifyYourIdentity": { "message": "Verifique sua identidade" @@ -1537,10 +1537,10 @@ "message": "O que é um dispositivo?" }, "aDeviceIs": { - "message": "Um dispositivo é uma instalação única do aplicativo Bitwarden em qual você se conectou. Reinstalar, limpar os dados do aplicativo ou limpar seus cookies pode resultar em um dispositivo aparecendo várias vezes." + "message": "Um dispositivo é uma instalação única do aplicativo do Bitwarden em qual você se conectou. Reinstalar, limpar os dados do aplicativo ou limpar seus cookies pode resultar em um dispositivo aparecendo várias vezes." }, "logInInitiated": { - "message": "Login iniciado" + "message": "Autenticação iniciada" }, "logInRequestSent": { "message": "Solicitação enviada" @@ -1549,13 +1549,13 @@ "message": "Enviar" }, "emailAddressDesc": { - "message": "Você usará o seu endereço de e-mail para a iniciar sessão." + "message": "Você usará o seu endereço de e-mail para se conectar." }, "yourName": { "message": "Seu nome" }, "yourNameDesc": { - "message": "Como devemos chamá-lo?" + "message": "Como devemos te chamar?" }, "masterPass": { "message": "Senha principal" @@ -1570,7 +1570,7 @@ "message": "Uma dica de senha principal pode ajudá-lo(a) a lembrar a senha caso você esqueça." }, "reTypeMasterPass": { - "message": "Digite novamente a senha principal" + "message": "Redigitar senha principal" }, "masterPassHint": { "message": "Dica da senha principal (opcional)" @@ -1582,7 +1582,7 @@ "message": "Dica da senha principal" }, "masterPassHintText": { - "message": "Se você esquecer sua senha, a dica da senha pode ser enviada ao seu e-mail. $CURRENT$/$MAXIMUM$ caracteres máximos.", + "message": "Se você esquecer sua senha, a dica da senha pode ser enviada ao seu e-mail. $CURRENT$ do máximo de $MAXIMUM$ caracteres.", "placeholders": { "current": { "content": "$1", @@ -1604,13 +1604,13 @@ "message": "Solicitar dica" }, "requestPasswordHint": { - "message": "Dica da senha principal" + "message": "Solicitar dica da senha" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Digite o endereço de e-mail da sua conta e sua dica da senha será enviada para você" + "message": "Digite o endereço de e-mail da sua conta e dica da sua senha será enviada para você" }, "getMasterPasswordHint": { - "message": "Obter dica da senha principal" + "message": "Receber dica da senha principal" }, "emailRequired": { "message": "O endereço de e-mail é obrigatório." @@ -1622,7 +1622,7 @@ "message": "A senha principal é obrigatória." }, "confirmMasterPasswordRequired": { - "message": "A senha principal é obrigatória." + "message": "É necessário redigitar a senha principal." }, "masterPasswordMinlength": { "message": "A senha principal deve ter pelo menos $VALUE$ caracteres.", @@ -1638,13 +1638,13 @@ "message": "A confirmação da senha principal não corresponde." }, "newAccountCreated": { - "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." + "message": "A sua conta nova foi criada! Agora você pode se conectar." }, "newAccountCreated2": { - "message": "Sua nova conta foi criada!" + "message": "Sua conta nova foi criada!" }, "youHaveBeenLoggedIn": { - "message": "Você está conectado!" + "message": "Você foi conectado!" }, "trialAccountCreated": { "message": "Conta criada com sucesso." @@ -1674,7 +1674,7 @@ "message": "Desbloquear" }, "loggedInAsEmailOn": { - "message": "Entrou como $EMAIL$ em $HOSTNAME$.", + "message": "Conectado como $EMAIL$ em $HOSTNAME$.", "placeholders": { "email": { "content": "$1", @@ -1690,7 +1690,7 @@ "message": "Senha principal inválida" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Senha principal inválida. Confirme se seu e-mail está correto e sua conta foi criada em $HOST$.", + "message": "Senha principal inválida. Confirme que seu e-mail está correto e que sua conta foi criada em $HOST$.", "placeholders": { "host": { "content": "$1", @@ -1726,19 +1726,19 @@ "message": "Adicione itens usados frequentemente aos favoritos para acesso rápido." }, "noSearchResults": { - "message": "Nenhum resultado de pesquisa" + "message": "Nenhum resultado de busca" }, "clearFiltersOrTryAnother": { - "message": "Limpar filtros ou tentar outro termo de pesquisa" + "message": "Limpe os filtros ou tente outro termo de busca" }, "noPermissionToViewAllCollectionItems": { - "message": "Você não tem permissão para visualizar todos os itens nesta coleção." + "message": "Você não tem permissão para ver todos os itens neste conjunto." }, "youDoNotHavePermissions": { - "message": "Você não pode acessar esta coleção" + "message": "Você não tem permissões para esse conjunto" }, "noCollectionsInList": { - "message": "Não há coleções para listar." + "message": "Não há conjuntos para listar." }, "noGroupsInList": { "message": "Não há grupos para listar." @@ -1756,7 +1756,7 @@ "message": "Nova organização" }, "noOrganizationsList": { - "message": "Você não pertence a nenhuma organização. Organizações permitem-lhe compartilhar itens em segurança com outros usuários." + "message": "Você não pertence à nenhuma organização. Organizações te permitem compartilhar itens em segurança com outros usuários." }, "notificationSentDevice": { "message": "Uma notificação foi enviada para seu dispositivo." @@ -1817,19 +1817,19 @@ "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Usar código de recuperação" + "message": "Usar seu código de recuperação" }, "insertU2f": { - "message": "Insira a sua chave de segurança na porta USB do seu computador. Se ele tiver um botão, toque nele." + "message": "Insira a sua chave de segurança na porta USB do seu computador. Se ela tiver um botão, toque nele." }, "loginUnavailable": { - "message": "Entrada indisponível" + "message": "Autenticação indisponível" }, "noTwoStepProviders": { "message": "Esta conta tem a autenticação em duas etapas ativada. No entanto, nenhum dos provedores de autenticação em duas etapas configurados são suportados por este navegador web." }, "noTwoStepProviders2": { - "message": "Por favor utilize um navegador web suportado (tal como o Chrome) e/ou inclua provedores adicionais que são melhor suportados entre navegadores web (tal como um aplicativo de autenticação)." + "message": "Utilize um navegador web suportado (tal como o Chrome) e/ou inclua provedores adicionais que são melhor suportados entre navegadores web (tal como um aplicativo de autenticação)." }, "twoStepOptions": { "message": "Opções de autenticação em duas etapas" @@ -1844,7 +1844,7 @@ "message": "Chave de recuperação inválida" }, "authenticatorAppTitle": { - "message": "Aplicativo de autenticação" + "message": "Aplicativo autenticador" }, "authenticatorAppDescV2": { "message": "Digite um código gerado por um aplicativo autenticador como o Bitwarden Authenticator.", @@ -1861,7 +1861,7 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "Verifique com o Duo Security utilizando o aplicativo Duo Mobile, SMS, chamada telefônica, ou chave de segurança U2F.", + "message": "Verifique com o Duo Security para sua organização 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." }, "u2fDesc": { @@ -1898,10 +1898,10 @@ "message": "Organizações" }, "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." + "message": "Escolha uma organização para a qual deseja mover este item. Mover 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." }, "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." + "message": "Edite os conjuntos com os quais este item está sendo compartilhado. Somente usuários da organização com acesso a estes conjuntos poderão ver esse item." }, "deleteSelectedItemsDesc": { "message": "$COUNT$ item(ns) serão enviados para a lixeira.", @@ -1913,7 +1913,7 @@ } }, "deleteSelectedCollectionsDesc": { - "message": "$COUNT$ coleção(ões) serão apagadas para sempre.", + "message": "$COUNT$ conjunto(s) serão apagadas para sempre.", "placeholders": { "count": { "content": "$1", @@ -1925,7 +1925,7 @@ "message": "Tem certeza que deseja continuar?" }, "moveSelectedItemsDesc": { - "message": "Escolha a pasta para a qual você deseja mover os $COUNT$ itens selecionados.", + "message": "Escolha a pasta para a qual você deseja adicionar o(s) $COUNT$ item(ns) selecionado(s).", "placeholders": { "count": { "content": "$1", @@ -1943,13 +1943,13 @@ "message": "Copiar UUID" }, "errorRefreshingAccessToken": { - "message": "Erro ao acessar token de recarregamento" + "message": "Erro de recarregamento do token de acesso" }, "errorRefreshingAccessTokenDesc": { - "message": "Nenhum token de atualização ou chave de API foi encontrado. Tente sair e entrar novamente." + "message": "Nenhum token de atualização ou chave de API foi encontrado. Tente se desconectar e se conectar novamente." }, "warning": { - "message": "Aviso" + "message": "Atenção" }, "confirmVaultExport": { "message": "Confirmar exportação do cofre" @@ -1958,7 +1958,7 @@ "message": "Confirmar exportação de segredos" }, "exportWarningDesc": { - "message": "Esta exportação contém os dados do seu cofre em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Exclua o arquivo imediatamente após terminar de usá-lo." + "message": "Esta exportação contém os dados do seu cofre em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague o arquivo imediatamente após terminar de usá-lo." }, "exportSecretsWarningDesc": { "message": "Esta exportação contém seus dados de segredos em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague-o imediatamente após terminar de usá-lo." @@ -1991,7 +1991,7 @@ "message": "Esta senha será usada para exportar e importar este arquivo" }, "confirmMasterPassword": { - "message": "Confirme a senha principal" + "message": "Confirmar senha principal" }, "confirmFormat": { "message": "Confirmar formato" @@ -2006,7 +2006,7 @@ "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha principal da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." }, "passwordProtectedOptionDescription": { - "message": "Defina uma senha para criptografar a exportação e importá-la para qualquer conta do Bitwarden usando a senha para descriptografar." + "message": "Configure uma senha de arquivo para criptografar a exportação e importá-la para qualquer conta do Bitwarden usando a senha para descriptografá-la." }, "exportTypeHeading": { "message": "Tipo da exportação" @@ -2015,7 +2015,7 @@ "message": "Restrita à conta" }, "passwordProtected": { - "message": "Protegido por senha" + "message": "Protegida por senha" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Senha do arquivo\" e \"Confirmar senha do arquivo\" não correspondem." @@ -2030,16 +2030,16 @@ "message": "Dados do cofre exportados" }, "passwordGenerator": { - "message": "Gerador de senha" + "message": "Gerador de senhas" }, "minComplexityScore": { - "message": "Pontuação de complexidade mínima" + "message": "Pontuação mínima de complexidade" }, "minNumbers": { "message": "Mínimo de números" }, "minSpecial": { - "message": "Mínimo de especiais", + "message": "Mínimo de caracteres especiais", "description": "Minimum special characters" }, "ambiguous": { @@ -2078,14 +2078,14 @@ "message": "Separador das palavras" }, "capitalize": { - "message": "Iniciais em Maiúsculas", + "message": "Iniciais maiúsculas", "description": "Make the first letter of a word uppercase." }, "includeNumber": { "message": "Incluir número" }, "generatorPolicyInEffect": { - "message": "Os requisitos de política empresarial foram aplicados às suas opções de gerador.", + "message": "Os requisitos da política empresarial foram aplicados às opções do seu gerador.", "description": "Indicates that a policy limits the credential generator screen." }, "passwordHistory": { @@ -2101,13 +2101,13 @@ "message": "Se continuar, todos os itens serão apagados para sempre do histórico do gerador. Tem certeza que deseja continuar?" }, "noPasswordsInList": { - "message": "Não existem senhas para listar." + "message": "Não há senhas para listar." }, "clearHistory": { "message": "Limpar histórico" }, "nothingToShow": { - "message": "Nada a mostrar" + "message": "Nada para mostrar" }, "nothingGeneratedRecently": { "message": "Você não gerou nada recentemente" @@ -2123,7 +2123,7 @@ "message": "Alterar e-mail" }, "changeEmailTwoFactorWarning": { - "message": "Prosseguir irá alterar o endereço de e-mail da sua conta. Isso não mudará o endereço de e-mail usado para autenticação de dois fatores. Você pode alterar esse endereço de e-mail nas configurações de autenticação de duas etapas." + "message": "Prosseguir irá alterar o endereço de e-mail da sua conta. Isso não mudará o endereço de e-mail usado para autenticação de duas etapas. Você pode alterar esse endereço de e-mail nas configurações de autenticação de duas etapas." }, "newEmail": { "message": "Novo e-mail" @@ -2144,13 +2144,13 @@ "message": "O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. As sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "changePasswordWarning": { - "message": "Ao alterar sua senha, você precisará entrar com a sua senha nova. Sessões ativas em outros dispositivos serão desconectados dentro de uma hora." + "message": "Ao alterar sua senha, você precisará se conectar com a sua senha nova. Sessões ativas em outros dispositivos serão desconectadas dentro de uma hora." }, "emailChanged": { "message": "E-mail salvo" }, "logBackIn": { - "message": "Por favor, reinicie a sessão." + "message": "Conecte-se novamente." }, "currentSession": { "message": "Sessão atual" @@ -2159,7 +2159,7 @@ "message": "Solicitação pendente" }, "logBackInOthersToo": { - "message": "Por favor, reinicie a sessão. Se estiver usando outros aplicativos do Bitwarden, encerre a sessão e reinicie também." + "message": "Conecte-se novamente. Se estiver usando outros aplicativos do Bitwarden, desconecte-se e conecte-se neles também." }, "changeMasterPassword": { "message": "Alterar senha principal" @@ -2183,7 +2183,7 @@ "message": "Iterações da KDF" }, "kdfIterationsDesc": { - "message": "As iterações KDF mais altas podem ajudar a proteger a sua senha principal de ser descoberta pela força bruta de um invasor. Recomendamos um valor de $VALUE$ ou mais.", + "message": "Ter iterações de KDF mais altas podem ajudar a proteger a sua senha principal de ser descoberta pela força bruta de um invasor. Recomendamos um valor de $VALUE$ ou mais.", "placeholders": { "value": { "content": "$1", @@ -2192,7 +2192,7 @@ } }, "kdfIterationsWarning": { - "message": "Usar iterações de KDF muito altas pode resultar em baixo desempenho ao entrar (e desbloquear) no Bitwarden em dispositivos com CPUs mais lentas. Recomendamos que você aumente o valor em incrementos de $INCREMENT$ e, em seguida, teste em todos os seus dispositivos.", + "message": "Configurar as suas iterações da KDF muito altas pode resultar em baixo desempenho ao se conectar no (e desbloquear o) Bitwarden em dispositivos mais lentos ou antigos. Recomendamos que você aumente o valor em incrementos de $INCREMENT$ e, em seguida, teste em todos os seus dispositivos.", "placeholders": { "increment": { "content": "$1", @@ -2205,13 +2205,13 @@ "description": "Memory refers to computer memory (RAM). MB is short for megabytes." }, "argon2Warning": { - "message": "Usar valores altos para as iterações, memória, e paralelismo da KDF pode resultar em baixo desempenho ao entrar (e desbloquear) o Bitwarden em dispositivos mais lentos ou antigos. Recomendamos alterar estes valores individualmente em incrementos pequenos, e então, testar em todos os seus dispositivos." + "message": "Configurar valores altos para as iterações, memória, e paralelismo da KDF pode resultar em baixo desempenho ao se conectar (e desbloquear) o Bitwarden em dispositivos mais lentos ou antigos. Recomendamos alterar estes valores individualmente em incrementos pequenos, e então, testar em todos os seus dispositivos." }, "kdfParallelism": { "message": "Paralelismo da KDF" }, "argon2Desc": { - "message": "Mais iterações KDF, memória e paralelismo podem ajudar a proteger sua senha principal de ser descoberta por força bruta por um invasor." + "message": "Mais iterações, memória, e paralelismo da KDF podem ajudar a proteger sua senha principal de ser descoberta por força bruta por um invasor." }, "encKeySettingsChanged": { "message": "As configurações da chave de criptografia foram salvas" @@ -2223,19 +2223,19 @@ "message": "Desautorizar sessões" }, "deauthorizeSessionsDesc": { - "message": "Preocupado por ter iniciado a sessão em outro dispositivo? Prossiga abaixo para desautorizar todos os computadores e dispositivos que tenha usado. Esta medida de segurança é recomendada se usou um computador público ou guardou acidentalmente a sua senha num dispositivo que não lhe pertence. Esta ação também apagará todas as sessões anteriores registadas iniciadas com autenticação em duas etapas." + "message": "Preocupado que sua conta esteja conectada em outro dispositivo? Prossiga abaixo para desautorizar todos os computadores e dispositivos que tenha usado. Esta medida de segurança é recomendada se usou um computador público ou salvou acidentalmente a sua senha num dispositivo que não lhe pertence. Isso também limpará todas as sessões lembradas de autenticação com duas etapas." }, "deauthorizeSessionsWarning": { "message": "O processo também desconectará você da sua sessão atual, exigindo que você se conecte novamente. Você também será solicitado a efetuar a autenticação em duas etapas novamente, se estiver ativada. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "newDeviceLoginProtection": { - "message": "Entrada de dispositivos novos" + "message": "Autenticação de dispositivos novos" }, "turnOffNewDeviceLoginProtection": { - "message": "Desativar proteção de entrada de dispositivos novos" + "message": "Desativar proteção de autenticação de dispositivos novos" }, "turnOnNewDeviceLoginProtection": { - "message": "Ativar proteção de entrada de dispositivos novos" + "message": "Ativar proteção de autenticação de dispositivos novos" }, "turnOffNewDeviceLoginProtectionModalDesc": { "message": "Continue abaixo para desativar os e-mails de verificação que o Bitwarden enviará quando se conectar a partir de um novo dispositivo." @@ -2247,7 +2247,7 @@ "message": "Com a proteção de autenticação de dispositivos novos desativada, qualquer um com a sua senha principal pode acessar sua conta de qualquer dispositivo. Para proteger sua conta sem e-mails de verificação, configure a autenticação em duas etapas." }, "accountNewDeviceLoginProtectionSaved": { - "message": "Alterações na proteção na entrada de dispositivos novos salvas" + "message": "Alterações na proteção na autenticação de dispositivos novos salvas" }, "sessionsDeauthorized": { "message": "Todas as sessões desautorizadas" @@ -2265,19 +2265,19 @@ "message": "Limpar cofre" }, "purgedOrganizationVault": { - "message": "Cofre da organização limpado." + "message": "Cofre da organização limpo." }, "vaultAccessedByProvider": { "message": "Cofre acessado pelo provedor." }, "purgeVaultDesc": { - "message": "Continue abaixo para excluir todos os itens e pastas do seu cofre. Itens que pertencem a uma organização com a qual você compartilha não serão excluídos." + "message": "Continue abaixo para apagar todos os itens e pastas no seu cofre. Itens que pertencem a uma organização com a qual você compartilha não serão apagados." }, "purgeOrgVaultDesc": { - "message": "Continue abaixo para excluir todos os itens no cofre da organização." + "message": "Continue abaixo para apagar todos os itens no cofre da organização." }, "purgeVaultWarning": { - "message": "Limpando o seu cofre permanentemente. Não pode ser desfeito." + "message": "Limpar seu cofre é para sempre. Não pode ser desfeito." }, "vaultPurged": { "message": "O cofre foi limpo." @@ -2289,7 +2289,7 @@ "message": "Prossiga abaixo para apagar sua conta e todos os dados do cofre." }, "deleteAccountWarning": { - "message": "A exclusão de sua conta é permanente. Não pode ser desfeito." + "message": "O apagamento da sua conta é permanente. Não pode ser desfeito." }, "accountDeleted": { "message": "Conta apagada" @@ -2298,7 +2298,7 @@ "message": "A sua conta foi fechada e todos os dados associados foram apagados." }, "deleteOrganizationWarning": { - "message": "O apagamento da organização é permanente. Não pode ser desfeito." + "message": "O apagamento da sua organização é permanente. Não pode ser desfeito." }, "myAccount": { "message": "Minha conta" @@ -2310,7 +2310,7 @@ "message": "Importar dados" }, "onboardingImportDataDetailsPartOne": { - "message": "Se você não tem dados para importar, você pode criar um ", + "message": "Se você não tem dados para importar, você pode criar um(a) ", "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": { @@ -2333,7 +2333,7 @@ "message": "Erro de importação" }, "importErrorDesc": { - "message": "Houve um problema com os dados que você tentou importar. Resolva os erros listados abaixo em seu arquivo de origem e tente novamente." + "message": "Houve um problema com os dados que você tentou importar. Resolva os erros listados abaixo em seu arquivo-fonte e tente novamente." }, "importSuccess": { "message": "Dados importados com sucesso" @@ -2360,7 +2360,7 @@ } }, "importFormatError": { - "message": "Os dados não estão formatados corretamente. Por favor, verifique o seu arquivo de importação e tente novamente." + "message": "Os dados não estão formatados corretamente. Verifique o seu arquivo de importação e tente novamente." }, "importNothingError": { "message": "Nada foi importado." @@ -2378,13 +2378,13 @@ "message": "Selecione uma pasta" }, "selectImportCollection": { - "message": "Selecione uma coleção" + "message": "Selecione um conjunto" }, "importTargetHintCollection": { - "message": "Selecione esta opção caso queira importar os conteúdos importados do arquivo movidos para uma coleção" + "message": "Selecione esta opção caso queira importar os conteúdos do arquivo para um conjunto" }, "importTargetHintFolder": { - "message": "Selecione esta opção caso queira importar os conteúdos importados do arquivo movidos para uma pasta" + "message": "Selecione esta opção caso queira importar os conteúdos do arquivo para uma pasta" }, "importUnassignedItemsError": { "message": "Arquivo contém itens não atribuídos." @@ -2396,7 +2396,7 @@ "message": "Selecione o arquivo de importação" }, "chooseFile": { - "message": "Escolher arquivo" + "message": "Selecionar arquivo" }, "noFileChosen": { "message": "Nenhum arquivo escolhido" @@ -2405,7 +2405,7 @@ "message": "ou copie/cole o conteúdo do arquivo de importação" }, "instructionsFor": { - "message": "$NAME$ instruções", + "message": "Instruções para $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -2424,7 +2424,7 @@ "message": "Personalize sua experiência no cofre web." }, "preferencesUpdated": { - "message": "Preferências atualizadas" + "message": "Preferências salvas" }, "language": { "message": "Idioma" @@ -2463,10 +2463,10 @@ "message": "Novo domínio personalizado" }, "newCustomDomainDesc": { - "message": "Digite uma lista de domínios separados por vírgulas. Apenas domínios \"base\" são permitidos. Não insira subdomínios. Por exemplo, digite \"google.com.br\" em vez de \"www.google.com.br\". Você também pode digitar \"androidapp://package.name\" para associar um aplicativo Android a outros domínios de site." + "message": "Digite uma lista de domínios separados por vírgulas. Apenas domínios \"base\" são permitidos. Não digite subdomínios. Por exemplo, digite \"google.com\" em vez de \"www.google.com\". Você também pode digitar \"androidapp://package.name\" para associar um aplicativo Android a outros domínios." }, "customDomainX": { - "message": "Domínio Personalizado $INDEX$", + "message": "Domínio personalizado $INDEX$", "placeholders": { "index": { "content": "$1", @@ -2481,10 +2481,10 @@ "message": "Autenticação em duas etapas" }, "twoStepLoginEnforcement": { - "message": "Obrigatoriedade da autenticação em duas etapas" + "message": "Aplicação da autenticação em duas etapas" }, "twoStepLoginDesc": { - "message": "Proteja a sua conta exigindo uma etapa adicional ao iniciar sessão." + "message": "Proteja a sua conta exigindo uma etapa adicional ao se conectar." }, "twoStepLoginTeamsDesc": { "message": "Ative a autenticação em duas etapas para sua organização." @@ -2500,7 +2500,7 @@ "message": "Para aplicar a autenticação em duas etapas pelo Duo, use as opções abaixo." }, "twoStepLoginOrganizationSsoDesc": { - "message": "Se você tem SSO configurado ou planeja ter, a autenticação em duas etapas já pode ser aplicada por meio do seu provedor de identidade." + "message": "Se você tem o SSO configurado ou planeja ter, a autenticação em duas etapas já pode ser aplicada por meio do seu provedor de identidade." }, "twoStepLoginRecoveryWarning": { "message": "A configuração de autenticação 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." @@ -2509,16 +2509,16 @@ "message": "Remover tipo de item de cartão" }, "restrictedItemTypePolicyDesc": { - "message": "Não permitir que membros criem tipos de itens de cartões. Cartões existentes serão apagados automaticamente." + "message": "Não permita que membros criem cartões. Cartões existentes serão apagados automaticamente." }, "restrictCardTypeImport": { - "message": "Não é possível importar tipos de item de cartão" + "message": "Não é possível importar itens do tipo cartão" }, "restrictCardTypeImportDesc": { "message": "Uma política configurada por uma ou mais organizações impede que você importe cartões em seus cofres." }, "yourSingleUseRecoveryCode": { - "message": "Seu código de recuperação de uso único pode ser usado para desativar a autenticação em duas etapas no caso de você perder acesso ao seu provedor de login em duas etapas. O Bitwarden recomenda que você anote o código de recuperação e o mantenha em um lugar seguro." + "message": "Seu código de recuperação de uso único pode ser usado para desativar a autenticação em duas etapas no caso de você perder acesso ao seu provedor de autenticação em duas etapas. O Bitwarden recomenda que você anote o código de recuperação e o mantenha em um lugar seguro." }, "viewRecoveryCode": { "message": "Ver código de recuperação" @@ -2541,13 +2541,13 @@ "description": "Premium membership" }, "premiumMembership": { - "message": "Assinatura Premium" + "message": "Plano Premium" }, "premiumRequired": { - "message": "Requer assinatura Premium" + "message": "Requer Premium" }, "premiumRequiredDesc": { - "message": "É necessário ter uma conta premium para usar esse recurso." + "message": "Um plano Premium é necessário para usar esse recurso." }, "youHavePremiumAccess": { "message": "Você tem acesso Premium" @@ -2559,10 +2559,10 @@ "message": "Gerenciar" }, "manageCollection": { - "message": "Gerenciar coleção" + "message": "Gerenciar conjunto" }, "viewItems": { - "message": "Visualizar itens" + "message": "Ver itens" }, "viewItemsHidePass": { "message": "Ver itens, senhas ocultas" @@ -2574,10 +2574,10 @@ "message": "Editar itens, senhas ocultas" }, "disable": { - "message": "Desabilitar" + "message": "Desativar" }, "orgUserDetailsNotFound": { - "message": "Detalhes de membro não encontrados." + "message": "Detalhes do membro não encontrados." }, "revokeAccess": { "message": "Revogar acesso" @@ -2589,7 +2589,7 @@ "message": "Este provedor de autenticação em duas etapas está ativo em sua conta." }, "twoStepLoginAuthDesc": { - "message": "Insira a sua senha principal para modificar as configurações de login em duas etapas." + "message": "Digite a sua senha principal para modificar as configurações de autenticação em duas etapas." }, "twoStepAuthenticatorInstructionPrefix": { "message": "Baixe um app autenticador como o" @@ -2604,7 +2604,7 @@ "message": "." }, "continueToExternalUrlTitle": { - "message": "Continuar para $URL$?", + "message": "Continuar em $URL$?", "placeholders": { "url": { "content": "$1", @@ -2616,13 +2616,13 @@ "message": "Você está saindo do Bitwarden e abrindo um site externo em uma nova janela." }, "twoStepContinueToBitwardenUrlTitle": { - "message": "Continuar para bitwarden.com?" + "message": "Continuar em bitwarden.com?" }, "twoStepContinueToBitwardenUrlDesc": { "message": "O Bitwarden Authenticator permite que você armazene as chaves de autenticador e gere códigos TOTP para fluxos de verificação de 2 etapas. Saiba mais no site bitwarden.com." }, "twoStepAuthenticatorScanCodeV2": { - "message": "Leia o código QR abaixo com o seu app de autenticação ou insira a chave." + "message": "Leia o código QR abaixo com o seu app de autenticação ou cole a chave." }, "twoStepAuthenticatorQRCanvasError": { "message": "Não foi possível carregar o código QR. Tente novamente ou use a chave abaixo." @@ -2643,16 +2643,16 @@ "message": "Provedor de autenticação em duas etapas desativado." }, "twoFactorYubikeyAdd": { - "message": "Adicionar um novo Yubikey à sua conta" + "message": "Adicione uma nova YubiKey à sua conta" }, "twoFactorYubikeyPlugIn": { - "message": "Conecte o YubiKey na porta USB do seu computador." + "message": "Conecte a YubiKey na porta USB do seu computador." }, "twoFactorYubikeySelectKey": { - "message": "Selecione o YubiKey no primeiro campo vazio abaixo." + "message": "Selecione o primeiro campo vazio de entrada da YubiKey." }, "twoFactorYubikeyTouchButton": { - "message": "Toque o botão do Yubikey." + "message": "Toque o botão da YubiKey." }, "twoFactorYubikeySaveForm": { "message": "Salve o formulário." @@ -2661,10 +2661,10 @@ "message": "Devido as limitações da plataforma, YubiKeys não podem ser usadas em todos os aplicativos do Bitwarden. Você deve ativar outro provedor de autenticação em duas etapas, para que você possa acessar a sua conta quando YubiKeys não puderem ser usadas. Plataformas suportadas:" }, "twoFactorYubikeySupportUsb": { - "message": "Cofre Web, aplicativo de desktop, CLI e todas as extensões do navegador em um dispositivo com uma porta USB que pode aceitar o seu YubiKey." + "message": "Cofre web, aplicativo de computador, CLI, e todas as extensões de navegador em um dispositivo com uma porta USB que pode aceitar a sua YubiKey." }, "twoFactorYubikeySupportMobile": { - "message": "Aplicativos móveis em um dispositivo com capacidades de NFC ou uma porta USB que pode aceitar o seu YubiKey." + "message": "Aplicativos móveis em um dispositivo com capacidades de NFC ou uma porta USB que pode aceitar a sua YubiKey." }, "yubikeyX": { "message": "YubiKey $INDEX$", @@ -2685,7 +2685,7 @@ } }, "webAuthnkeyX": { - "message": "$INDEX$ª Chave WebAuthn", + "message": "Chave WebAuthn $INDEX$", "placeholders": { "index": { "content": "$1", @@ -2700,34 +2700,34 @@ "message": "Uma das minhas chaves suporta NFC." }, "twoFactorYubikeySupportsNfcDesc": { - "message": "Se um dos seus YubiKeys oferece suporte a NFC (como um NEO YubiKey), você será solicitado em dispositivos móveis sempre que for detectada disponibilidade de NFC." + "message": "Se uma das suas YubiKeys oferece suporte a NFC (como uma YubiKey NEO), você será solicitado em dispositivos móveis sempre que for detectada disponibilidade da NFC." }, "yubikeysUpdated": { - "message": "YubiKeys atualizado" + "message": "YubiKeys atualizadas" }, "disableAllKeys": { "message": "Desativar todas as chaves" }, "twoFactorDuoDesc": { - "message": "Insira as informações do aplicativo Bitwarden no painel do administrador do Duo." + "message": "Digite as informações do aplicativo Bitwarden no painel de administração do Duo." }, "twoFactorDuoClientId": { - "message": "Id do cliente" + "message": "ID do cliente" }, "twoFactorDuoClientSecret": { "message": "Segredo do cliente" }, "twoFactorDuoApiHostname": { - "message": "Servidor da API" + "message": "Nome do servidor da API" }, "twoFactorEmailDesc": { - "message": "Siga estas etapas para configurar o login em duas etapas com e-mail:" + "message": "Siga estas etapas para configurar a autenticação em duas etapas com e-mail:" }, "twoFactorEmailEnterEmail": { - "message": "Insira o e-mail que você deseja receber o códigos de verificação" + "message": "Digite o e-mail que você deseja receber os códigos de verificação" }, "twoFactorEmailEnterCode": { - "message": "Insira o código de verificação de 6 dígitos que foi enviado pro seu e-mail" + "message": "Digite o código de verificação de 6 dígitos que foi enviado pro seu e-mail" }, "sendEmail": { "message": "Enviar e-mail" @@ -2751,31 +2751,31 @@ "message": "Dê à chave de segurança um nome amigável para identificá-la." }, "twoFactorU2fPlugInReadKey": { - "message": "Conecte a chave de segurança na porta USB do seu computador e clique no botão \"Ler Chave\"." + "message": "Conecte a chave de segurança na porta USB do seu computador e clique no botão \"Ler chave\"." }, "twoFactorU2fTouchButton": { "message": "Se a chave de segurança tiver um botão, toque nele." }, "twoFactorU2fSaveForm": { - "message": "Salvar o formulário." + "message": "Salve o formulário." }, "twoFactorU2fWarning": { - "message": "Devido às limitações da plataforma, o FIDO U2F não pode ser usado em todos os aplicativos do Bitwarden. Deve configurar outro provedor de autenticação em duas etapas para poder acessar a sua conta quando o FIDO U2F não puder ser usado. Plataformas suportadas:" + "message": "Devido às limitações da plataforma, o FIDO U2F não pode ser usado em todos os aplicativos do Bitwarden. Você deve configurar outro provedor de autenticação em duas etapas para poder acessar a sua conta quando o FIDO U2F não puder ser usado. Plataformas suportadas:" }, "twoFactorU2fSupportWeb": { - "message": "Cofre web e extensões de navegador em um desktop/laptop com um navegador que suporte U2F (Chrome, Opera, Vivaldi ou Firefox com o FIDO U2F ativado)." + "message": "Cofre web e extensões de navegador em um computador com um navegador que suporte U2F (Chrome, Opera, Vivaldi ou Firefox com o FIDO U2F ativado)." }, "twoFactorU2fWaiting": { - "message": "Esperando que você toque no botão da sua chave de segurança" + "message": "Aguardando que você toque no botão da sua chave de segurança" }, "twoFactorU2fClickSave": { - "message": "Clique no botão \"Salvar\" abaixo para ativar essa chave de segurança para a autenticação em duas etapas." + "message": "Clique no botão de \"Salvar\" abaixo para ativar essa chave de segurança para a autenticação em duas etapas." }, "twoFactorU2fProblemReadingTryAgain": { "message": "Houve um problema ao ler a chave de segurança. Tente novamente." }, "twoFactorRecoveryYourCode": { - "message": "Seu código de recuperação de login em duas etapas do Bitwarden" + "message": "Seu código de recuperação da autenticação em duas etapas do Bitwarden" }, "twoFactorRecoveryNoCode": { "message": "Você ainda não ativou nenhum provedor de autenticação em duas etapas. Depois de ativar um provedor de autenticação em duas etapas, você pode conferir aqui o seu código de recuperação." @@ -2818,7 +2818,7 @@ } }, "noUnsecuredWebsites": { - "message": "Nenhum item no seu cofre tem URIs inseguros." + "message": "Nenhum item no seu cofre tem URIs sem segurança." }, "inactive2faReport": { "message": "Autenticação em duas etapas inativa" @@ -2830,7 +2830,7 @@ "message": "Credenciais encontradas sem autenticação em duas etapas" }, "inactive2faFoundReportDesc": { - "message": "Encontramos $COUNT$ site(s) em seu(s) $VAULT$ que pode(m) não estar configurado com a autenticação em duas etapas (de acordo com 2fa.directory). Para proteger ainda mais essas contas, você deve configurar a autenticação em duas etapas.", + "message": "Encontramos $COUNT$ site(s) em seu(s) $VAULT$ que pode(m) não estar configurado(s) com a autenticação em duas etapas (de acordo com 2fa.directory). Para proteger ainda mais essas contas, você deve configurar a autenticação em duas etapas.", "placeholders": { "count": { "content": "$1", @@ -2858,7 +2858,7 @@ "message": "Senhas expostas encontradas" }, "exposedPasswordsFoundReportDesc": { - "message": "Encontramos $COUNT$ itens em seu(s) cofres $VAULT$ com senhas expostas em vazamentos conhecidos de dados. Você deve alterá-las para uma nova senha.", + "message": "Encontramos $COUNT$ itens em seu(s) cofre(s) $VAULT$ com senhas expostas em vazamentos conhecidos de dados. Você deve alterá-las para uma nova senha.", "placeholders": { "count": { "content": "$1", @@ -2871,7 +2871,7 @@ } }, "noExposedPasswords": { - "message": "Nenhum item no seu cofre tem senhas que foram expostas em violações de dados conhecidas." + "message": "Nenhum item em seu cofre tem senhas expostas em vazamentos de dados conhecidos." }, "checkExposedPasswords": { "message": "Conferir senhas expostas" @@ -2957,16 +2957,16 @@ "message": "Vazamento de dados" }, "breachDesc": { - "message": "Contas comprometidas podem expor sua informação pessoal. Dê segurança a contas vazadas ativando o 2FA ou criando uma senha mais forte." + "message": "Contas comprometidas podem expor sua informação pessoal. Proteja as contas vazadas ativando o 2FA ou criando uma senha mais forte." }, "breachCheckUsernameEmail": { - "message": "Verifique qualquer nome de usuário ou endereço de e-mail que você usa." + "message": "Confira quaisquer nomes de usuário ou endereços de e-mail que você usa." }, "checkBreaches": { - "message": "Conferir brechas" + "message": "Conferir vazamentos" }, "breachUsernameNotFound": { - "message": "$USERNAME$ não foi encontrado em nenhuma violação de dados conhecida.", + "message": "$USERNAME$ não foi encontrado em nenhum vazamento de dados conhecido.", "placeholders": { "username": { "content": "$1", @@ -2979,7 +2979,7 @@ "description": "ex. Good News, No Breached Accounts Found!" }, "breachUsernameFound": { - "message": "$USERNAME$ foi encontrado em $COUNT$ violações de dados diferentes on-line.", + "message": "$USERNAME$ foi encontrado em $COUNT$ vazamentos de dados diferentes on-line.", "placeholders": { "username": { "content": "$1", @@ -3004,19 +3004,19 @@ "message": "Usuários afetados" }, "breachOccurred": { - "message": "Ocorreu uma brecha" + "message": "Ocorreu um vazamento" }, "breachReported": { - "message": "Brecha reportada" + "message": "Vazamento relatado" }, "reportError": { "message": "Ocorreu um erro ao tentar carregar o relatório. Tente novamente" }, "billing": { - "message": "Cobrança" + "message": "Faturamento" }, "billingPlanLabel": { - "message": "Plano de cobrança" + "message": "Plano de faturamento" }, "paymentType": { "message": "Tipo de pagamento" @@ -3034,17 +3034,17 @@ "description": "Add more credit to your account's balance." }, "amount": { - "message": "Montante", + "message": "Valor", "description": "Dollar amount, or quantity." }, "creditDelayed": { "message": "O crédito adicionado aparecerá em sua conta após o pagamento ser totalmente processado. Alguns métodos de pagamento são mais lentos e podem levar mais tempo para processar do que outros." }, "makeSureEnoughCredit": { - "message": "Por favor, certifique-se de que sua conta tenha crédito suficiente para esta compra. Se sua conta não tiver crédito suficiente disponível, seu método de pagamento padrão será usado para completar a diferença. Você pode adicionar crédito à sua conta na página de Cobrança." + "message": "Certifique-se de que sua conta tenha crédito suficiente para esta compra. Se sua conta não tiver crédito suficiente disponível, seu método de pagamento padrão será usado para completar a diferença. Você pode adicionar crédito à sua conta na página de Faturamento." }, "notEnoughAccountCredit": { - "message": "Você não tem crédito suficiente na conta para esta compra. Você pode adicionar crédito à sua conta na página de Cobrança." + "message": "Você não tem crédito suficiente na conta para esta compra. Você pode adicionar crédito à sua conta na página de Faturamento." }, "creditAppliedDesc": { "message": "O crédito da sua conta pode ser usado para efetuar compras. Qualquer crédito disponível será automaticamente usado em faturas geradas nesta conta." @@ -3054,34 +3054,43 @@ "description": "Another way of saying \"Get a Premium membership\"" }, "premiumUpdated": { - "message": "Você se tornou Premium." + "message": "Você fez upgrade para o Premium." }, "premiumUpgradeUnlockFeatures": { - "message": "Faça upgrade da sua conta para uma conta Premium e desbloqueie ótimos recursos adicionais." + "message": "Faça upgrade da sua conta para um plano Premium e desbloqueie ótimos recursos adicionais." }, "premiumSignUpStorage": { - "message": "1 GB de armazenamento de arquivos encriptados." + "message": "1 GB de armazenamento criptografado para anexo de arquivos." + }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ de armazenamento criptografado para anexos de arquivo.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } }, "premiumSignUpTwoStepOptions": { - "message": "Opções adicionais de autenticação em duas etapas, como YubiKey, e Duo." + "message": "Opções de autenticação em duas etapas proprietárias como YubiKey e Duo." }, "premiumSignUpEmergency": { "message": "Acesso de emergência" }, "premiumSignUpReports": { - "message": "Higiene de senha, saúde da conta, e relatórios sobre violação de dados para manter o seu cofre seguro." + "message": "Higiene de senhas, saúde da conta, e relatórios sobre vazamentos de dados para manter o seu cofre seguro." }, "premiumSignUpTotp": { - "message": "Gerador de código de verificação TOTP (2FA) para logins no seu cofre." + "message": "Gerador de código de verificação TOTP (2FA) para credenciais no seu cofre." }, "premiumSignUpSupport": { "message": "Prioridade no suporte ao cliente." }, "premiumSignUpFuture": { - "message": "Todos os recursos Premium no futuro. Mais em breve!" + "message": "Todos as recursos do Premium no futuro. Mais em breve!" }, "premiumPrice": { - "message": "Tudo por apenas $PRICE$ /ano!", + "message": "Tudo por apenas $PRICE$ por ano!", "placeholders": { "price": { "content": "$1", @@ -3090,7 +3099,7 @@ } }, "premiumPriceWithFamilyPlan": { - "message": "Seja Premium por apenas $PRICE$ por ano, ou obtenha contas premium para $FAMILYPLANUSERCOUNT$ usuários e compartilhamento familiar ilimitado com um ", + "message": "Torne-se Premium por apenas $PRICE$ por ano, ou obtenha contas Premium para $FAMILYPLANUSERCOUNT$ usuários e compartilhamento familiar ilimitado com um ", "placeholders": { "price": { "content": "$1", @@ -3103,16 +3112,16 @@ } }, "bitwardenFamiliesPlan": { - "message": "Plano Bitwarden Families." + "message": "Plano Bitwarden Familías." }, "addons": { - "message": "Complementos" + "message": "Extensões" }, "premiumAccess": { "message": "Acesso Premium" }, "premiumAccessDesc": { - "message": "Você pode adicionar acesso Premium a todos os membros da sua organização a $PRICE$ por $INTERVAL$.", + "message": "Você pode adicionar acesso ao Premium a todos os membros da sua organização por $PRICE$ ao $INTERVAL$.", "placeholders": { "price": { "content": "$1", @@ -3124,14 +3133,23 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Sua assinatura Premium terminou" + }, + "premiumSubscriptionEndedDesc": { + "message": "Para recuperar o seu acesso ao seu arquivo, retoma sua assinatura Premium. Se editar detalhes de um item arquivado antes de retomar, ele será movido de volta para o seu cofre." + }, + "restartPremium": { + "message": "Retomar Premium" + }, "additionalStorageGb": { "message": "Armazenamento adicional (GB)" }, "additionalStorageGbDesc": { - "message": "# de GB adicional" + "message": "Número de GB adicionais" }, "additionalStorageIntervalDesc": { - "message": "Seu plano tem $SIZE$ de armazenamento criptografado de arquivos. Você pode adicionar armazenamento adicional por $PRICE$ por GB /$INTERVAL$.", + "message": "Seu plano vem com $SIZE$ de armazenamento criptografado de arquivos. Você pode adicionar armazenamento adicional por $PRICE$ por GB ao $INTERVAL$.", "placeholders": { "size": { "content": "$1", @@ -3169,7 +3187,7 @@ "message": "mês" }, "monthAbbr": { - "message": "mês.", + "message": "mês", "description": "Short abbreviation for 'month'" }, "paymentChargedAnnually": { @@ -3188,22 +3206,22 @@ "message": "Seu método de pagamento será cobrado por qualquer assinatura não paga." }, "paymentChargedWithTrial": { - "message": "Seu plano vem com um teste gratuito de 7 dias. Seu cartão não será cobrado até que o período de teste termine. Você pode cancelar a qualquer momento." + "message": "Seu plano vem com um teste grátis de 7 dias. Seu cartão não será cobrado até que o período de teste termine. Você pode cancelar a qualquer momento." }, "paymentInformation": { "message": "Informações de pagamento" }, "billingInformation": { - "message": "Informações de cobrança" + "message": "Informações de faturamento" }, "billingTrialSubLabel": { - "message": "Seu método de pagamento não será cobrado durante o teste gratuito de 7 dias." + "message": "Seu método de pagamento não será cobrado durante o teste grátis de 7 dias." }, "creditCard": { "message": "Cartão de crédito" }, "paypalClickSubmit": { - "message": "Clique no botão do PayPal para entrar na sua conta do PayPal e, em seguida, clique no botão Enviar abaixo para continuar." + "message": "Clique no botão do PayPal para se conectar à sua conta do PayPal e, em seguida, clique no botão Enviar abaixo para continuar." }, "cancelSubscription": { "message": "Cancelar assinatura" @@ -3233,7 +3251,7 @@ "message": "Você tem certeza que deseja cancelar? Você perderá o acesso a todos os recursos dessa assinatura no final deste ciclo de faturamento." }, "canceledSubscription": { - "message": "A assinatura foi cancelada." + "message": "Assinatura cancelada" }, "neverExpires": { "message": "Nunca vence" @@ -3245,22 +3263,22 @@ "message": "Próxima cobrança" }, "nextChargeHeader": { - "message": "Next Charge" + "message": "Próxima cobrança" }, "plan": { - "message": "Plan" + "message": "Plano" }, "details": { "message": "Detalhes" }, "discount": { - "message": "discount" + "message": "desconto" }, "downloadLicense": { "message": "Baixar licença" }, "viewBillingToken": { - "message": "Ver token de cobrança" + "message": "Ver token de faturamento" }, "updateLicense": { "message": "Atualizar licença" @@ -3269,7 +3287,7 @@ "message": "Gerenciar assinatura" }, "launchCloudSubscription": { - "message": "Lançar assinatura na nuvem" + "message": "Iniciar Assinatura na Nuvem" }, "storage": { "message": "Armazenamento" @@ -3281,7 +3299,7 @@ "message": "Remover armazenamento" }, "subscriptionStorage": { - "message": "A sua assinatura tem um total de $MAX_STORAGE$ GB de armazenamento de arquivos criptografados. Você está usando $USED_STORAGE$ no momento.", + "message": "A sua assinatura tem um total de $MAX_STORAGE$ GB de armazenamento criptografado de arquivos. Você está usando $USED_STORAGE$ no momento.", "placeholders": { "max_storage": { "content": "$1", @@ -3315,11 +3333,11 @@ "message": "Nenhuma fatura paga." }, "paid": { - "message": "Pago", + "message": "Paga", "description": "Past tense status of an invoice. ex. Paid or unpaid." }, "unpaid": { - "message": "Por pagar", + "message": "Não paga", "description": "Past tense status of an invoice. ex. Paid or unpaid." }, "transactions": { @@ -3330,7 +3348,7 @@ "message": "Sem transações." }, "chargeNoun": { - "message": "Carregar", + "message": "Cobrança", "description": "Noun. A charge from a payment method." }, "refundNoun": { @@ -3353,10 +3371,10 @@ "message": "GB de armazenamento para remover" }, "storageAddNote": { - "message": "A adição de armazenamento resultará em ajustes no valor total da fatura e será cobrado imediatamente pela forma de pagamento registrada. A primeira cobrança será rateada pelo restante do ciclo de faturamento atual." + "message": "A adição de armazenamento resultará em ajustes no valor total da fatura e será cobrado imediatamente pela forma de pagamento cadastrada. A primeira cobrança será rateada pelo restante do ciclo de faturamento atual." }, "storageRemoveNote": { - "message": "A remoção do armazenamento resultará em ajustes no valor total da fatura que serão divididos em sua próxima cobrança de faturamento." + "message": "A remoção do armazenamento resultará em ajustes no valor total da fatura que serão rateados na sua próxima cobrança de faturamento." }, "adjustedStorage": { "message": "Ajustado $AMOUNT$ GB de armazenamento.", @@ -3371,10 +3389,10 @@ "message": "Contatar suporte ao cliente" }, "contactSupportShort": { - "message": "Entrar em contato com o suporte" + "message": "Contatar suporte" }, "updatedPaymentMethod": { - "message": "Atualizar forma de pagamento." + "message": "Método de pagamento atualizado." }, "purchasePremium": { "message": "Comprar Premium" @@ -3392,16 +3410,16 @@ } }, "uploadLicenseFilePremium": { - "message": "Para fazer upgrade da sua conta para uma assinatura Premium, você precisa enviar um arquivo de licença válido." + "message": "Para fazer upgrade da sua conta para um plano Premium, você precisa enviar um arquivo de licença válido." }, "uploadLicenseFileOrg": { - "message": "Para criar uma organização hospedada localmente, você precisa fazer o upload de um arquivo de licença válido." + "message": "Para criar uma organização hospedada localmente, você precisa enviar um arquivo de licença válido." }, "accountEmailMustBeVerified": { "message": "O endereço de e-mail da sua conta deve ser verificado." }, "newOrganizationDesc": { - "message": "As organizações permitem que você compartilhe partes do seu cofre com outras pessoas, além de gerenciar usuários relacionados para uma entidade específica, como uma família, uma equipe pequena ou uma empresa grande." + "message": "As organizações permitem que você compartilhe partes do seu cofre com outras pessoas, além de gerenciar usuários relacionados a uma entidade específica, como uma família, uma equipe pequena, ou uma empresa grande." }, "generalInformation": { "message": "Informações gerais" @@ -3410,10 +3428,10 @@ "message": "Nome da organização" }, "accountOwnedBusiness": { - "message": "Esta conta pertence a uma empresa." + "message": "Esta conta pertence à uma empresa." }, "billingEmail": { - "message": "E-mail de cobrança" + "message": "E-mail de faturamento" }, "businessName": { "message": "Nome da empresa" @@ -3431,10 +3449,10 @@ "message": "Vagas de usuários adicionais" }, "userSeatsDesc": { - "message": "# de assentos de usuário" + "message": "Número de vagas de usuário" }, "userSeatsAdditionalDesc": { - "message": "O seu plano vem com assentos de usuário de $BASE_SEATS$. Você pode adicionar mais usuários por $SEAT_PRICE$ por usuário /mês.", + "message": "Seu plano vem com $BASE_SEATS$ vagas de usuário. Você pode adicionar usuários adicionais por $SEAT_PRICE$ por usuário ao mês.", "placeholders": { "base_seats": { "content": "$1", @@ -3447,14 +3465,14 @@ } }, "userSeatsHowManyDesc": { - "message": "Quanto mais assentos de usuário você precisa? Você também pode adicionar mais assentos depois, se necessário." + "message": "Quanto mais vagas de usuário você precisa? Você também pode adicionar mais vagas depois, se necessário." }, "planNameFree": { "message": "Grátis", "description": "Free as in 'free beer'." }, "planDescFree": { - "message": "Para usuários de testes ou pessoais para compartilhar com $COUNT$ outro usuário.", + "message": "Para testes ou usuários pessoais para compartilhar com $COUNT$ outro usuário.", "placeholders": { "count": { "content": "$1", @@ -3463,10 +3481,10 @@ } }, "planNameFamilies": { - "message": "Families" + "message": "Famílias" }, "planDescFamilies": { - "message": "Para uso pessoal, para compartilhar com a família & amigos." + "message": "Para uso pessoal, para compartilhar com a família e amigos." }, "planNameTeams": { "message": "Equipes" @@ -3475,7 +3493,7 @@ "message": "Para empresas e outras organizações de equipe." }, "planNameTeamsStarter": { - "message": "Teams Starter" + "message": "Equipes Iniciantes" }, "planNameEnterprise": { "message": "Empresarial" @@ -3517,7 +3535,7 @@ } }, "limitedCollections": { - "message": "Limitado a $COUNT$ coleções", + "message": "Limitado a $COUNT$ conjuntos", "placeholders": { "count": { "content": "$1", @@ -3538,10 +3556,10 @@ "message": "Adicione e compartilhe com usuários ilimitados" }, "createUnlimitedCollections": { - "message": "Crie coleções ilimitadas" + "message": "Crie conjuntos ilimitados" }, "gbEncryptedFileStorage": { - "message": "$SIZE$ de armazenamento encriptado", + "message": "$SIZE$ de armazenamento criptografado", "placeholders": { "size": { "content": "$1", @@ -3565,13 +3583,13 @@ "message": "Acompanhe as ações do usuário com os registros de auditoria" }, "enforce2faDuo": { - "message": "Reforçar o 2FA com o Duo" + "message": "Aplique o 2FA com o Duo" }, "priorityCustomerSupport": { - "message": "Suporte prioritário ao cliente" + "message": "Suporte ao cliente com prioridade" }, "xDayFreeTrial": { - "message": "Avaliação gratuita de $COUNT$ dia(s), cancele a qualquer momento", + "message": "Teste grátis de $COUNT$ dia(s), cancele a qualquer momento", "placeholders": { "count": { "content": "$1", @@ -3589,7 +3607,7 @@ } }, "trialSecretsManagerThankYou": { - "message": "Obrigado por se inscrever no Bitwarden Secrets Manager para $PLAN$!", + "message": "Obrigado por se inscrever no Bitwarden Gerenciador de Segredos para $PLAN$!", "placeholders": { "plan": { "content": "$1", @@ -3598,7 +3616,7 @@ } }, "trialPaidInfoMessage": { - "message": "Seu $PLAN$ de teste de 7 dias será convertido em uma assinatura paga após 7 dias.", + "message": "Seu teste de 7 dias do $PLAN$ será convertido em uma assinatura paga após 7 dias.", "placeholders": { "plan": { "content": "$1", @@ -3607,13 +3625,13 @@ } }, "trialConfirmationEmail": { - "message": "Enviamos um e-mail de confirmação para o e-mail de cobrança da sua equipe em " + "message": "Enviamos um e-mail de confirmação para o e-mail de faturamento da sua equipe em " }, "monthly": { - "message": "Mensal" + "message": "Mensalmente" }, "annually": { - "message": "Anual" + "message": "Anualmente" }, "annual": { "message": "Anual" @@ -3628,7 +3646,7 @@ "message": "A sua nova organização está pronta!" }, "organizationUpgraded": { - "message": "Um upgrade foi feito na organização" + "message": "Upgrade feito na organização" }, "leave": { "message": "Sair" @@ -3640,16 +3658,16 @@ "message": "Você saiu da organização." }, "defaultCollection": { - "message": "Coleção padrão" + "message": "Conjunto padrão" }, "getHelp": { - "message": "Obter ajuda" + "message": "Receber ajuda" }, "getApps": { - "message": "Baixe os apps" + "message": "Baixar os apps" }, "loggedInAs": { - "message": "Sessão iniciada como" + "message": "Conectado como" }, "eventLogs": { "message": "Registro de eventos" @@ -3679,7 +3697,7 @@ "message": "Editar grupo" }, "deleteGroupConfirmation": { - "message": "Você tem certeza que deseja excluir este grupo?" + "message": "Você tem certeza que deseja apagar este grupo?" }, "deleteMultipleGroupsConfirmation": { "message": "Você tem certeza que deseja apagar os seguintes $QUANTITY$ grupo(s)?", @@ -3691,16 +3709,16 @@ } }, "removeUserConfirmation": { - "message": "Você tem certeza que deseja remover este(a) usuário(a)?" + "message": "Você tem certeza que deseja remover este usuário?" }, "removeOrgUserConfirmation": { "message": "Quando um membro é removido, ele já não mais acesso aos dados da organização, e esta ação é irreversível. Para adicionar o membro de volta à organização, ele deve ser convidado e integrado novamente." }, "revokeUserConfirmation": { - "message": "Quando um membro é revogado, ele não tem mais acesso aos dados da organização. Para restaurar rapidamente o acesso ao membro vá para a aba Revogados." + "message": "Quando um membro é revogado, ele não tem mais acesso aos dados da organização. Para restaurar rapidamente o acesso do membro vá para a aba de Revogados." }, "removeUserConfirmationKeyConnector": { - "message": "Alerta! Este usuário precisa do Conector de Chave para gerenciar sua criptografia. Remover este usuário de sua organização desativará permanentemente sua conta. Essa ação não pode ser desfeita. Você quer prosseguir?" + "message": "Alerta! Este usuário precisa do Key Connector para gerenciar sua criptografia. Remover este usuário de sua organização desativará permanentemente a conta dele. Essa ação não pode ser desfeita. Você quer prosseguir?" }, "externalId": { "message": "ID externo" @@ -3712,10 +3730,10 @@ "message": "ID externo do SSO" }, "ssoExternalIdDesc": { - "message": "O ID Externo do SSO é uma referência não criptografada entre o Bitwarden e seu provedor de SSO configurado." + "message": "O ID externo do SSO é uma referência não criptografada entre o Bitwarden e seu provedor de SSO configurado." }, "nestCollectionUnder": { - "message": "Agrupar coleção sob" + "message": "Agrupar conjunto em" }, "accessControl": { "message": "Controle de acesso" @@ -3724,25 +3742,25 @@ "message": "Somente leitura" }, "newCollection": { - "message": "Nova coleção" + "message": "Novo conjunto" }, "addCollection": { - "message": "Adicionar coleção" + "message": "Adicionar conjunto" }, "editCollection": { - "message": "Editar coleção" + "message": "Editar conjunto" }, "collectionInfo": { - "message": "Informações da coleção" + "message": "Informações do conjunto" }, "deleteCollectionConfirmation": { - "message": "Você tem certeza que deseja excluir esta coleção?" + "message": "Você tem certeza que deseja apagar este conjunto?" }, "editMember": { "message": "Editar membro" }, "fieldOnTabRequiresAttention": { - "message": "Um campo na aba '$TAB$' requer sua atenção.", + "message": "Um campo na aba de '$TAB$' requer sua atenção.", "placeholders": { "tab": { "content": "$1", @@ -3754,7 +3772,7 @@ "message": "Convide um novo usuário para sua organização digitando o endereço de e-mail da conta Bitwarden dele abaixo. Se ele não tiver uma conta no Bitwarden, ele será solicitado a criar uma nova conta." }, "inviteMultipleEmailDesc": { - "message": "Você pode convidar até $COUNT$ usuários por vez, separando-os com uma vírgula.", + "message": "Digite até $COUNT$ e-mails, separados com vírgula.", "placeholders": { "count": { "content": "$1", @@ -3769,16 +3787,16 @@ "message": "Você tem 0 convites restantes." }, "userUsingTwoStep": { - "message": "Este usuário está usando o login em duas etapas para proteger a sua conta." + "message": "Este usuário está usando a autenticação em duas etapas para proteger a sua conta." }, "search": { - "message": "Pesquisar" + "message": "Buscar" }, "invited": { - "message": "Convidado" + "message": "Convidados" }, "confirmed": { - "message": "Confirmado" + "message": "Confirmados" }, "clientOwnerEmail": { "message": "Email do proprietário do cliente" @@ -3787,7 +3805,7 @@ "message": "Proprietário" }, "ownerDesc": { - "message": "Gerencie todos os aspectos da sua organização, incluindo cobrança e assinaturas" + "message": "Gerencie todos os aspectos da sua organização, incluindo o faturamento e as assinaturas" }, "clientOwnerDesc": { "message": "Este usuário deve ser independente do Provedor. Se o Provedor estiver desassociado da organização, este usuário manterá a propriedade da organização." @@ -3796,16 +3814,16 @@ "message": "Administrador" }, "adminDesc": { - "message": "Gerencie o acesso à organização, todas as coleções, membros, relatórios, e configurações de segurança" + "message": "Gerencie o acesso à organização, todos os conjuntos, membros, relatórios, e configurações de segurança" }, "user": { "message": "Usuário" }, "userDesc": { - "message": "Acesse e adicione itens a coleções atribuídas" + "message": "Acesse e adicione itens aos conjuntos atribuídos" }, "all": { - "message": "Todos" + "message": "Tudo" }, "addAccess": { "message": "Adicionar acesso" @@ -3814,10 +3832,10 @@ "message": "Adicionar filtro de acesso" }, "refresh": { - "message": "Atualizar" + "message": "Recarregar" }, "timestamp": { - "message": "Timestamp" + "message": "Horário" }, "event": { "message": "Evento" @@ -3837,14 +3855,14 @@ "description": "Browser extension/addon" }, "desktop": { - "message": "Área de Trabalho", + "message": "Computador", "description": "Desktop app" }, "webVault": { "message": "Cofre web" }, "webApp": { - "message": "App web" + "message": "Aplicativo web" }, "cli": { "message": "CLI" @@ -3853,10 +3871,10 @@ "message": "Cofre web do Bitwarden" }, "bitSecretsManager": { - "message": "Gerenciador de Segredos do Bitwarden" + "message": "Bitwarden Gerenciador de Segredos" }, "loggedIn": { - "message": "Conectou" + "message": "Conectou-se" }, "changedPassword": { "message": "Senha da conta alterada" @@ -3871,10 +3889,10 @@ "message": "Conta recuperada da autenticação em duas etapas." }, "failedLogin": { - "message": "Falha na tentativa de login com senha incorreta." + "message": "Falha na tentativa de autenticação com senha incorreta." }, "failedLogin2fa": { - "message": "A tentativa de login falhou com o login incorreto em duas etapas." + "message": "Falha na tentativa de autenticação com autenticação em duas etapas errada." }, "incorrectPassword": { "message": "Senha incorreta" @@ -3899,7 +3917,7 @@ "message": "Editou as configurações da organização." }, "createdItemId": { - "message": "Item criado $ID$.", + "message": "Criou o item $ID$.", "placeholders": { "id": { "content": "$1", @@ -3908,7 +3926,7 @@ } }, "editedItemId": { - "message": "Item editado $ID$.", + "message": "Editou o item $ID$.", "placeholders": { "id": { "content": "$1", @@ -3917,7 +3935,7 @@ } }, "deletedItemId": { - "message": "Item $ID$ enviado para lixeira.", + "message": "Enviou o item $ID$ para a lixeira.", "placeholders": { "id": { "content": "$1", @@ -3941,7 +3959,7 @@ "message": "Ver todas as opções de autenticação" }, "viewedItemId": { - "message": "Item $ID$ visualizado.", + "message": "Visualizou o item $ID$.", "placeholders": { "id": { "content": "$1", @@ -3950,7 +3968,7 @@ } }, "viewedPasswordItemId": { - "message": "Senha visualizada para o item $ID$.", + "message": "Visualizou a senha do item $ID$.", "placeholders": { "id": { "content": "$1", @@ -3959,7 +3977,7 @@ } }, "viewedHiddenFieldItemId": { - "message": "Campo oculto visualizado para o item $ID$.", + "message": "Visualizou o campo oculto do item $ID$.", "placeholders": { "id": { "content": "$1", @@ -3977,7 +3995,7 @@ } }, "viewedSecurityCodeItemId": { - "message": "Código de segurança visualizado para o item $ID$.", + "message": "Visualizou o código de segurança do item $ID$.", "placeholders": { "id": { "content": "$1", @@ -3986,7 +4004,7 @@ } }, "viewCollectionWithName": { - "message": "Ver coleção - $NAME$", + "message": "Ver conjunto - $NAME$", "placeholders": { "name": { "content": "$1", @@ -4004,7 +4022,7 @@ } }, "copiedPasswordItemId": { - "message": "Senha copiada para o item $ID$.", + "message": "Copiou a senha do item $ID$.", "placeholders": { "id": { "content": "$1", @@ -4013,7 +4031,7 @@ } }, "copiedHiddenFieldItemId": { - "message": "Campo oculto copiado para o item $ID$.", + "message": "Copiou o campo oculto do item $ID$.", "placeholders": { "id": { "content": "$1", @@ -4022,7 +4040,7 @@ } }, "copiedSecurityCodeItemId": { - "message": "Código de segurança copiado para o item $ID$.", + "message": "Copiou o código de segurança do item $ID$.", "placeholders": { "id": { "content": "$1", @@ -4031,7 +4049,7 @@ } }, "autofilledItemId": { - "message": "Item $ID$ auto-preenchido.", + "message": "Preencheu automaticamente o item $ID$.", "placeholders": { "id": { "content": "$1", @@ -4040,7 +4058,7 @@ } }, "createdCollectionId": { - "message": "Coleção criada $ID$.", + "message": "Criou o conjunto $ID$.", "placeholders": { "id": { "content": "$1", @@ -4049,7 +4067,7 @@ } }, "editedCollectionId": { - "message": "Coleção editada $ID$.", + "message": "Editou o conjunto $ID$.", "placeholders": { "id": { "content": "$1", @@ -4058,10 +4076,10 @@ } }, "deletedCollections": { - "message": "Apagou as coleções" + "message": "Conjuntos apagados" }, "deletedCollectionId": { - "message": "Coleção excluída $ID$.", + "message": "Apagou o conjunto $ID$.", "placeholders": { "id": { "content": "$1", @@ -4070,7 +4088,7 @@ } }, "editedPolicyId": { - "message": "Política $ID$ editada.", + "message": "Editou a política $ID$.", "placeholders": { "id": { "content": "$1", @@ -4079,7 +4097,7 @@ } }, "createdGroupId": { - "message": "Grupo criado $ID$.", + "message": "Criou o grupo $ID$.", "placeholders": { "id": { "content": "$1", @@ -4088,7 +4106,7 @@ } }, "editedGroupId": { - "message": "Grupo editado $ID$.", + "message": "Editou o grupo $ID$.", "placeholders": { "id": { "content": "$1", @@ -4097,7 +4115,7 @@ } }, "deletedGroupId": { - "message": "Grupo excluído $ID$.", + "message": "Apagou o grupo $ID$.", "placeholders": { "id": { "content": "$1", @@ -4115,7 +4133,7 @@ } }, "removedUserId": { - "message": "Usuário removido $ID$.", + "message": "Removeu o usuário $ID$.", "placeholders": { "id": { "content": "$1", @@ -4133,7 +4151,7 @@ } }, "revokedUserId": { - "message": "Acesso de $ID$ revogado da organização.", + "message": "Revogou o acesso de $ID$ à organização.", "placeholders": { "id": { "content": "$1", @@ -4142,7 +4160,7 @@ } }, "restoredUserId": { - "message": "Acesso de $ID$ restaurado à organização.", + "message": "Restaurou o acesso de $ID$ à organização.", "placeholders": { "id": { "content": "$1", @@ -4160,7 +4178,7 @@ } }, "createdAttachmentForItem": { - "message": "Anexo criado para o item $ID$.", + "message": "Criou anexo para o item $ID$.", "placeholders": { "id": { "content": "$1", @@ -4169,7 +4187,7 @@ } }, "deletedAttachmentForItem": { - "message": "Anexo excluído para o item $ID$.", + "message": "Apagou anexo do item $ID$.", "placeholders": { "id": { "content": "$1", @@ -4178,7 +4196,7 @@ } }, "editedCollectionsForItem": { - "message": "Coleção editada para o item $ID$.", + "message": "Editou conjuntos do item $ID$.", "placeholders": { "id": { "content": "$1", @@ -4187,7 +4205,7 @@ } }, "invitedUserId": { - "message": "Usuário(a) convidado(a) $ID$.", + "message": "Convidou o usuário $ID$.", "placeholders": { "id": { "content": "$1", @@ -4196,7 +4214,7 @@ } }, "confirmedUserId": { - "message": "Usuário(a) confirmado(a) $ID$.", + "message": "Confirmou o usuário $ID$.", "placeholders": { "id": { "content": "$1", @@ -4205,7 +4223,7 @@ } }, "editedUserId": { - "message": "Usuário(a) editado(a) $ID$.", + "message": "Editou o usuário $ID$.", "placeholders": { "id": { "content": "$1", @@ -4214,7 +4232,7 @@ } }, "editedGroupsForUser": { - "message": "Grupo editado para usuário(a) $ID$.", + "message": "Editou grupos para o usuário $ID$.", "placeholders": { "id": { "content": "$1", @@ -4223,10 +4241,10 @@ } }, "unlinkedSso": { - "message": "SSO desvinculado." + "message": "Desvinculou o SSO." }, "unlinkedSsoUser": { - "message": "SSO desvinculado para o usuário $ID$.", + "message": "Desvinculou o SSO para o usuário $ID$.", "placeholders": { "id": { "content": "$1", @@ -4289,7 +4307,7 @@ "message": "Você está tentando se conectar?" }, "logInAttemptBy": { - "message": "Tentativa de autenticação por $EMAIL$", + "message": "Tentativa de acesso por $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -4310,7 +4328,7 @@ "message": "Negar acesso" }, "thisRequestIsNoLongerValid": { - "message": "Este pedido não é mais válido." + "message": "Esta solicitação não é mais válida." }, "loginRequestApprovedForEmailOnDevice": { "message": "Solicitação de autenticação aprovada para $EMAIL$ em $DEVICE$", @@ -4326,7 +4344,7 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "Você negou uma tentativa de autenticação de outro dispositivo. Se era você, tente entrar com o dispositivo novamente." + "message": "Você negou uma tentativa de autenticação de outro dispositivo. Se era você, tente se conectar com o dispositivo novamente." }, "loginRequestHasAlreadyExpired": { "message": "A solicitação de autenticação já expirou." @@ -4350,7 +4368,7 @@ "message": "Confira seu e-mail" }, "followTheLinkInTheEmailSentTo": { - "message": "Siga o link no e-mail enviado para" + "message": "Abra o link no e-mail enviado para" }, "andContinueCreatingYourAccount": { "message": "e continue criando a sua conta." @@ -4359,7 +4377,7 @@ "message": "Nenhum e-mail?" }, "goBack": { - "message": "Voltar" + "message": "Volte" }, "toEditYourEmailAddress": { "message": "para editar o seu endereço de e-mail." @@ -4383,7 +4401,7 @@ "message": "Acesso a grupos" }, "groupAccessUserDesc": { - "message": "Conceda acesso ao membro a coleções adicionado-o a um ou mais grupos." + "message": "Conceda acesso ao membro a conjuntos adicionado-o a um ou mais grupos." }, "invitedUsers": { "message": "Usuário(s) convidado(s)" @@ -4434,13 +4452,13 @@ "message": "Verificar e-mail" }, "verifyEmailDesc": { - "message": "Confirme o endereço de e-mail da sua conta para desbloquear o acesso a todos os recursos." + "message": "Verifique o endereço de e-mail da sua conta para desbloquear o acesso a todos os recursos." }, "verifyEmailFirst": { - "message": "Primeiro, o endereço de e-mail da sua conta precisa ser confirmado." + "message": "Primeiro, o endereço de e-mail da sua conta precisa ser verificado." }, "checkInboxForVerification": { - "message": "Verifique sua caixa de entrada do e-mail para obter o código de verificação." + "message": "Confira sua caixa de entrada do e-mail por um link de verificação." }, "emailVerified": { "message": "E-mail da conta verificado" @@ -4449,49 +4467,49 @@ "message": "E-mail verificado" }, "emailVerifiedFailed": { - "message": "Não é possível confirmar o seu e-mail. Tente enviar um novo e-mail de verificação." + "message": "Não é possível verificar o seu e-mail. Tente enviar um novo e-mail de verificação." }, "emailVerificationRequired": { - "message": "Verificação de e-mail obrigatória" + "message": "Verificação de e-mail necessária" }, "emailVerificationRequiredDesc": { "message": "Você precisa verificar o seu e-mail para usar este recurso." }, "updateBrowser": { - "message": "Atualize o navegador" + "message": "Atualizar navegador" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Gerando sua Inteligência de Acesso..." }, "fetchingMemberData": { - "message": "Fetching member data..." + "message": "Buscando dados de membros..." }, "analyzingPasswordHealth": { - "message": "Analyzing password health..." + "message": "Analisando saúde das senhas..." }, "calculatingRiskScores": { - "message": "Calculating risk scores..." + "message": "Calculando pontuações de risco..." }, "generatingReportData": { - "message": "Generating report data..." + "message": "Gerando dados do relatório..." }, "savingReport": { - "message": "Saving report..." + "message": "Salvando relatório..." }, "compilingInsights": { - "message": "Compiling insights..." + "message": "Compilando conhecimentos..." }, "loadingProgress": { - "message": "Loading progress" + "message": "Progresso de carregamento" }, "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "message": "Isto pode levar alguns minutos." }, "riskInsightsRunReport": { "message": "Executar relatório" }, "updateBrowserDesc": { - "message": "Você está usando um navegador da Web não suportado. O cofre web pode não funcionar corretamente." + "message": "Você está usando um navegador da web não suportado. O cofre web pode não funcionar corretamente." }, "youHaveAPendingLoginRequest": { "message": "Você tem uma solicitação de autenticação pendente de outro dispositivo." @@ -4503,7 +4521,7 @@ "message": "Solicitação de autenticação" }, "freeTrialEndPromptCount": { - "message": "Seu teste gratuito termina em $COUNT$ dias.", + "message": "Seu teste grátis termina em $COUNT$ dias.", "placeholders": { "count": { "content": "$1", @@ -4512,7 +4530,7 @@ } }, "freeTrialEndPromptMultipleDays": { - "message": "$ORGANIZATION$, seu teste gratuito termina em $COUNT$ dias.", + "message": "$ORGANIZATION$, seu teste grátis termina em $COUNT$ dias.", "placeholders": { "count": { "content": "$2", @@ -4525,7 +4543,7 @@ } }, "freeTrialEndPromptTomorrow": { - "message": "$ORGANIZATION$, o seu teste gratuito termina amanhã.", + "message": "$ORGANIZATION$, o seu teste grátis termina amanhã.", "placeholders": { "organization": { "content": "$1", @@ -4534,10 +4552,10 @@ } }, "freeTrialEndPromptTomorrowNoOrgName": { - "message": "Seu teste gratuito termina amanhã." + "message": "Seu teste grátis termina amanhã." }, "freeTrialEndPromptToday": { - "message": "$ORGANIZATION$, seu teste gratuito termina hoje.", + "message": "$ORGANIZATION$, seu teste grátis termina hoje.", "placeholders": { "organization": { "content": "$1", @@ -4546,7 +4564,7 @@ } }, "freeTrialEndingTodayWithoutOrgName": { - "message": "Seu teste gratuito termina hoje." + "message": "Seu teste grátis termina hoje." }, "clickHereToAddPaymentMethod": { "message": "Clique aqui para adicionar um método de pagamento." @@ -4564,25 +4582,25 @@ } }, "joinOrganizationDesc": { - "message": "Você foi convidado para participar da organização listada acima. Para aceitar o convite, você precisa iniciar sessão ou criar uma nova conta no Bitwarden." + "message": "Você foi convidado para participar da organização listada acima. Para aceitar o convite, você precisa se conectar ou criar uma nova conta no Bitwarden." }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Termine de juntar-se nessa organização definindo uma senha principal." + "message": "Termine de juntar-se à organização configurando uma senha principal." }, "inviteAccepted": { "message": "Convite aceito" }, "invitationAcceptedDesc": { - "message": "Successfully accepted your invitation." + "message": "Seu convite foi aceito com sucesso." }, "inviteInitAcceptedDesc": { - "message": "Você agora pode acessar esta organização." + "message": "Agora, você pode acessar esta organização." }, "inviteAcceptFailed": { "message": "Não é possível aceitar o convite. Peça a um administrador da organização para enviar um novo convite." }, "inviteAcceptFailedShort": { - "message": "Não foi possível aceitar o convite. $DESCRIPTION$", + "message": "Não é possível aceitar o convite. $DESCRIPTION$", "placeholders": { "description": { "content": "$1", @@ -4594,10 +4612,10 @@ "message": "Lembrar e-mail" }, "recoverAccountTwoStepDesc": { - "message": "Se você não puder acessar sua conta por meio de seus métodos normais de autenticação em duas etapas, poderá usar seu código de recuperação para desativar a funcionalidade de duas etapas da sua conta." + "message": "Se você não puder acessar sua conta por meio de seus métodos normais de autenticação em duas etapas, poderá usar seu código de recuperação para desativar todos os provedores de duas etapas da sua conta." }, "logInBelowUsingYourSingleUseRecoveryCode": { - "message": "Autentique-se abaixo usando seu código de recuperação de uso único. Isso irá desativar todos os provedores de duas etapas na sua conta." + "message": "Conecte-se abaixo usando seu código de recuperação de uso único. Isso irá desativar todos os provedores de duas etapas na sua conta." }, "recoverAccountTwoStep": { "message": "Recuperar autenticação em duas etapas da conta" @@ -4608,8 +4626,26 @@ "learnMore": { "message": "Saiba mais" }, + "migrationsFailed": { + "message": "Ocorreu um erro ao atualizar as configurações de criptografia." + }, + "updateEncryptionSettingsTitle": { + "message": "Atualize suas configurações de criptografia" + }, + "updateEncryptionSettingsDesc": { + "message": "As novas configurações de criptografia recomendadas melhorarão a segurança da sua conta. Digite sua senha principal para atualizar agora." + }, + "confirmIdentityToContinue": { + "message": "Confirme sua identidade para continuar" + }, + "enterYourMasterPassword": { + "message": "Digite sua senha principal" + }, + "updateSettings": { + "message": "Atualizar configurações" + }, "deleteRecoverDesc": { - "message": "Insira seu endereço de e-mail abaixo para recuperar e excluir sua conta." + "message": "Digite seu endereço de e-mail abaixo para recuperar e apagar sua conta." }, "deleteRecoverEmailSent": { "message": "Se a sua conta existir, enviamos um e-mail para você com mais instruções." @@ -4654,7 +4690,7 @@ "message": "Organização apagada" }, "organizationDeletedDesc": { - "message": "A organização e todos os dados associados foram excluídos." + "message": "A organização e todos os dados associados foram apagados." }, "organizationUpdated": { "message": "Organização salva" @@ -4663,18 +4699,18 @@ "message": "Informações de tributos" }, "taxInformationDesc": { - "message": "Para clientes dentro dos EUA, o código postal (ZIP code) é necessário para satisfazer os requisitos fiscais das vendas. para outros países você pode opcionalmente fornecer um número de identificação fiscal (VAT/GST) e/ou um endereço para aparecer nas suas faturas." + "message": "Para clientes dentro dos EUA, o código postal (ZIP code) é necessário para satisfazer os requisitos fiscais das vendas, para outros países você pode opcionalmente fornecer um número de identificação fiscal (VAT/GST) e/ou um endereço para aparecer nas suas faturas." }, "billingPlan": { "message": "Plano", "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." }, "changeBillingPlan": { - "message": "Alterar plano", + "message": "Fazer upgrade do plano", "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." }, "changeBillingPlanUpgrade": { - "message": "Atualize sua conta para outro plano fornecendo as informações abaixo. Por favor, verifique se você tem um método de pagamento ativo adicionado à conta.", + "message": "Faça upgrade da sua conta para outro plano fornecendo as informações abaixo. Certifique-se que você tem um método de pagamento ativo adicionado à conta.", "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." }, "invoiceNumber": { @@ -4697,10 +4733,10 @@ "message": "Verificar conta bancária" }, "verifyBankAccountDesc": { - "message": "Fizemos dois micro-depósitos em sua conta bancária (pode demorar de 1 a 2 dias úteis para aparecer). Insira esses valores para confirmar a conta bancária." + "message": "Fizemos dois micro-depósitos em sua conta bancária (pode demorar de 1 a 2 dias úteis para aparecer). Digite esses valores para confirmar a conta bancária." }, "verifyBankAccountInitialDesc": { - "message": "O pagamento com uma conta bancária só está disponível para clientes nos Estados Unidos. Você será solicitado a confirmar sua conta bancária. Vamos fazer dois micro-depósitos nos próximos 1-2 dias úteis. Insira esses valores na página de faturamento da organização para confirmar a conta bancária." + "message": "O pagamento com uma conta bancária só está disponível para clientes nos Estados Unidos. Você precisará verificar sua conta bancária. Faremos um micro-depósito nos próximos 1-2 dias úteis. Digite os valores na página de assinatura da organização para verificar a conta bancária." }, "verifyBankAccountFailureWarning": { "message": "A falha na verificação da conta bancária resultará em um pagamento não atendido e sua assinatura será suspensa." @@ -4712,7 +4748,7 @@ "message": "Conta bancária" }, "amountX": { - "message": "Montante $COUNT$", + "message": "Valor $COUNT$", "description": "Used in bank account verification of micro-deposits. Amount, as in a currency amount. Ex. Amount 1 is $2.00, Amount 2 is $1.50", "placeholders": { "count": { @@ -4741,20 +4777,20 @@ "message": "Individual (pessoal)" }, "enterInstallationId": { - "message": "Insira o seu ID de instalação" + "message": "Digite o seu ID de instalação" }, "limitSubscriptionDesc": { - "message": "Defina um limite de vagas para sua assinatura. Quando esse limite for atingido, você não poderá convidar novos usuários." + "message": "Configure um limite de vagas para sua assinatura. Quando esse limite for atingido, você não poderá convidar novos usuários." }, "limitSmSubscriptionDesc": { - "message": "Defina um limite de vagas para sua assinatura do Gerenciador de Segredos. Ao atingir este limite, você não poderá convidar novos membros." + "message": "Configure um limite de vagas para sua assinatura do Gerenciador de Segredos. Ao atingir este limite, você não poderá convidar novos membros." }, "maxSeatLimit": { "message": "Limite de vagas (opcional)", "description": "Upper limit of seats to allow through autoscaling" }, "maxSeatCost": { - "message": "Custo de vaga potencial máximo" + "message": "Custo máximo possível de vagas" }, "addSeats": { "message": "Adicionar vagas", @@ -4777,7 +4813,7 @@ } }, "limitSubscription": { - "message": "Assinatura Limite (Opcional)" + "message": "Limitar assinatura (opcional)" }, "subscriptionSeats": { "message": "Vagas da assinatura" @@ -4813,7 +4849,7 @@ } }, "subscriptionUserSeatsWithoutAdditionalSeatsOption": { - "message": "Você pode convidar até $COUNT$ membros sem custos adicionais. Entre em contato com o Suporte ao Cliente para melhorar seu plano e convidar mais membros.", + "message": "Você pode convidar até $COUNT$ membros sem custos adicionais. Entre em contato com o Suporte ao Cliente para fazer upgrade do seu plano e convidar mais membros.", "placeholders": { "count": { "content": "$1", @@ -4822,7 +4858,7 @@ } }, "subscriptionFreePlan": { - "message": "Você não pode convidar mais do que $COUNT$ usuários sem melhorar seu plano.", + "message": "Você não pode convidar mais do que $COUNT$ usuários sem fazer upgrade do seu plano.", "placeholders": { "count": { "content": "$1", @@ -4831,7 +4867,7 @@ } }, "subscriptionUpgrade": { - "message": "Você não pode convidar mais de $COUNT$ membros sem melhorar seu plano.", + "message": "Você não pode convidar mais de $COUNT$ membros sem fazer upgrade do seu plano.", "placeholders": { "count": { "content": "$1", @@ -4849,7 +4885,7 @@ } }, "subscriptionMaxReached": { - "message": "Ajustes em sua assinatura resultarão em alterações rateadas em seus totais de cobrança. Você não pode convidar mais de $COUNT$ usuários sem aumentar suas vagas de assinatura.", + "message": "Ajustes em sua assinatura resultarão em alterações rateadas em seus totais de cobrança. Você não pode convidar mais de $COUNT$ usuários sem aumentar as vagas da sua assinatura.", "placeholders": { "count": { "content": "$1", @@ -4858,7 +4894,7 @@ } }, "subscriptionSeatMaxReached": { - "message": "Você não pode convidar mais de $COUNT$ membros sem aumentar as suas vagas da assinatura.", + "message": "Você não pode convidar mais de $COUNT$ membros sem aumentar as vagas da sua assinatura.", "placeholders": { "count": { "content": "$1", @@ -4873,13 +4909,13 @@ "message": "Vagas a remover" }, "seatsAddNote": { - "message": "A adição de assentos de usuário resultará em ajustes no total da cobrança, e será cobrada imediatamente pela sua forma de pagamento registrada. A primeira cobrança será rateada pelo restante do ciclo do faturamento atual." + "message": "A adição de vagas de usuário resultará em ajustes no total da cobrança, e será cobrada imediatamente pela sua forma de pagamento cadastrada. A primeira cobrança será rateada pelo restante do ciclo do faturamento atual." }, "seatsRemoveNote": { - "message": "A remoção dos assentos de usuário resultará em ajustes no total do faturamento, que será rateado como crédito para sua próxima cobrança." + "message": "A remoção de vagas de usuário resultará em ajustes no total do faturamento, que será rateado como crédito para sua próxima cobrança." }, "adjustedSeats": { - "message": "Ajustado $AMOUNT$ assentos de usuário.", + "message": "Ajustou $AMOUNT$ vagas de usuário.", "placeholders": { "amount": { "content": "$1", @@ -4897,7 +4933,7 @@ } }, "reorderToggleButton": { - "message": "Reordene $LABEL$. Use a tecla de seta para mover o item para cima ou para baixo.", + "message": "Reordenar $LABEL$. Use a tecla de seta para mover o item para cima ou para baixo.", "placeholders": { "label": { "content": "$1", @@ -4940,10 +4976,10 @@ } }, "updateEncryptionKeyWarning": { - "message": "Depois de atualizar sua chave de criptografia, é necessário encerrar e iniciar a sessão em todos os aplicativos do Bitwarden que você está usando atualmente (como o aplicativo móvel ou as extensões do navegador). Não encerrar e iniciar sessão (que baixa sua nova chave de criptografia) pode resultar em corrupção de dados. Nós tentaremos desconectá-lo automaticamente, mas isso pode demorar um pouco." + "message": "Depois de atualizar sua chave de criptografia, é necessário se desconectar e reconectar em todos os aplicativos do Bitwarden que você está usando atualmente (como o aplicativo móvel ou as extensões do navegador). Não fazer isso (que descarrega sua nova chave de criptografia) pode resultar em corrupção de dados. Nós tentaremos desconectá-lo automaticamente, mas isso pode demorar um pouco." }, "updateEncryptionKeyAccountExportWarning": { - "message": "Quaisquer exportações restritas pela conta que você tem salvas se tornarão inválidas." + "message": "Quaisquer exportações que você tem salvas que são restritas à conta se tornarão inválidas." }, "legacyEncryptionUnsupported": { "message": "A criptografia legada não é mais suportada. Entre em contato com o suporte para recuperar a sua conta." @@ -4955,7 +4991,7 @@ "message": "Carregando" }, "upgrade": { - "message": "Atualizar" + "message": "Fazer upgrade" }, "upgradeOrganization": { "message": "Fazer upgrade da organização" @@ -4973,7 +5009,7 @@ "message": "Reembolsado" }, "nothingSelected": { - "message": "Você selecionou nada." + "message": "Voce não selecionou nada." }, "receiveMarketingEmailsV2": { "message": "Receba conselhos, novidades, e oportunidades de pesquisa do Bitwarden em sua caixa de entrada." @@ -5009,13 +5045,13 @@ "message": "Filtros" }, "vaultTimeout": { - "message": "Tempo Limite do Cofre" + "message": "Tempo limite do cofre" }, "vaultTimeout1": { "message": "Tempo limite" }, "vaultTimeoutDesc": { - "message": "Escolha quando o tempo limite do seu cofre irá se esgotar e executar a ação selecionada." + "message": "Escolha quando o seu cofre executará a ação do tempo limite do cofre." }, "vaultTimeoutLogoutDesc": { "message": "Escolha quando seu cofre será desconectado." @@ -5039,7 +5075,7 @@ "message": "4 horas" }, "onRefresh": { - "message": "Ao recarregar o navegador" + "message": "No reinício do navegador" }, "dateUpdated": { "message": "Atualizado", @@ -5075,7 +5111,7 @@ "message": "Contas de serviço não podem ser criadas em organizações suspensas. Entre em contato com o proprietário da organização para obter assistência." }, "licenseIsExpired": { - "message": "A licença está expirada." + "message": "A licença está vencida." }, "updatedUsers": { "message": "Usuários atualizados" @@ -5090,7 +5126,7 @@ "message": "Propriedade" }, "whoOwnsThisItem": { - "message": "Quem possui este item?" + "message": "Quem é o proprietário deste item?" }, "strong": { "message": "Forte", @@ -5105,7 +5141,7 @@ "description": "ex. A weak password. Scale: Very Weak -> Weak -> Good -> Strong" }, "veryWeak": { - "message": "Muito Fraca", + "message": "Muito fraca", "description": "ex. A very weak password. Scale: Very Weak -> Weak -> Good -> Strong" }, "weakMasterPassword": { @@ -5115,13 +5151,13 @@ "message": "Senha fraca identificada. Você deve usar uma senha principal forte para proteger a sua conta. Tem certeza que quer usar esta senha principal?" }, "rotateAccountEncKey": { - "message": "Também rodar a chave de encriptação da minha conta" + "message": "Rotacionar a chave de criptografia da minha conta" }, "rotateEncKeyTitle": { "message": "Rotacionar chave de criptografia" }, "rotateEncKeyConfirmation": { - "message": "Você tem certeza que quer rodar a chave de encriptação da sua conta?" + "message": "Tem certeza que quer rotacionar a chave de criptografia da sua conta?" }, "attachmentsNeedFix": { "message": "Este item tem anexos de arquivos antigos que precisam ser corrigidos." @@ -5134,10 +5170,10 @@ "description": "This is a verb. ex. 'Fix The Car'" }, "oldAttachmentsNeedFixDesc": { - "message": "Há anexos de arquivos antigos no seu cofre que precisam ser corrigidos antes que você possa girar a chave de criptografia da sua conta." + "message": "Há anexos de arquivos antigos no seu cofre que precisam ser corrigidos antes que você possa rotacionar a chave de criptografia da sua conta." }, "yourAccountsFingerprint": { - "message": "A sua frase biométrica", + "message": "A frase biométrica da sua conta", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "fingerprintEnsureIntegrityVerify": { @@ -5145,7 +5181,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "fingerprintMatchInfo": { - "message": "Certifique-se de que o seu cofre esteja desbloqueado e a frase biométrica corresponda ao outro dispositivo." + "message": "Certifique-se de que o seu cofre está desbloqueado e a frase biométrica corresponda ao do outro dispositivo." }, "fingerprintPhraseHeader": { "message": "Frase biométrica" @@ -5158,42 +5194,42 @@ "message": "Você será notificado assim que a solicitação ser aprovada" }, "free": { - "message": "Gratuito", + "message": "Grátis", "description": "Free, as in 'Free beer'" }, "apiKey": { - "message": "Chave API" + "message": "Chave da API" }, "apiKeyDesc": { - "message": "Sua chave API pode ser usada para autenticar na API pública do Bitwarden." + "message": "Sua chave da API pode ser usada para autenticar na API pública do Bitwarden." }, "apiKeyRotateDesc": { - "message": "Girar a chave da API invalidará a chave anterior. Você pode girar sua chave de API se acreditar que a chave atual não é mais segura de usar." + "message": "Rotacionar a chave da API invalidará a chave anterior. Você pode rotacionar sua chave de API se acreditar que a chave atual não é mais segura de usar." }, "apiKeyWarning": { - "message": "Sua chave de API tem acesso total à organização. Deve ser mantido em segredo." + "message": "Sua chave de API tem acesso total à organização. Deve ser mantida em segredo." }, "userApiKeyDesc": { - "message": "Sua chave API pode ser usada para autenticar no Bitwarden CLI." + "message": "Sua chave da API pode ser usada para autenticar no Bitwarden CLI." }, "userApiKeyWarning": { - "message": "Sua chave de API é um mecanismo de autenticação alternativo. Deve ser mantido em segredo." + "message": "Sua chave de API é um mecanismo de autenticação alternativo. Deve ser mantida em segredo." }, "oauth2ClientCredentials": { "message": "Credenciais do cliente OAuth 2.0", "description": "'OAuth 2.0' is a programming protocol. It should probably not be translated." }, "viewApiKey": { - "message": "Visualizar chave da API" + "message": "Ver chave da API" }, "rotateApiKey": { "message": "Rotacionar chave da API" }, "selectOneCollection": { - "message": "Você deve selecionar pelo menos uma coleção." + "message": "Você deve selecionar pelo menos um conjunto." }, "couldNotChargeCardPayInvoice": { - "message": "Não foi possível cobrar no seu cartão. Por favor, veja e pague a fatura pendente listada abaixo." + "message": "Não foi possível cobrar o seu cartão. Veja e pague a fatura pendente listada abaixo." }, "minLength": { "message": "Comprimento mínimo" @@ -5202,10 +5238,10 @@ "message": "Clonar" }, "masterPassPolicyTitle": { - "message": "Requisitos de senha principal" + "message": "Requisitos da senha principal" }, "masterPassPolicyDesc": { - "message": "Defina os requisitos para a força da senha principal." + "message": "Configure os requisitos para a força da senha principal." }, "passwordStrengthScore": { "message": "Pontuação de força da senha $SCORE$", @@ -5220,7 +5256,7 @@ "message": "Exigir autenticação em duas etapas" }, "twoStepLoginPolicyDesc": { - "message": "Exigir que os usuários definam a autenticação de dois passos nas suas contas pessoais." + "message": "Exija que os membros configurem a autenticação de duas etapas." }, "twoStepLoginPolicyWarning": { "message": "Membros da organização que não são proprietários ou administradores e não têm a autenticação em duas etapas configurada em sua conta serão removidos da organização e receberão um e-mail notificando-os sobre a mudança." @@ -5229,7 +5265,7 @@ "message": "Você é um membro de uma organização que exige que a autenticação de dois passos seja ativada na sua conta de usuário. Se desativar todos os serviços de autenticação de dois passos, você será automaticamente removido dessas organizações." }, "passwordGeneratorPolicyDesc": { - "message": "Defina requisitos para o gerador de senhas." + "message": "Configure requisitos para o gerador de senhas." }, "masterPasswordPolicyInEffect": { "message": "Uma ou mais políticas da organização exigem que a sua senha principal cumpra aos seguintes requisitos:" @@ -5253,16 +5289,16 @@ } }, "policyInEffectUppercase": { - "message": "Contém um ou mais caracteres em maiúsculo" + "message": "Conter um ou mais caracteres em maiúsculo" }, "policyInEffectLowercase": { - "message": "Contém um ou mais caracteres em minúsculo" + "message": "Conter um ou mais caracteres em minúsculo" }, "policyInEffectNumbers": { - "message": "Contém um ou mais números" + "message": "Conter um ou mais números" }, "policyInEffectSpecial": { - "message": "Contém um ou mais dos seguintes caracteres especiais $CHARS$", + "message": "Conter um ou mais dos seguintes caracteres especiais $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -5301,28 +5337,28 @@ "description": "Noun: A special folder for holding deleted items that have not yet been permanently deleted" }, "searchTrash": { - "message": "Pesquisar na lixeira" + "message": "Buscar na lixeira" }, "permanentlyDelete": { - "message": "Apagar para sempre" + "message": "Apagar permanentemente" }, "permanentlyDeleteSelected": { - "message": "Apagar selecionados para sempre" + "message": "Apagar selecionados permanentemente" }, "permanentlyDeleteItem": { - "message": "Apagar item para sempre" + "message": "Apagar item permanentemente" }, "permanentlyDeleteItemConfirmation": { - "message": "Você tem certeza que deseja excluir permanentemente esse item?" + "message": "Você tem certeza que deseja apagar permanentemente esse item?" }, "permanentlyDeletedItem": { - "message": "Item apagado para sempre" + "message": "Item apagado permanentemente" }, "permanentlyDeletedItems": { - "message": "Itens apagados para sempre" + "message": "Itens apagados permanentemente" }, "permanentlyDeleteSelectedItemsDesc": { - "message": "Você selecionou $COUNT$ item(ns) para excluir permanentemente. Tem certeza que deseja excluir permanentemente todos estes itens?", + "message": "Você selecionou $COUNT$ item(ns) para apagar permanentemente. Tem certeza que quer apagar todos estes itens permanentemente?", "placeholders": { "count": { "content": "$1", @@ -5331,7 +5367,7 @@ } }, "permanentlyDeletedItemId": { - "message": "Item $ID$ apagado para sempre", + "message": "Item $ID$ apagado permanentemente", "placeholders": { "id": { "content": "$1", @@ -5361,7 +5397,7 @@ } }, "vaultTimeoutLogOutConfirmation": { - "message": "Sair irá remover todo o acesso ao seu cofre e requer autenticação online após o período de tempo limite. Tem certeza de que deseja usar esta configuração?" + "message": "Desconectar-se irá remover todo o acesso ao seu cofre e requirirá autenticação on-line após o período de tempo limite. Tem certeza de que deseja usar esta configuração?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Confirmação de ação do tempo limite" @@ -5376,7 +5412,7 @@ "message": "Incluir informações sobre IVA/GST (opcional)" }, "taxIdNumber": { - "message": "ID de Imposto VAT/GST" + "message": "ID de imposto VAT/GST" }, "taxInfoUpdated": { "message": "Informações de impostos atualizadas." @@ -5391,13 +5427,13 @@ "message": "Identificador da organização" }, "ssoLogInWithOrgIdentifier": { - "message": "Entre usando o portal de autenticação única da sua organização. Digite o identificador de SSO da sua organização para começar." + "message": "Conecte-se usando o portal de autenticação única da sua organização. Digite o identificador de SSO da sua organização para começar." }, "singleSignOnEnterOrgIdentifier": { "message": "Digite o identificador de SSO da sua organização para começar" }, "singleSignOnEnterOrgIdentifierText": { - "message": "Para entrar com seu provedor de SSO, digite o identificador de SSO da sua organização para começar. Talvez seja necessário digitar este identificador de SSO quando entrar com um novo dispositivo." + "message": "Para se conectar com seu provedor de SSO, digite o identificador de SSO da sua organização para começar. Talvez seja necessário digitar este identificador de SSO quando se conectar com um novo dispositivo." }, "enterpriseSingleSignOn": { "message": "Autenticação única empresarial" @@ -5406,7 +5442,7 @@ "message": "Agora você pode fechar esta aba e continuar na extensão." }, "youSuccessfullyLoggedIn": { - "message": "Você entrou na sua conta com sucesso" + "message": "Você se conectou com sucesso" }, "thisWindowWillCloseIn5Seconds": { "message": "Esta janela será fechada automaticamente em 5 segundos" @@ -5415,13 +5451,13 @@ "message": "Você pode fechar esta janela" }, "includeAllTeamsFeatures": { - "message": "Recursos para Todas as Equipes, além de:" + "message": "Todos os recursos do Equipes, mais:" }, "includeAllTeamsStarterFeatures": { - "message": "Todos os recursos do Teams Starter, mais:" + "message": "Todos os recursos do Equipes Iniciantes, mais:" }, "chooseMonthlyOrAnnualBilling": { - "message": "Escolha cobrança mensal ou anual" + "message": "Escolha faturamento mensal ou anual" }, "abilityToAddMoreThanNMembers": { "message": "Capacidade de adicionar mais que $COUNT$ membros", @@ -5474,7 +5510,7 @@ "message": "Restrinja membros de entrar em outras organizações. Essa política é necessária para organizações que tenham ativado a verificação de domínio." }, "singleOrgBlockCreateMessage": { - "message": "Sua organização atual tem uma política que não permite que você entre em mais de uma organização. Por favor, entre em contato com os administradores da sua organização ou cadastre-se a partir de uma conta do Bitwarden diferente." + "message": "Sua organização atual tem uma política que não permite que você entre em mais de uma organização. Entre em contato com os administradores da sua organização ou cadastre-se a partir de uma conta do Bitwarden diferente." }, "singleOrgPolicyMemberWarning": { "message": "Membros não adequados serão colocados no estado revogado até que saiam de todas as outras organizações. Administradores estão isentos e podem restaurar os membros assim que se adequarem." @@ -5483,7 +5519,7 @@ "message": "Exigir autenticação única" }, "requireSsoPolicyDesc": { - "message": "Exija que os usuários entrem com o método de autenticação única empresarial." + "message": "Exija que os usuários se conectem com o método de autenticação única empresarial." }, "prerequisite": { "message": "Pré-requisito" @@ -5528,7 +5564,7 @@ "message": "Texto" }, "sendPasswordDescV3": { - "message": "Adicione uma senha opcional para os destinatários acessarem este Send.", + "message": "Adicione uma senha opcional para que os destinatários acessem este Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { @@ -5552,25 +5588,25 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSend": { - "message": "Excluir Send", + "message": "Apagar Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { - "message": "Tem certeza de que quer apagar este Send para sempre?", + "message": "Tem certeza de que quer apagar este Send permanentemente?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Data de apagamento" }, "deletionDateDescV2": { - "message": "O Send será apagado para sempre nesta data.", + "message": "O Send será apagado permanentemente nesta data.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "Data de validade" }, "expirationDateDesc": { - "message": "Se configurado, o acesso a este Send expirará no horário especificado.", + "message": "Se configurado, o acesso a este Send acabará no horário especificado.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { @@ -5580,7 +5616,7 @@ "message": "Desativado" }, "revoked": { - "message": "Revogado" + "message": "Revogados" }, "sendLink": { "message": "Link do Send", @@ -5616,14 +5652,14 @@ "message": "Ocultar texto por padrão" }, "expired": { - "message": "Expirado" + "message": "Vencido" }, "searchSends": { - "message": "Pesquisar Sends", + "message": "Buscar nos Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendProtectedPassword": { - "message": "Este Send está protegido com uma senha. Por favor, digite a senha abaixo para continuar.", + "message": "Este Send está protegido com uma senha. Digite a senha abaixo para continuar.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendProtectedPasswordDontKnow": { @@ -5638,11 +5674,11 @@ "message": "Baixar anexos" }, "sendAccessPasswordTitle": { - "message": "Digite a senha para visualizar este Send", + "message": "Digite a senha para ver este Send", "description": "Title of the Send password authentication screen." }, "sendAccessContentTitle": { - "message": "Visualizar Send", + "message": "Ver Send", "description": "Title of the Send view content screen." }, "sendAccessUnavailable": { @@ -5650,7 +5686,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "missingSendFile": { - "message": "O arquivo associado a este Send não foi encontrado.", + "message": "O arquivo associado com este Send não foi encontrado.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "noSendsInList": { @@ -5661,10 +5697,10 @@ "message": "Acesso de emergência" }, "emergencyAccessDesc": { - "message": "Conceda e gerencie o acesso de emergência para contatos confiáveis. Contatos confiáveis podem solicitar acesso a visualizar ou assumir o controle da sua conta em caso de emergência. Visite a nossa página de ajuda para obter mais informações e detalhes sobre como o compartilhamento de conhecimento zero funciona." + "message": "Conceda e gerencie o acesso de emergência para contatos de confiança. Contatos de confiança podem solicitar acesso para ver ou assumir o controle da sua conta em caso de emergência. Visite a nossa página de ajuda para obter mais informações e detalhes sobre como o compartilhamento de conhecimento zero funciona." }, "emergencyAccessOwnerWarning": { - "message": "Você é o Proprietário de uma ou mais organizações. Se você conceder o assumir controle a um contato de emergência, eles serão capazes de usar todas as suas permissões como Proprietário ao assumir controle." + "message": "Você é o proprietário de uma ou mais organizações. Se você conceder o assumir controle a um contato de emergência, eles serão capazes de usar todas as suas permissões como proprietário ao assumir controle." }, "trustedEmergencyContacts": { "message": "Contatos de emergência confiados" @@ -5679,7 +5715,7 @@ "message": "Designado como contato de emergência" }, "noGrantedAccess": { - "message": "Você ainda não foi designado como um contato de emergência de ninguém." + "message": "Você ainda não foi designado como um contato de emergência para ninguém." }, "inviteEmergencyContact": { "message": "Convidar contato de emergência" @@ -5703,7 +5739,7 @@ "message": "Assumir controle" }, "takeoverDesc": { - "message": "Pode redefinir a sua conta com uma nova senha principal." + "message": "Pode reconfigurar a sua conta com uma nova senha principal." }, "waitTime": { "message": "Tempo de espera" @@ -5727,7 +5763,7 @@ "message": "Usuário convidado." }, "acceptEmergencyAccess": { - "message": "Você foi convidado para se tornar um contato de emergência para o usuário listado acima. Para aceitar o convite, você precisa entrar ou criar uma nova conta no Bitwarden." + "message": "Você foi convidado para se tornar um contato de emergência para o usuário listado acima. Para aceitar o convite, você precisa se conectar ou criar uma nova conta no Bitwarden." }, "emergencyInviteAcceptFailed": { "message": "Não é possível aceitar o convite. Peça ao usuário para enviar um novo convite." @@ -5748,7 +5784,7 @@ "message": "Solicitar acesso" }, "requestAccessConfirmation": { - "message": "Tem certeza que deseja solicitar acesso de emergência? Você receberá acesso após $WAITTIME$ dia(s) ou sempre que o usuário aprovar manualmente a solicitação.", + "message": "Tem certeza que deseja solicitar acesso de emergência? Você receberá acesso após $WAITTIME$ dia(s) ou se o usuário aprovar manualmente a solicitação.", "placeholders": { "waittime": { "content": "$1", @@ -5791,10 +5827,10 @@ "message": "Acesso de emergência recusado" }, "grantorDetailsNotFound": { - "message": "Detalhes do condecente não encontrados" + "message": "Detalhes do concedente não encontrados" }, "passwordResetFor": { - "message": "Senha redefinida para $USER$. Agora você pode acessar usando a nova senha.", + "message": "Senha reconfigurada para $USER$. Agora você pode se conectar usando a nova senha.", "placeholders": { "user": { "content": "$1", @@ -5806,15 +5842,15 @@ "message": "Aplicar propriedade de dados da organização" }, "organizationDataOwnershipDesc": { - "message": "Exigir que todos os itens sejam propriedade de uma organização, removendo a opção de armazenar itens ao nível da conta.", + "message": "Exija que todos os itens sejam propriedade de uma organização, removendo a opção de armazenar itens ao nível da conta.", "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "Todos os itens serão propriedade da e salvos na organização, ativando controles, visibilidade, e relatórios ao nível da organização. Quando ativado, um conjunto padrão ficará disponível para que cada membro armazene seus itens. Saiba mais sobre gerenciar o ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { - "message": "ciclo de vida credencial", + "message": "ciclo de vida de credenciais", "description": "This will be used as a hyperlink" }, "organizationDataOwnershipWarningTitle": { @@ -5827,85 +5863,85 @@ "message": "não será selecionado automaticamente ao criar itens" }, "organizationDataOwnershipWarning3": { - "message": "não pode ser gerenciado pelo Console de Admin até que o usuário seja removido" + "message": "não pode ser gerenciado pelo painel de administração até que o usuário seja removido" }, "organizationDataOwnershipWarningContentTop": { - "message": "Ao desligar esta política, a coleção padrão: " + "message": "Ao desativar esta política, o conjunto padrão: " }, "organizationDataOwnershipWarningContentBottom": { "message": "Saiba mais sobre o ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about the credential lifecycle.'" }, "availableNow": { - "message": "Available now" + "message": "Disponível agora" }, "autoConfirm": { - "message": "Automatic confirmation of new users" + "message": "Confirmação automática de novos usuários" }, "autoConfirmDescription": { - "message": "New users invited to the organization will be automatically confirmed when an admin’s device is unlocked.", + "message": "Novos usuários convidados para a organização serão confirmados automaticamente quando o dispositivo de um administrador for desbloqueado.", "description": "This is the description of the policy as it appears in the 'Policies' page" }, "howToTurnOnAutoConfirm": { - "message": "How to turn on automatic user confirmation" + "message": "Como ativar a confirmação automática de usuários" }, "autoConfirmExtension1": { - "message": "Open your Bitwarden extension" + "message": "Abra a sua extensão do Bitwarden" }, "autoConfirmExtension2": { - "message": "Select", + "message": "Selecione", "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtension3": { - "message": " Turn on", + "message": " Ativar", "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { - "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." + "message": "A extensão de navegador do Bitwarden foi aberta com sucesso. Agora você pode ativar a configuração de confirmação automática de usuários." }, "autoConfirmPolicyEditDescription": { - "message": "New users invited to the organization will be automatically confirmed when an admin’s device is unlocked. Before turning on this policy, please review and agree to the following: ", + "message": "Novos usuários convidados para a organização serão confirmados automaticamente quando o dispositivo de um administrador for desbloqueado. Antes de ativar esta política, revisa e concorde com o seguinte: ", "description": "This is the description of the policy as it appears inside the policy edit dialog" }, "autoConfirmAcceptSecurityRiskTitle": { - "message": "Potential security risk. " + "message": "Potencial risco de segurança. " }, "autoConfirmAcceptSecurityRiskDescription": { - "message": "Automatic user confirmation could pose a security risk to your organization’s data." + "message": "A confirmação automática de usuários pode representar um risco para a segurança dos dados da sua organização." }, "autoConfirmAcceptSecurityRiskLearnMore": { - "message": "Learn about the risks", + "message": "Saiba mais sobre os riscos", "description": "The is the link copy for the first check box option in the edit policy dialog" }, "autoConfirmSingleOrgRequired": { - "message": "Single organization policy required. " + "message": "Política de organização única necessária. " }, "autoConfirmSingleOrgRequiredDesc": { - "message": "All members must only belong to this organization to activate this automation." + "message": "Todos os membros devem pertencer somente a esta organização para ativar esta automação." }, "autoConfirmSingleOrgExemption": { - "message": "Single organization policy will extend to all roles. " + "message": "A política de organização única estenderá a todos os cargos. " }, "autoConfirmNoEmergencyAccess": { - "message": "No emergency access. " + "message": "Sem acesso de emergência. " }, "autoConfirmNoEmergencyAccessDescription": { - "message": "Emergency Access will be removed." + "message": "O acesso de emergência será removido." }, "autoConfirmCheckBoxLabel": { - "message": "I accept these risks and policy updates" + "message": "Eu aceito estes riscos e atualizações de política" }, "personalOwnership": { "message": "Remover cofre individual" }, "personalOwnershipPolicyDesc": { - "message": "Exigir que os usuários salvem itens em uma organização removendo a opção de cofre individual." + "message": "Exija que os usuários salvem itens em uma organização removendo a opção de cofre individual." }, "personalOwnershipExemption": { "message": "Os proprietários e administradores da organização são isentos da aplicação desta política." }, "personalOwnershipSubmitError": { - "message": "Devido a uma política empresarial, você não pode salvar itens no seu cofre pessoal. Altere a opção de propriedade para uma organização e escolha entre as coleções disponíveis." + "message": "Devido a uma política empresarial, você não pode salvar itens no seu cofre individual. Altere a opção de propriedade para uma organização e escolha entre os conjuntos disponíveis." }, "desktopAutotypePolicy": { "message": "Configuração padrão da digitação automática no computador" @@ -5918,18 +5954,18 @@ "message": "Remover Send" }, "disableSendPolicyDesc": { - "message": "Não permitir que usuários criem ou editem Sends.", + "message": "Não permita que usuários criem ou editem Sends.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disableSendExemption": { - "message": "Os usuários da organização que podem gerenciar as políticas da organização estão isentos da aplicação desta política." + "message": "Os usuários da organização que podem gerenciar as políticas da organização são isentos da aplicação desta política." }, "sendDisabled": { "message": "Send removido", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Devido a uma política empresarial, você só é capaz de apagar um Send existente.", + "message": "Devido a uma política corporativa, você só pode apagar um Send existente.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptions": { @@ -5941,23 +5977,23 @@ "description": "'Sends' is a plural noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsExemption": { - "message": "Os usuários da organização que podem gerenciar as políticas da organização estão isentos da aplicação desta política." + "message": "Os usuários da organização que podem gerenciar as políticas da organização são isentos da aplicação desta política." }, "disableHideEmail": { "message": "Sempre mostrar o endereço de e-mail do membro com destinatários ao criar ou editar um Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "uriMatchDetectionPolicy": { - "message": "Default URI match detection" + "message": "Detecção de correspondência padrão de URI" }, "uriMatchDetectionPolicyDesc": { - "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + "message": "Determine quando as credenciais são sugeridas para preenchimento automático. Administradores e proprietários ficam isentos desta política." }, "uriMatchDetectionOptionsLabel": { - "message": "Default URI match detection" + "message": "Detecção de correspondência padrão de URI" }, "invalidUriMatchDefaultPolicySetting": { - "message": "Please select a valid URI match detection option.", + "message": "Selecione uma opção de detecção de correspondência de URI válida.", "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." }, "modifiedPolicyId": { @@ -5979,7 +6015,7 @@ "message": "Personalizado" }, "customDesc": { - "message": "Conceder permissões personalizadas aos membros" + "message": "Conceda permissões personalizadas aos membros" }, "customDescNonEnterpriseStart": { "message": "Cargos personalizados são um ", @@ -5994,7 +6030,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customNonEnterpriseError": { - "message": "Para ativar as permissões personalizadas, a organização deve estar em um plano Enterprise 2020." + "message": "Para ativar as permissões personalizadas, a organização deve estar em um plano Empresarial 2020." }, "permissions": { "message": "Permissões" @@ -6015,16 +6051,16 @@ "message": "Você não tem a permissão necessária para executar esta ação." }, "manageAllCollections": { - "message": "Gerenciar todas as coleções" + "message": "Gerenciar todos os conjuntos" }, "createNewCollections": { - "message": "Criar novas coleções" + "message": "Criar novos conjuntos" }, "editAnyCollection": { - "message": "Editar qualquer coleção" + "message": "Editar qualquer conjunto" }, "deleteAnyCollection": { - "message": "Apagar qualquer coleção" + "message": "Apagar qualquer conjunto" }, "manageGroups": { "message": "Gerenciar grupos" @@ -6042,7 +6078,7 @@ "message": "Gerenciar recuperação de conta" }, "disableRequiredError": { - "message": "Você deve desativar manualmente a política $POLICYNAME$ antes que esta política possa ser desativada.", + "message": "Você deve desativar manualmente a política de $POLICYNAME$ antes que esta política possa ser desativada.", "placeholders": { "policyName": { "content": "$1", @@ -6054,10 +6090,10 @@ "message": "Uma política de organização está afetando suas opções de propriedade." }, "personalOwnershipPolicyInEffectImports": { - "message": "Uma política da organização bloqueou a importação de itens em seu cofre pessoal." + "message": "Uma política da organização bloqueou a importação de itens em seu cofre individual." }, "personalOwnershipCheckboxDesc": { - "message": "Remover propriedade individual para usuários da organização" + "message": "Remova a propriedade individual para usuários da organização" }, "send": { "message": "Send", @@ -6091,19 +6127,19 @@ "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": "Desenvolvimento, DevOps e equipes de TI escolhem o Gerenciador de Segredos do Bitwarden para gerenciar e implementar seus segredos de infraestrutura e máquina de forma segura." + "message": "Equipes de desenvolvimento, DevOps e TI escolhem o Bitwarden Gerenciador de Segredos para gerenciar e implementar seus segredos de infraestrutura e máquina de forma segura." }, "centralizeSecretsManagement": { "message": "Centralize o gerenciamento de segredos." }, "centralizeSecretsManagementDescription": { - "message": "Armazene e gerencie com segurança segredos em um único local para impedir dispersão de segredos em sua organização." + "message": "Armazene e gerencie segredos com segurança em um único local para impedir dispersão de segredos em sua organização." }, "preventSecretLeaks": { "message": "Impeça vazamentos de segredos." }, "preventSecretLeaksDescription": { - "message": "Proteja segredos com criptografia de ponta a ponta. Não há mais segredos de codificação rígida ou compartilhamento através de arquivos .env." + "message": "Proteja segredos com criptografia de ponta a ponta. Nada mais de segredos hard-coded ou compartilhamento através de arquivos .env." }, "enhanceDeveloperProductivity": { "message": "Melhore a produtividade dos desenvolvedores." @@ -6112,7 +6148,7 @@ "message": "Recupere e implemente segredos programaticamente em tempo de execução para que os desenvolvedores possam se concentrar no que mais importa, como melhorar a qualidade do código." }, "strengthenBusinessSecurity": { - "message": "Fortaleça a segurança empresarial." + "message": "Fortaleça a segurança da empresa." }, "strengthenBusinessSecurityDescription": { "message": "Mantenha controle rígido sobre o acesso de máquina e humano a segredos com integrações de SSO, registros de eventos e rotação de acesso." @@ -6127,7 +6163,7 @@ "message": "Adicione uma anotação" }, "bitwardenSecretsManager": { - "message": "Gerenciador de Segredos do Bitwarden" + "message": "Bitwarden Gerenciador de Segredos" }, "moreProductsFromBitwarden": { "message": "Mais produtos do Bitwarden" @@ -6136,19 +6172,19 @@ "message": "Solicitar acesso ao Gerenciador de Segredos" }, "youNeedApprovalFromYourAdminToTrySecretsManager": { - "message": "Você precisa de aprovação do seu administrador para testar o Gerenciador de Segredos." + "message": "Você precisa de aprovação do seu administrador para experimentar o Gerenciador de Segredos." }, "smAccessRequestEmailSent": { "message": "Solicitação de acesso para o gerenciador de segredos enviada ao e-mail dos administradores." }, "requestAccessSMDefaultEmailContent": { - "message": "Olá,\n\nEstou solicitando uma assinatura do Gerenciador de Segredos do Bitwarden para a nossa equipe. O vosso apoio significaria muito!\n\nO Gerenciador de Segredos do Bitwarden é uma solução de gerenciamento de segredos criptografados de ponta a ponta para armazenamento seguro, compartilhamento, e implementação de credenciais de máquina como chaves de API, senhas de banco de dados e certificados de autenticação.\n\nEle nos ajudará a:\n\n- Melhorar a segurança\n- Otimizar o fluxo\n- Impedir caros vazamentos de segredos\n\nPara solicitar uma avaliação gratuita para nossa equipe, entre em contato com o Bitwarden.\n\nObrigado pela sua ajuda!" + "message": "Olá,\n\nEstou solicitando uma assinatura do Bitwarden Gerenciador de Segredos para a nossa equipe. O vosso apoio significaria muito!\n\nO Gerenciador de Segredos do Bitwarden é uma solução de gerenciamento de segredos criptografados de ponta a ponta para armazenamento seguro, compartilhamento, e implementação de credenciais de máquina como chaves de API, senhas de banco de dados e certificados de autenticação.\n\nEle nos ajudará a:\n\n- Melhorar a segurança\n- Otimizar o fluxo\n- Impedir caros vazamentos de segredos\n\nPara solicitar uma avaliação gratuita para nossa equipe, por favor, entre em contato com o Bitwarden.\n\nObrigado pela sua ajuda!" }, "giveMembersAccess": { - "message": "Dar acesso aos membros:" + "message": "Dê acesso aos membros:" }, "viewAndSelectTheMembers": { - "message": "visualize e selecione os membros que você deseja dar acesso ao Gerenciador de Segredos." + "message": "veja e selecione os membros que você deseja dar acesso ao Gerenciador de Segredos." }, "openYourOrganizations": { "message": "Abra o da sua organização" @@ -6199,7 +6235,7 @@ "message": "A data de apagamento fornecida não é válida." }, "expirationDateAndTimeRequired": { - "message": "Uma data e hora de expiração são obrigatórias." + "message": "Uma data e hora de validade são obrigatórias." }, "deletionDateAndTimeRequired": { "message": "Uma data e hora de apagamento são obrigatórias." @@ -6208,10 +6244,10 @@ "message": "Ocorreu um erro ao salvar as suas datas de apagamento e validade." }, "hideYourEmail": { - "message": "Oculte seu endereço de e-mail dos visualizadores." + "message": "Ocultar seu endereço de e-mail dos visualizadores." }, "webAuthnFallbackMsg": { - "message": "Para verificar seu 2FA, clique no botão abaixo." + "message": "Para verificar sua 2FA, clique no botão abaixo." }, "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" @@ -6232,7 +6268,7 @@ "message": "Sua senha nova não pode ser a mesma que a sua atual." }, "hintEqualsPassword": { - "message": "Sua dica de senha não pode ser a mesma que a sua senha." + "message": "A dica da sua senha não pode ser a mesma coisa que sua senha." }, "enrollAccountRecovery": { "message": "Inscrever-se na recuperação de conta" @@ -6247,7 +6283,7 @@ "message": "Inscrito com sucesso!" }, "withdrawPasswordResetSuccess": { - "message": "Retirou-se com sucesso!" + "message": "Retirada com sucesso!" }, "eventEnrollAccountRecovery": { "message": "O usuário $ID$ se inscreveu na recuperação de conta.", @@ -6268,7 +6304,7 @@ } }, "eventAdminPasswordReset": { - "message": "Senha principal do usuário $ID$ redefinida.", + "message": "Senha principal do usuário $ID$ reconfigurada.", "placeholders": { "id": { "content": "$1", @@ -6277,7 +6313,7 @@ } }, "eventResetSsoLink": { - "message": "Link de SSO redefinido para o usuário $ID$", + "message": "Reconfigurou o link do SSO para o usuário $ID$", "placeholders": { "id": { "content": "$1", @@ -6295,10 +6331,10 @@ } }, "resetPassword": { - "message": "Redefinir senha" + "message": "Reconfigurar senha" }, "resetPasswordLoggedOutWarning": { - "message": "O processo desconectará $NAME$ de sua sessão atual, obrigando que eles entrem novamente. As sessões ativas em outros dispositivos podem continuar ativas por até uma hora.", + "message": "O processo desconectará $NAME$ de sua sessão atual, obrigando que eles se conectem novamente. As sessões ativas em outros dispositivos podem continuar ativas por até uma hora.", "placeholders": { "name": { "content": "$1", @@ -6307,7 +6343,7 @@ } }, "emergencyAccessLoggedOutWarning": { - "message": "O processo desconectará $NAME$ de sua sessão atual, obrigando que eles entrem novamente. As sessões ativas em outros dispositivos podem continuar ativas por até uma hora.", + "message": "O processo desconectará $NAME$ de sua sessão atual, obrigando que eles se conectem novamente. As sessões ativas em outros dispositivos podem continuar ativas por até uma hora.", "placeholders": { "name": { "content": "$1", @@ -6325,55 +6361,55 @@ "message": "Uma ou mais políticas da organização exigem que a senha principal cumpra aos seguintes requisitos:" }, "resetPasswordSuccess": { - "message": "Senha redefinida com sucesso!" + "message": "Senha reconfigurada com sucesso!" }, "resetPasswordEnrollmentWarning": { - "message": "A inscrição permitirá que os administradores da organização alterem sua senha principal. Tem certeza que deseja se inscrever?" + "message": "A inscrição permitirá que os administradores da organização alterem sua senha principal" }, "accountRecoveryPolicy": { - "message": "Gerenciar recuperação de conta" + "message": "Gerenciamento da recuperação de conta" }, "accountRecoveryPolicyDesc": { - "message": "Baseado no método de criptografia, recupere contas quando senhas principais ou dispositivos confiáveis forem esquecidos ou perdidos." + "message": "Baseado no método de criptografia, recupere contas quando senhas principais ou dispositivos confiados forem esquecidos ou perdidos." }, "accountRecoveryPolicyWarning": { - "message": "Contas existentes com senhas principais exigirá que os membros se inscrevam antes que os administradores possam recuperar suas contas. A inscrição automática irá ativar a recuperação de conta para novos membros." + "message": "Contas existentes com senhas principais exigirão que os membros se inscrevam si mesmos antes que os administradores possam recuperar suas contas. A inscrição automática irá ativar a recuperação de conta para novos membros." }, "accountRecoverySingleOrgRequirementDesc": { "message": "A política empresarial de organização única deve ser ativada antes de ativar esta política." }, "resetPasswordPolicyAutoEnroll": { - "message": "Inscrição Automática" + "message": "Inscrição automática" }, "resetPasswordPolicyAutoEnrollCheckbox": { - "message": "Inscrever automaticamente novos usuários" + "message": "Exigir que novos membros sejam inscritos automaticamente" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha principal." + "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na reconfiguração de senha. A inscrição permitirá que os administradores da organização alterem sua senha principal." }, "resetPasswordOrgKeysError": { - "message": "A resposta das Chaves da Organização é nula" + "message": "A resposta das chaves da organização é nula" }, "resetPasswordDetailsError": { - "message": "A resposta para Redefinir os Detalhes da Senha é nula" + "message": "A resposta para os detalhes de reconfiguração de senha é nula" }, "trashCleanupWarning": { - "message": "As cifras que ficarem na lixeira por mais de 30 dias serão excluídas automaticamente." + "message": "Items que estão na lixeira por mais de 30 dias serão apagados automaticamente." }, "trashCleanupWarningSelfHosted": { - "message": "As cifras que estiverem na Lixeira por um tempo serão excluídas automaticamente." + "message": "Itens que ficarem na lixeira por um tempo serão apagados automaticamente." }, "passwordPrompt": { - "message": "Nova solicitação de senha principal" + "message": "Resolicitar senha principal" }, "passwordConfirmation": { "message": "Confirmação de senha principal" }, "passwordConfirmationDesc": { - "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha principal para verificar sua identidade." + "message": "Esta ação está protegida. Para continuar, digite a sua senha principal novamente para verificar sua identidade." }, "reinviteSelected": { - "message": "Reenviar Convites" + "message": "Reenviar convites" }, "resendNotification": { "message": "Reenviar notificação" @@ -6385,7 +6421,7 @@ "message": "Tem certeza de que deseja remover os seguintes usuários? O processo pode levar alguns segundos para ser concluído e não pode ser interrompido ou cancelado." }, "removeOrgUsersConfirmation": { - "message": "Quando membro(s) são removidos, eles não têm acesso aos dados da organização e esta ação é irreversível. Para adicionar o membro de volta à organização, ele deve ser convidado e integrado novamente. O processo pode demorar alguns segundos para ser concluído e não pode ser interrompido ou cancelado." + "message": "Quando membro(s) são removidos, eles não têm mais acesso aos dados da organização e esta ação é irreversível. Para adicionar o membro de volta à organização, ele deve ser convidado e integrado novamente. O processo pode demorar alguns segundos para ser concluído e não pode ser interrompido ou cancelado." }, "revokeUsersWarning": { "message": "Quando membro(s) são revogados, eles não têm mais acesso aos dados da organização. Para restaurar o acesso rapidamente, vá para a aba Revogados. O processo pode demorar alguns segundos para ser concluído e não pode ser interrompido ou cancelado." @@ -6397,7 +6433,7 @@ "message": "Escolha um tema para o seu cofre web." }, "themeSystem": { - "message": "Usar Tema do Sistema" + "message": "Usar tema do sistema" }, "themeDark": { "message": "Escuro" @@ -6406,16 +6442,16 @@ "message": "Claro" }, "confirmSelected": { - "message": "Confirmar Selecionado(s)" + "message": "Confirmar selecionados" }, "bulkConfirmStatus": { - "message": "Status de ação em massa" + "message": "Estado de ação em massa" }, "bulkConfirmMessage": { - "message": "Confirmado com sucesso." + "message": "Confirmado com sucesso" }, "bulkReinviteMessage": { - "message": "Convidado novamente com sucesso." + "message": "Re-convidado com sucesso" }, "bulkRemovedMessage": { "message": "Removido com sucesso" @@ -6427,13 +6463,13 @@ "message": "Acesso à organização restaurado com sucesso" }, "bulkFilteredMessage": { - "message": "Excluído, não aplicável para esta ação." + "message": "Excluído, não aplicável para esta ação" }, "nonCompliantMembersTitle": { - "message": "Membros não compatíveis" + "message": "Membros não adequados" }, "nonCompliantMembersError": { - "message": "Os membros que não estão em conformidade com a política de “login” único ou de duas etapas não poderão ser restaurados até cumprirem os requisitos da política" + "message": "Os membros que não estão em conformidade com a política de organização única ou de duas etapas não poderão ser restaurados até cumprirem os requisitos da política" }, "fingerprint": { "message": "Impressão digital" @@ -6445,36 +6481,36 @@ "message": "Erro" }, "decryptionError": { - "message": "Erro ao descriptografar" + "message": "Erro de descriptografia" }, "couldNotDecryptVaultItemsBelow": { "message": "O Bitwarden não pôde descriptografar o(s) item(ns) do cofre listado abaixo." }, "contactCSToAvoidDataLossPart1": { - "message": "Contato com o cliente feito com sucesso", + "message": "Contate o costumer success", "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 adicional dos dados.", + "message": "para evitar a perca de dados adicionais.", "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" }, "setupProvider": { - "message": "Configuração do Provedor" + "message": "Configuração do provedor" }, "setupProviderLoginDesc": { - "message": "Você foi convidado para configurar um novo provedor. Para continuar, você precisa iniciar sessão ou criar uma nova conta no Bitwarden." + "message": "Você foi convidado para configurar um novo provedor. Para continuar, você precisa se conectar ou criar uma nova conta no Bitwarden." }, "setupProviderDesc": { - "message": "Por favor, insira os detalhes abaixo para completar a configuração do provedor. Entre em Contato com o Suporte ao Cliente se tiver alguma dúvida." + "message": "Digite os detalhes abaixo para completar a configuração do provedor. Entre em contato com o suporte ao cliente se tiver alguma dúvida." }, "providerName": { - "message": "Nome do Provedor" + "message": "Nome do provedor" }, "providerSetup": { - "message": "O provedor foi configurado." + "message": "Provedor configurado com sucesso" }, "clients": { "message": "Clientes" @@ -6484,13 +6520,13 @@ "description": "This is used as a table header to describe which client application created an event log." }, "providerAdmin": { - "message": "Administrador do Provedor" + "message": "Administrador do provedor" }, "providerAdminDesc": { "message": "O usuário de maior acesso que pode gerenciar todos os aspectos do seu provedor, bem como acessar e gerenciar organizações de clientes." }, "serviceUser": { - "message": "Usuário de Serviço" + "message": "Usuário do serviço" }, "serviceUserDesc": { "message": "Os usuários do serviço podem acessar e gerenciar todas as organizações de clientes." @@ -6499,10 +6535,10 @@ "message": "Convide um novo usuário para seu provedor digitando o endereço de e-mail da conta Bitwarden dele abaixo. Se ele não tiver uma conta no Bitwarden, ele será solicitado a criar uma nova conta." }, "joinProvider": { - "message": "Participar do Provedor" + "message": "Juntar-se ao provedor" }, "joinProviderDesc": { - "message": "Você foi convidado para participar do provedor listado acima. Para aceitar o convite, você precisa iniciar sessão ou criar uma nova conta no Bitwarden." + "message": "Você foi convidado para participar do provedor listado acima. Para aceitar o convite, você precisa se conectar ou criar uma nova conta no Bitwarden." }, "providerInviteAcceptFailed": { "message": "Não foi possível aceitar o convite. Peça ao administrador do provedor para enviar um novo convite." @@ -6517,25 +6553,25 @@ "message": "Provedor" }, "newClientOrganization": { - "message": "Nova Organização de Cliente" + "message": "Nova organização de cliente" }, "newClientOrganizationDesc": { - "message": "Crie uma nova organização de cliente que será associada a você como o provedor. Você poderá acessar e gerenciar esta organização." + "message": "Crie uma organização de cliente que será associada a você como o provedor. Você poderá acessar e gerenciar esta organização." }, "newClient": { "message": "Novo cliente" }, "addExistingOrganization": { - "message": "Adicionar Organização Existente" + "message": "Adicionar organização existente" }, "addNewOrganization": { "message": "Adicionar nova organização" }, "myProvider": { - "message": "Meu Provedor" + "message": "Meu provedor" }, "addOrganizationConfirmation": { - "message": "Tem certeza de que deseja adicionar $ORGANIZATION$ como um cliente a $PROVIDER$?", + "message": "Tem certeza de que deseja adicionar $ORGANIZATION$ como um cliente de $PROVIDER$?", "placeholders": { "organization": { "content": "$1", @@ -6560,10 +6596,10 @@ } }, "providerIsDisabled": { - "message": "O provedor está desativado." + "message": "Provedor suspenso" }, "providerUpdated": { - "message": "Provedor atualizado" + "message": "Provedor salvo" }, "yourProviderIs": { "message": "Seu provedor é $PROVIDER$. Eles têm privilégios administrativos e de cobrança para sua organização.", @@ -6590,70 +6626,70 @@ "message": "Adicionar" }, "masterPasswordSuccessfullySet": { - "message": "Master password successfully set" + "message": "Senha principal configurada com sucesso" }, "updatedMasterPassword": { - "message": "Senha Mestra Atualizada" + "message": "Senha principal salva" }, "updateMasterPassword": { - "message": "Atualizar Senha Mestra" + "message": "Atualizar senha principal" }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Change your master password to complete account recovery." + "message": "Altere a sua senha principal para concluir a recuperação da conta." }, "updateMasterPasswordSubtitle": { - "message": "Your master password does not meet this organization’s requirements. Change your master password to continue." + "message": "Sua senha principal não corresponde aos requisitos dessa organização. Altere a sua senha principal para continuar." }, "updateMasterPasswordWarning": { - "message": "Sua Senha Mestra foi alterada recentemente por um administrador em sua organização. Para acessar o cofre, você deve atualizar sua Senha Mestra agora. Prosseguir irá desconectá-lo da sessão atual, exigindo que você faça login novamente. As sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "Sua senha principal foi alterada recentemente por um administrador na sua organização. Para acessar o cofre, você deve atualizar sua senha principal agora. Prosseguir irá desconectá-lo da sessão atual, exigindo que você se conecte novamente. As sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "masterPasswordInvalidWarning": { - "message": "Sua Senha Mestra foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizar sua Senha Mestra agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "A sua senha principal não atende aos requisitos da política desta organização. Para poder se juntar à organização, você precisa atualizar sua senha principal agora. O processo te desconectará da sua sessão atual, exigindo que você se conecte novamente. Sessões ativas em outros dispositivos podem ficar ativas por até uma hora." }, "updateWeakMasterPasswordWarning": { - "message": "A sua senha principal não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha principal agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "A sua senha principal não atende aos requisitos da política desta organização. Para poder acessar o cofre, você precisa atualizar sua senha principal agora. O processo te desconectará da sua sessão atual, exigindo que você se conecte novamente. Sessões ativas em outros dispositivos podem ficar ativas por até uma hora." }, "automaticAppLoginWithSSO": { - "message": "Automatic login with SSO" + "message": "Autenticação automática com SSO" }, "automaticAppLoginWithSSODesc": { - "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." + "message": "Estenda a segurança e conveniência do SSO para aplicativos não gerenciados. Quando usuários abrir um aplicativo pelo seu provedor de identidade, os detalhes de autenticação serão preenchidos e enviados automaticamente, criando um fluxo seguro e de um só clique do provedor de identidade ao aplicativo." }, "automaticAppLoginIdpHostLabel": { - "message": "Provedor de identidade" + "message": "Servidor do provedor de identidade" }, "automaticAppLoginIdpHostDesc": { - "message": "Digite a URL do seu provedor de identidade. Insira vários URLs, separando com uma vírgula." + "message": "Digite o URL do seu provedor de identidade. Digite vários URLs separando com uma vírgula." }, "tdeDisabledMasterPasswordRequired": { - "message": "Sua organização atualizou suas opções de descriptografia. Por favor, defina uma senha principal para acessar seu cofre." + "message": "Sua organização atualizou suas opções de descriptografia. Configure uma senha principal para acessar seu cofre." }, "sessionTimeoutPolicyTitle": { - "message": "Session timeout" + "message": "Tempo limite da sessão" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "Configure um tempo limite máximo para a sessão de todos os membros, exceto os proprietários." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "Tempo limite máximo permitido" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "Tempo limite máximo permitido é necessário." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "Tempo é inválido. Altere pelo menos um valor." }, "sessionTimeoutAction": { - "message": "Session timeout action" + "message": "Ação do tempo limite da sessão" }, "immediately": { - "message": "Immediately" + "message": "Imediatamente" }, "onSystemLock": { - "message": "On system lock" + "message": "No bloqueio do sistema" }, "onAppRestart": { - "message": "On app restart" + "message": "No reinício do aplicativo" }, "hours": { "message": "Horas" @@ -6662,22 +6698,22 @@ "message": "Minutos" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Você tem certeza de que deseja permitir um tempo limite máximo de \"Nunca\" para todos os membros?" }, "sessionTimeoutConfirmationNeverDescription": { - "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." + "message": "Esta opção salvará as chaves criptográficas dos seus membros em seus dispositivos. Se escolher esta opção, certifique-se de que seus dispositivos estão adequadamente protegidos." }, "learnMoreAboutDeviceProtection": { - "message": "Learn more about device protection" + "message": "Saiba mais sobre a proteção de dispositivos" }, "sessionTimeoutConfirmationOnSystemLockTitle": { - "message": "\"System lock\" will only apply to the browser and desktop app" + "message": "\"Bloqueio de sistema\" só será aplicado ao navegador e aplicativo de computador" }, "sessionTimeoutConfirmationOnSystemLockDescription": { - "message": "The mobile and web app will use \"on app restart\" as their maximum allowed timeout, since the option is not supported." + "message": "O aplicativo móvel e web usarão \"no reinício do aplicativo\" como tempo máximo permitido, já que a opção não é suportada." }, "vaultTimeoutPolicyInEffect": { - "message": "As políticas da sua organização estão afetando o tempo limite do seu cofre. O Tempo Limite Máximo permitido do Cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s)", + "message": "As políticas da sua organização configuraram o seu máximo permitido do tempo limite do cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", "placeholders": { "hours": { "content": "$1", @@ -6703,7 +6739,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "As políticas da sua organização estão afetando o tempo limite do seu cofre. O Tempo Limite Máximo permitido do Cofre é {0} hora(s) e {1} minuto(s). Seu tempo limite do cofre está definido em {2}.", + "message": "As políticas da sua organização estão afetando o tempo limite do seu cofre. \nO tempo limite máximo permitido para o cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de tempo limite do seu cofre está configurada como $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -6720,7 +6756,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "As políticas da sua organização definiram a ação tempo limite do seu cofre para $ACTION$.", + "message": "As políticas da sua organização configuraram a ação do tempo limite do seu cofre para $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -6729,205 +6765,205 @@ } }, "vaultTimeoutToLarge": { - "message": "O tempo limite do seu cofre excede a restrição definida por sua organização." + "message": "O tempo limite do seu cofre excede as restrições estabelecidas pela sua organização." }, "vaultCustomTimeoutMinimum": { - "message": "Tempo limite mínimo personalizado é 1 minuto." + "message": "O mínimo do tempo limite personalizado é de 1 minuto." }, "vaultTimeoutRangeError": { "message": "Tempo limite do cofre não está dentro do intervalo permitido." }, "disablePersonalVaultExport": { - "message": "Desabilitar Exportação de Cofre Pessoal" + "message": "Remover exportação do cofre individual" }, "disablePersonalVaultExportDescription": { "message": "Não permita que os membros exportem dados do seu cofre individual." }, "vaultExportDisabled": { - "message": "Exportação de Cofre Desabilitada" + "message": "Exportação de cofre removida" }, "personalVaultExportPolicyInEffect": { - "message": "Uma ou mais políticas da organização impdem que você exporte seu cofre pessoal." + "message": "Uma ou mais políticas da organização impedem que você exporte seu cofre individual." }, "activateAutofill": { "message": "Ativar preenchimento automático" }, "activateAutofillPolicyDesc": { - "message": "Ative a configuração de preenchimento automático ao carregamento de página na extensão do navegador para todos os membros existentes e novos." + "message": "Ative a configuração de preenchimento automático no carregamento da página na extensão do navegador para todos os membros existentes e novos." }, "experimentalFeature": { - "message": "Sites comprometidos ou não confiáveis podem tomar vantagem do autopreenchimento ao carregar a página." + "message": "Sites comprometidos ou não confiáveis podem tomar vantagem do preenchimento automático ao carregar a página." }, "learnMoreAboutAutofill": { "message": "Saiba mais sobre o preenchimento automático" }, "selectType": { - "message": "Selecionar Tipo de SSO" + "message": "Selecionar tipo de SSO" }, "type": { "message": "Tipo" }, "openIdConnectConfig": { - "message": "Configuração OpenID Connect" + "message": "Configuração do OpenID Connect" }, "samlSpConfig": { - "message": "Configuração do Provedor de Serviço SAML" + "message": "Configuração do provedor de serviço SAML" }, "samlIdpConfig": { - "message": "Configuração do Provedor de Identidade SAML" + "message": "Configuração do provedor de identidade SAML" }, "callbackPath": { - "message": "Caminho de Retorno" + "message": "Caminho de callback" }, "signedOutCallbackPath": { - "message": "Caminho de Retorno de Chamada Desconectado" + "message": "Caminho de callback desconectado" }, "authority": { "message": "Autoridade" }, "clientId": { - "message": "ID do Cliente" + "message": "ID do cliente" }, "clientSecret": { - "message": "Segredo do Cliente" + "message": "Segredo do cliente" }, "metadataAddress": { - "message": "Endereço dos Metadados" + "message": "Endereço dos metadados" }, "oidcRedirectBehavior": { - "message": "Comportamento de Redirecionamento OIDC" + "message": "Comportamento de redirecionamento OIDC" }, "getClaimsFromUserInfoEndpoint": { - "message": "Obter Reivindicações do Terminal de Informações do Usuário" + "message": "Obter reivindicações do link de informações de usuário" }, "additionalScopes": { - "message": "Escopos Adicionais/Personalizados (delimitado por vírgulas)" + "message": "Escopos personalizados" }, "additionalUserIdClaimTypes": { - "message": "Tipos de Reivindicação de ID de Usuário Adicionais/Personalizados (delimitado por vírgulas)" + "message": "Tipos de reivindicação de ID de usuário personalizado" }, "additionalEmailClaimTypes": { - "message": "Tipos de Reivindicação de E-mail Adicionais/Personalizados (delimitado por vírgulas)" + "message": "Tipos de reivindicação de e-mail" }, "additionalNameClaimTypes": { - "message": "Tipos de Reivindicação de Nome Adicionais/Personalizados (delimitado por vírgulas)" + "message": "Tipos de reivindicação de nomes personalizados" }, "acrValues": { - "message": "Valores de Referência de Classe de Contexto de Autenticação Solicitada (acr_values)" + "message": "Valores de referência de classe de contexto de autenticação solicitados" }, "expectedReturnAcrValue": { - "message": "Esperado Valor de Reivindicação \"acr\" Na Resposta (validação acr)" + "message": "Era esperado o valor de reivindicação \"acr\" na resposta" }, "spEntityId": { - "message": "ID da Entidade SP" + "message": "ID da entidade SP" }, "spMetadataUrl": { - "message": "URL de Metadados SAML 2.0" + "message": "URL de metadados do SAML 2.0" }, "spAcsUrl": { - "message": "URL do Serviço de Declaração de Consumidor (ACS)" + "message": "URL do serviço de declaração de consumidor (ACS)" }, "spNameIdFormat": { - "message": "Formato ID do Nome" + "message": "Formato do ID do nome" }, "spOutboundSigningAlgorithm": { - "message": "Algoritmo de Assinatura de Saída" + "message": "Algoritmo de assinatura de saída" }, "spSigningBehavior": { - "message": "Comportamento de Assinatura" + "message": "Comportamento de assinatura" }, "spMinIncomingSigningAlgorithm": { - "message": "Algoritmo de Assinatura de Entrada Mínima" + "message": "Algoritmo mínimo de assinatura de entrada" }, "spWantAssertionsSigned": { - "message": "Precisa de Declarações Assinadas" + "message": "Supor declarações assinadas" }, "spValidateCertificates": { - "message": "Validar Certificados" + "message": "Validar certificados" }, "spUniqueEntityId": { - "message": "Defina um ID único de entidade SP" + "message": "Configurar ID único para entidade SP" }, "spUniqueEntityIdDesc": { - "message": "Gerar um identificador que seja exclusivo para sua organização" + "message": "Gere um identificador que é único para a sua organização" }, "idpEntityId": { - "message": "ID da Entidade" + "message": "ID da entidade" }, "idpBindingType": { - "message": "Tipo de Ligação" + "message": "Tipo de vínculo" }, "idpSingleSignOnServiceUrl": { - "message": "URL de Serviço de Logon Único" + "message": "URL do serviço de autenticação única" }, "idpSingleLogoutServiceUrl": { - "message": "URL de Serviço de Desconexão Único" + "message": "URL do serviço de desconexão única" }, "idpX509PublicCert": { - "message": "Certificado Público X509" + "message": "Certificado público X509" }, "idpOutboundSigningAlgorithm": { - "message": "Algoritmo de Assinatura de Saída" + "message": "Algoritmo de assinatura de saída" }, "idpAllowUnsolicitedAuthnResponse": { - "message": "Permitir Retorno de Autenticação Não Solicitada" + "message": "Permitir respostas não solicitadas de autenticação" }, "idpAllowOutboundLogoutRequests": { - "message": "Permitir pedidos de saída" + "message": "Permitir solicitações de desconexão de saída" }, "idpSignAuthenticationRequests": { - "message": "Assinar pedidos de autenticação" + "message": "Assinar solicitações de autenticação" }, "ssoSettingsSaved": { - "message": "Configuração de logon único foi salva." + "message": "Configuração da autenticação única salva" }, "sponsoredFamilies": { - "message": "Bitwarden Families Gratuito" + "message": "Bitwarden Famílias Grátis" }, "sponsoredBitwardenFamilies": { - "message": "Sponsored families" + "message": "Famílias financiadas" }, "noSponsoredFamiliesMessage": { - "message": "No sponsored families" + "message": "Nenhuma família financiada" }, "nosponsoredFamiliesDetails": { - "message": "Sponsored non-member families plans will display here" + "message": "Planos familiares financiados de não membros aparecerão aqui" }, "sponsorshipFreeBitwardenFamilies": { - "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + "message": "Os membros da sua organização são elegíveis para o Bitwarden Famílias Grátis. Você pode financiar o Bitwarden Famílias de graça para funcionários que não são membros da sua organização do Bitwarden. Para financiar um não membro, é necessária uma vaga disponível dentro da sua organização." }, "sponsoredFamiliesRemoveActiveSponsorship": { - "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + "message": "Quando você remove um financiamento ativo, uma vaga dentro da sua organização ficará disponível após a data de renovação da organização financiada." }, "sponsoredFamiliesEligible": { - "message": "Você e sua família estão elegíveis para o Bitwarden Families Gratuito. Resgate com seu e-mail pessoal para manter seus dados seguros mesmo quando você não estiver no trabalho." + "message": "Você e a sua família são elegíveis para o Bitwarden Famílias Grátis. Resgate com seu e-mail pessoal para manter seus dados seguros mesmo quando você não estiver no trabalho." }, "sponsoredFamiliesEligibleCard": { - "message": "Resgate o seu plano Bitwarden for Families Grátis hoje para manter seus dados seguros mesmo quando você não estiver no trabalho." + "message": "Resgate o seu plano Bitwarden Famílias Grátis hoje para manter seus dados seguros mesmo quando não estiver no trabalho." }, "sponsoredFamiliesIncludeMessage": { - "message": "The Bitwarden for Families plan includes" + "message": "O plano Bitwarden Famílias inclui" }, "sponsoredFamiliesPremiumAccess": { - "message": "Acesso premium para até 6 usuários" + "message": "Acesso Premium para até 6 usuários" }, "sponsoredFamiliesSharedCollectionsForFamilyMembers": { - "message": "Shared collections for family members" + "message": "Conjuntos compartilhados para membros da família" }, "memberFamilies": { - "message": "Member families" + "message": "Famílias de membros" }, "noMemberFamilies": { - "message": "No member families" + "message": "Nenhuma família de membros" }, "noMemberFamiliesDescription": { - "message": "Members who have redeemed family plans will display here" + "message": "Os membros que resgataram os planos familiares aparecerão aqui" }, "membersWithSponsoredFamilies": { - "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + "message": "Os membros da sua organização são elegíveis para o Bitwarden Famílias Grátis. Aqui você pode ver os membros que financiaram uma organização do Famílias." }, "organizationHasMemberMessage": { - "message": "A sponsorship cannot be sent to $EMAIL$ because they are a member of your organization.", + "message": "Um financiamento não pode ser enviado para $EMAIL$ porque eles são um membro da sua organização.", "placeholders": { "email": { "content": "$1", @@ -6936,31 +6972,31 @@ } }, "badToken": { - "message": "O link não é mais válido. Peça ao patrocinador para reenviar a oferta." + "message": "O link não é mais válido. Peça ao financiador para reenviar a oferta." }, "reclaimedFreePlan": { - "message": "Plano gratuito recuperado" + "message": "Plano gratuito resgatado" }, "redeem": { "message": "Resgatar" }, "sponsoredFamiliesSelectOffer": { - "message": "Selecione a organização que você gostaria de patrocinar" + "message": "Selecione a organização que você gostaria de financiar" }, "familiesSponsoringOrgSelect": { - "message": "Qual oferta do Families Grátis você gostaria de resgatar?" + "message": "Qual oferta do Famílias Grátis você gostaria de resgatar?" }, "sponsoredFamiliesEmail": { - "message": "Digite seu e-mail pessoal para resgatar o Bitwarden Families" + "message": "Digite o seu e-mail pessoal para resgatar o Bitwarden Famílias" }, "sponsoredFamiliesLeaveCopy": { - "message": "Se você sair ou for removido desta organização, seu plano do Families irá expirar no final do período de cobrança." + "message": "Se você remover uma oferta ou ser removido da organização financiante, o seu financiamento irá expirar na próxima data de renovação." }, "acceptBitwardenFamiliesHelp": { - "message": "Aceite a oferta de uma organização existente ou crie uma nova organização de Famílias." + "message": "Aceite a oferta de uma organização existente ou crie uma organização de Famílias." }, "setupSponsoredFamiliesLoginDesc": { - "message": "Foi oferecida a você uma Organização do Plano Bitwarden Families gratuita. Para continuar, você precisa entrar na conta que recebeu a oferta." + "message": "Foi oferecida a você uma organização do plano Bitwarden Famílias Grátis. Para continuar, você precisa se conectar à conta que recebeu a oferta." }, "sponsoredFamiliesAcceptFailed": { "message": "Não é possível aceitar a oferta. Reenvie o e-mail da oferta da conta corporativa e tente novamente." @@ -6975,10 +7011,10 @@ } }, "sponsoredFamiliesOffer": { - "message": "Resgatar Oferta de Organização do Bitwarden Families Gratuita" + "message": "Aceitar Bitwarden Familías Grátis" }, "sponsoredFamiliesOfferRedeemed": { - "message": "A Oferta Gratuita do Bitwarden Families foi resgatada com sucesso" + "message": "Oferta do Bitwarden Familías Grátis resgatada com sucesso" }, "redeemed": { "message": "Resgatado" @@ -6987,7 +7023,7 @@ "message": "Conta resgatada" }, "revokeAccountMessage": { - "message": "Revoke account $NAME$", + "message": "Revogar conta $NAME$", "placeholders": { "name": { "content": "$1", @@ -6996,7 +7032,7 @@ } }, "resendEmailLabel": { - "message": "Reenviar e-mail de Patrocínio para $NAME$ patrocínio", + "message": "Reenviar o e-mail de financiamento para o financiamento do $NAME$", "placeholders": { "name": { "content": "$1", @@ -7005,7 +7041,7 @@ } }, "freeFamiliesPlan": { - "message": "Plano de famílias gratuitas" + "message": "Plano Famílias grátis" }, "redeemNow": { "message": "Resgatar agora" @@ -7014,37 +7050,37 @@ "message": "Destinatário" }, "removeSponsorship": { - "message": "Remover Patrocínio" + "message": "Remover financiamento" }, "removeSponsorshipConfirmation": { - "message": "Após remover um patrocínio, você será responsável por esta assinatura e faturas relacionadas. Você tem certeza que deseja continuar?" + "message": "Após remover um financiamento, você ficará responsável por esta assinatura e as faturas relacionadas. Você tem certeza que deseja continuar?" }, "sponsorshipCreated": { - "message": "Patrocínio criado" + "message": "Financiamento criado" }, "emailSent": { "message": "E-mail enviado" }, "removeSponsorshipSuccess": { - "message": "Patrocínio Removido" + "message": "Financiamento removido" }, "ssoKeyConnectorError": { - "message": "Erro de Key Connector: certifique-se de que a Key Connector está disponível e funcionando corretamente." + "message": "Erro do Key Connector: certifique-se de que o Key Connector está disponível e funcionando corretamente." }, "keyConnectorUrl": { - "message": "URL de Conector de Chave" + "message": "URL do Key Connector" }, "sendVerificationCode": { "message": "Enviar um código de verificação para o seu e-mail" }, "sendCode": { - "message": "Enviar Código" + "message": "Enviar código" }, "codeSent": { - "message": "Código Enviado" + "message": "Código enviado" }, "verificationCode": { - "message": "Código de Verificação" + "message": "Código de verificação" }, "confirmIdentity": { "message": "Confirme a sua identidade para continuar." @@ -7053,22 +7089,22 @@ "message": "O código de verificação é necessário." }, "webauthnCancelOrTimeout": { - "message": "A autenticação foi cancelada ou demorou muito. Por favor tente novamente." + "message": "A autenticação foi cancelada ou demorou muito. Tente novamente." }, "invalidVerificationCode": { "message": "Código de verificação inválido" }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + "message": "Uma senha principal não é mais necessária para membros da seguinte organização. Confirme o domínio abaixo com o administrador da sua organização." }, "keyConnectorDomain": { - "message": "Key Connector domain" + "message": "Domínio do Key Connector" }, "leaveOrganization": { - "message": "Sair da Organização" + "message": "Sair da organização" }, "removeMasterPassword": { - "message": "Remover Senha Mestra" + "message": "Remover senha principal" }, "removedMasterPassword": { "message": "Senha principal removida." @@ -7077,52 +7113,52 @@ "message": "Permitir autenticação por SSO" }, "allowSsoDesc": { - "message": "Uma vez definida, sua configuração será salva e os membros poderão se autenticar usando suas credenciais de Provedor de Identidade." + "message": "Uma vez configurada, sua configuração será salva e os membros poderão se autenticar usando suas credenciais do Provedor de Identidade." }, "ssoPolicyHelpStart": { - "message": "Ativar o", + "message": "Use a", "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": "requer política de autenticação de logon único", + "message": "política de aplicação da autenticação única", "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": "para exigir que todos os membros entrem com o SSO.", + "message": "para exigir que todos os membros se conectem com o 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": "Opções de Descriptografia de Membro" + "message": "Opções de descriptografia do membro" }, "memberDecryptionPassDesc": { - "message": "Uma vez autenticados, os membros irão descriptografar os dados do cofre usando suas Senhas Mestras." + "message": "Ao se autenticar, os membros descriptografarão os dados do cofre usando suas senhas principais." }, "keyConnector": { - "message": "Conector de Chave" + "message": "Key Connector" }, "memberDecryptionKeyConnectorDescStart": { - "message": "Conecte o login com SSO ao seu servidor de chave de descriptografia auto-hospedado. Usando essa opção, os membros não precisarão usar suas senhas principais para descriptografar os dados", + "message": "Conecte a autenticação com SSO ao seu servidor de chave de descriptografia auto-hospedado. Usando essa opção, os membros não precisarão usar suas senhas principais para descriptografar os dados dos cofres. As", "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": "requer autenticação SSO e políticas de organização única", + "message": "políticas de autenticação única e de organização única", "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": "são necessários para configurar a descriptografia do conector chave. Contacte o suporte do Bitwarden para configurar a assistência.", + "message": "são necessárias para configurar a descriptografia do Key Connector. Entre em contato com o suporte do Bitwarden para assistência na configuração.", "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": "\"Entrar com o SSO e Descriptografia de Conector de Chave\" está habilitado. Esta política se aplica apenas a Proprietários e Administradores." + "message": "\"Conectar-se com SSO e descriptografia do Key Connector\" está ativado. Esta política se aplica apenas a proprietários e administradores." }, "enabledSso": { - "message": "Ativado o SSO" + "message": "SSO foi ativado" }, "disabledSso": { - "message": "Desativado o SSO" + "message": "SSO foi desativado" }, "emailMustLoginWithSso": { - "message": "$EMAIL$ must login with Single Sign-on", + "message": "$EMAIL$ deve se conectar com a autenticação única", "placeholders": { "email": { "content": "$1", @@ -7131,58 +7167,58 @@ } }, "enabledKeyConnector": { - "message": "Ativado o Conector de Chave" + "message": "Key Connector foi ativado" }, "disabledKeyConnector": { - "message": "Desativado o Conector de Chave" + "message": "Key Connector foi desativado" }, "keyConnectorWarning": { - "message": "Depois que o Conector de Chave é configurado, as Opções de Descriptografia de Membro não podem ser alteradas." + "message": "Quando os membros começarem a usar o Key Connector, a sua organização não poderá reverter para a descriptografia com a senha principal. Prossiga somente se você está confortável com a implementação e gerenciamento de um servidor de chaves." }, "migratedKeyConnector": { - "message": "Migrado para o Conector de Chave" + "message": "Migrado para o Key Connector" }, "paymentSponsored": { - "message": "Por favor, forneça uma forma de pagamento para associar à organização. Não se preocupe, não cobraremos nada, a menos que você selecione recursos adicionais ou seu patrocínio expire. " + "message": "Forneça uma forma de pagamento para associar à organização. Não se preocupe, não cobraremos nada, a menos que você selecione recursos adicionais ou seu financiamento expire. " }, "orgCreatedSponsorshipInvalid": { - "message": "A oferta de patrocínio expirou, você pode excluir a organização que criou para evitar uma cobrança no final do seu teste de 7 dias. Caso contrário, você pode fechar este alerta para manter a organização e assumir a responsabilidade pela cobrança." + "message": "A oferta de financiamento expirou, você pode apagar a organização que criou para evitar uma cobrança no final do seu teste de 7 dias. Caso contrário, você pode fechar este alerta para manter a organização e assumir a responsabilidade pelo faturamento." }, "newFamiliesOrganization": { - "message": "Nova Organização de Famílias" + "message": "Nova organização de Famílias" }, "acceptOffer": { - "message": "Aceitar Oferta" + "message": "Aceitar oferta" }, "sponsoringOrg": { - "message": "Organização Patrocinadora" + "message": "Organização financiante" }, "keyConnectorTest": { "message": "Testar" }, "keyConnectorTestSuccess": { - "message": "Sucesso! Conector de Chave acessado." + "message": "Sucesso! O Key Connector foi contatado." }, "keyConnectorTestFail": { - "message": "Não foi possível acessar o Conector de Chave. Verifique a URL." + "message": "Não é possível acessar o Key Connector. Verifique o URL." }, "sponsorshipTokenHasExpired": { - "message": "A oferta de patrocínio expirou." + "message": "A oferta de financiamento expirou." }, "freeWithSponsorship": { - "message": "GRÁTIS com patrocínio" + "message": "GRÁTIS com financiamento" }, "viewBillingSyncToken": { - "message": "Ver Token de Faturamento Sync" + "message": "Ver token de sincronização de faturamento" }, "generateBillingToken": { "message": "Gerar token de faturamento" }, "copyPasteBillingSync": { - "message": "Copie e cole esse token nas configurações de Faturamento Sync da sua organização auto-hospedada." + "message": "Copie e cole esse token nas configurações de sincronização de faturamento da sua organização auto-hospedada." }, "billingSyncCanAccess": { - "message": "Seu token de faturamento Sync pode acessar e editar as configurações de assinatura desta organização." + "message": "Seu token de sincronização de faturamento pode acessar e editar as configurações de assinatura desta organização." }, "manageBillingTokenSync": { "message": "Gerenciar token de faturamento" @@ -7191,16 +7227,16 @@ "message": "Configurar sincronização de faturamento" }, "generateToken": { - "message": "Gerar o token" + "message": "Gerar token" }, "rotateToken": { - "message": "Renovar Token" + "message": "Rotacionar token" }, "rotateBillingSyncTokenWarning": { - "message": "Se você continuar, precisará re-configurar a sincronização de cobrança no seu servidor auto-hospedado." + "message": "Se você continuar, precisará reconfigurar a sincronização de faturamento no seu servidor auto-hospedado." }, "rotateBillingSyncTokenTitle": { - "message": "Renovar o Token de Sincronização de Cobrança invalidará o token anterior." + "message": "Rotacionar o token de sincronização de faturamento invalidará o token anterior." }, "selfHostedServer": { "message": "auto-hospedado" @@ -7212,49 +7248,49 @@ "message": "Especifique a URL de base da sua instalação local do Bitwarden. Exemplo: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "Para usuários avançados. Você pode especificar a URL de base de cada serviço independentemente." + "message": "Para configuração avançada, você pode especificar o URL de base de cada serviço independentemente." }, "selfHostedEnvFormInvalid": { - "message": "Você deve adicionar um URL do servidor de base ou pelo menos um ambiente personalizado." + "message": "Você deve adicionar um URL de base de um servidor ou pelo menos um ambiente personalizado." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "URLs devem usar HTTPS." }, "apiUrl": { - "message": "URL do servidor API" + "message": "URL do servidor da API" }, "webVaultUrl": { - "message": "URL do servidor do Cofre Web" + "message": "URL do servidor do cofre web" }, "identityUrl": { "message": "URL do servidor de identidade" }, "notificationsUrl": { - "message": "URL do servidor notificações" + "message": "URL do servidor de notificações" }, "iconsUrl": { - "message": "URL do Servidor de Ícones" + "message": "URL do servidor de ícones" }, "environmentSaved": { - "message": "URLs para ambiente salvo" + "message": "URLs do ambiente salvas" }, "selfHostingTitle": { - "message": "Auto-hospedado" + "message": "Auto-hospedagem" }, "selfHostingEnterpriseOrganizationSectionCopy": { - "message": "Para configurar sua organização no seu próprio servidor, você precisará fazer o upload do arquivo de licença. Para oferecer suporte a planos de famílias gratuitas e recursos avançados de faturamento para a sua organização auto-hospedada, você precisará configurar a sincronização de cobrança." + "message": "Para configurar sua organização no seu próprio servidor, você precisará enviar o arquivo da licença. Para oferecer suporte a planos do Famílias Grátis e recursos avançados de faturamento para a sua organização auto-hospedada, você precisará configurar a sincronização de faturamento." }, "billingSyncApiKeyRotated": { - "message": "Token renovado" + "message": "Token rotacionado" }, "billingSyncKeyDesc": { - "message": "Um Token de sincronização de faturamento das configurações de assinatura da sua organização da nuvem é necessário para completar este formulário." + "message": "Um token de sincronização de faturamento das configurações de assinatura da sua organização da nuvem é necessário para completar este formulário." }, "billingSyncKey": { - "message": "Token de Sincronização de Faturamento" + "message": "Token de sincronização de faturamento" }, "automaticBillingSyncDesc": { - "message": "Sincronização automática desbloqueia os patrocínios das famílias e permite-lhe sincronizar sua licença sem enviar um arquivo. Depois de fazer atualizações no servidor da nuvem do Bitwarden, selecione Sincronizar licença para aplicar as alterações." + "message": "A sincronização automática desbloqueia o financiamento das famílias e te permite sincronizar sua licença sem enviar um arquivo. Depois de atualizar o servidor da nuvem do Bitwarden, selecione Sincronizar licença para aplicar as alterações." }, "active": { "message": "Ativo" @@ -7263,13 +7299,13 @@ "message": "Inativo" }, "sentAwaitingSync": { - "message": "Enviado (Aguardando Sincronização)" + "message": "Enviado (aguardando sincronização)" }, "sent": { "message": "Enviado" }, "requestRemoved": { - "message": "Removido (Aguardando Sincronização)" + "message": "Removido (aguardando sincronização)" }, "requested": { "message": "Solicitado" @@ -7321,7 +7357,7 @@ } }, "idpSingleSignOnServiceUrlRequired": { - "message": "Necessário se o ID da Entidade não for um URL." + "message": "Necessário se o ID da entidade não for um URL." }, "offerNoLongerValid": { "message": "Esta oferta não é mais válida. Entre em contato com os administradores da sua organização para obter mais informações." @@ -7333,19 +7369,19 @@ "message": "Necessário se a autoridade não for válida." }, "separateMultipleWithComma": { - "message": "Separe várias tags com vírgula." + "message": "Separe vários com uma vírgula." }, "sessionTimeout": { - "message": "Sua sessão expirou. Volte e tente iniciar a sessão novamente." + "message": "Sua sessão expirou. Volte e tente se conectar novamente." }, "exportingPersonalVaultTitle": { - "message": "Exportando Cofre Pessoal" + "message": "Exportando cofre individual" }, "exportingOrganizationVaultTitle": { - "message": "Exportando o Cofre da Organização" + "message": "Exportando cofre da organização" }, "exportingIndividualVaultDescription": { - "message": "Apenas os itens individuais do cofre associados a $EMAIL$ serão exportados. Os itens do cofre da organização não serão incluídos. Apenas as informações de item do cofre serão exportadas e não incluirão anexos associados.", + "message": "Apenas os itens do cofre individual associados a $EMAIL$ serão exportados. Os itens do cofre de organizações não serão incluídos. Apenas as informações dos itens do cofre serão exportadas e não incluirão anexos associados.", "placeholders": { "email": { "content": "$1", @@ -7354,7 +7390,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Apenas os itens individuais do cofre, incluindo anexos associados ao $EMAIL$ serão exportados. Os itens do cofre da organização não serão incluídos", + "message": "Apenas os itens do cofre individual, incluindo anexos associados com $EMAIL$ serão exportados. Os itens do cofre de organizações não serão incluídos", "placeholders": { "email": { "content": "$1", @@ -7363,7 +7399,7 @@ } }, "exportingOrganizationVaultDesc": { - "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Itens do cofre pessoal e itens de outras organizações não serão incluídos.", + "message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado. Itens de cofres individuais e de outras organizações não serão incluídos.", "placeholders": { "organization": { "content": "$1", @@ -7372,7 +7408,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado.", "placeholders": { "organization": { "content": "$1", @@ -7381,7 +7417,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Os itens dos meus conjuntos não serão incluídos.", "placeholders": { "organization": { "content": "$1", @@ -7393,10 +7429,10 @@ "message": "Acesso negado. Você não tem permissão para ver esta página." }, "noPageAccess": { - "message": "You do not have access to this page" + "message": "Você não tem acesso a esta página" }, "masterPassword": { - "message": "Senha Mestra" + "message": "Senha principal" }, "security": { "message": "Segurança" @@ -7405,10 +7441,10 @@ "message": "Chaves" }, "billingHistory": { - "message": "Histórico de Cobrança" + "message": "Histórico de faturamento" }, "backToReports": { - "message": "Voltar para Relatórios" + "message": "Voltar aos relatórios" }, "organizationPicker": { "message": "Seletor de organização" @@ -7427,35 +7463,35 @@ } }, "accountSettings": { - "message": "Configurações da Conta" + "message": "Configurações da conta" }, "generator": { "message": "Gerador", "description": "Short for 'credential generator'." }, "generateUsername": { - "message": "Gerar Usuário" + "message": "Gerar nome de usuário" }, "generateEmail": { "message": "Gerar e-mail" }, "generatePassword": { - "message": "Gerar Senha" + "message": "Gerar senha" }, "generatePassphrase": { "message": "Gerar frase secreta" }, "passwordGenerated": { - "message": "Password generated" + "message": "Senha gerada" }, "passphraseGenerated": { - "message": "Passphrase generated" + "message": "Frase secreta gerada" }, "usernameGenerated": { - "message": "Username generated" + "message": "Nome de usuário gerado" }, "emailGenerated": { - "message": "Email generated" + "message": "E-mail gerado" }, "spinboxBoundariesHint": { "message": "Valor deve ser entre $MIN$ e $MAX$.", @@ -7472,7 +7508,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ caracteres ou mais para gerar uma senha forte.", + "message": " Utilize $RECOMMENDED$ ou mais caracteres para gerar um senha forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -7482,7 +7518,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use palavras $RECOMMENDED$ ou mais para gerar uma frase secreta forte.", + "message": " Utilize $RECOMMENDED$ ou mais palavras para gerar uma frase secreta forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -7492,7 +7528,7 @@ } }, "plusAddressedEmail": { - "message": "E-mail alternativo (com um +)", + "message": "E-mail endereçado com +", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { @@ -7502,7 +7538,7 @@ "message": "E-mail pega-tudo" }, "catchallEmailDesc": { - "message": "Use o catch-all configurado no seu domínio." + "message": "Use a caixa de entrada pega-tudo configurada no seu domínio." }, "useThisEmail": { "message": "Usar este e-mail" @@ -7512,25 +7548,25 @@ "description": "Generates domain-based username using random letters" }, "randomWord": { - "message": "Palavra Aleatória" + "message": "Palavra aleatória" }, "usernameGenerator": { - "message": "Gerador de usuário" + "message": "Gerador de nome de usuário" }, "useThisPassword": { - "message": "Use esta senha" + "message": "Usar esta senha" }, "useThisPassphrase": { - "message": "Use this passphrase" + "message": "Usar esta frase secreta" }, "useThisUsername": { - "message": "Use este nome de usuário" + "message": "Usar este nome de usuário" }, "securePasswordGenerated": { - "message": "Senha segura gerada! Não se esqueça de atualizar também sua senha no site." + "message": "Senha segura gerada! Não se esqueça de atualizar sua senha no site também." }, "useGeneratorHelpTextPartOne": { - "message": "Usar o gerador", + "message": "Use o gerador", "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": { @@ -7541,19 +7577,19 @@ "message": "Serviço" }, "unknownCipher": { - "message": "Item desconhecido, talvez você precise fazer login com outra conta para acessar este item." + "message": "Item desconhecido, talvez você precise pedir permissão para acessar este item." }, "unknownSecret": { - "message": "Unknown secret, you may need to request permission to access this secret." + "message": "Segredo desconhecido, talvez você precise pedir permissão para acessar este segredo." }, "unknownServiceAccount": { - "message": "Unknown machine account, you may need to request permission to access this machine account." + "message": "Conta de máquina desconhecida, talvez você precise pedir permissão para acessar esta conta de máquina." }, "unknownProject": { - "message": "Unknown project, you may need to request permission to access this project." + "message": "Projeto desconhecido, talvez você precise pedir permissão para acessar este projeto." }, "cannotSponsorSelf": { - "message": "Você não pode resgatar a conta ativa. Digite um e-mail diferente." + "message": "Você não pode resgatar para conta ativa. Digite um e-mail diferente." }, "revokeWhenExpired": { "message": "Expira em $DATE$", @@ -7565,7 +7601,7 @@ } }, "awaitingSyncSingular": { - "message": "Token renovado há $DAYS$ dia atrás. Atualize o token de sincronização de cobrança nas configurações da organização auto-hospedada.", + "message": "Token rotacionado há $DAYS$ dia atrás. Atualize o token de sincronização de faturamento nas configurações da organização auto-hospedada.", "placeholders": { "days": { "content": "$1", @@ -7574,7 +7610,7 @@ } }, "awaitingSyncPlural": { - "message": "Token renovado há $DAYS$ dias. Atualize o token de sincronização de cobrança nas configurações da sua organização auto-hospedada.", + "message": "Token rotacionado há $DAYS$ dias atrás. Atualize o token de sincronização de faturamento nas configurações da organização auto-hospedada.", "placeholders": { "days": { "content": "$1", @@ -7583,11 +7619,11 @@ } }, "lastSync": { - "message": "Última Sincronização", + "message": "Última sincronização", "description": "Used as a prefix to indicate the last time a sync occurred. Example \"Last sync 1968-11-16 00:00:00\"" }, "sponsorshipsSynced": { - "message": "Patrocínios auto-hospedados sincronizados." + "message": "Faturamentos auto-hospedados sincronizados." }, "billingManagedByProvider": { "message": "Gerenciado por $PROVIDER$", @@ -7599,11 +7635,11 @@ } }, "billingContactProviderForAssistance": { - "message": "Por favor, entre em contato com eles para obter mais ajuda", + "message": "Entre em contato com eles para obter mais ajuda", "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": "Alias de E-mail Encaminhado" + "message": "Alias de encaminhamento de e-mail" }, "forwardedEmailDesc": { "message": "Gere um alias de e-mail com um serviço externo de encaminhamento." @@ -7617,7 +7653,7 @@ "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "Erro $SERVICENAME$ : $ERRORMESSAGE$", + "message": "Erro do $SERVICENAME$: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -7645,7 +7681,7 @@ } }, "forwaderInvalidToken": { - "message": "Token de API $SERVICENAME$ inválido", + "message": "Token de API do $SERVICENAME$ inválido", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -7655,7 +7691,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Token de API $SERVICENAME$ inválido: $ERRORMESSAGE$", + "message": "Token de API da $SERVICENAME$ inválido: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -7693,7 +7729,7 @@ } }, "forwarderNoAccountId": { - "message": "Não foi possível obter a máscara do ID da conta de email $SERVICENAME$.", + "message": "Não é possível obter o ID da conta de e-mail mascarado do $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -7703,7 +7739,7 @@ } }, "forwarderNoDomain": { - "message": "Domínio $SERVICENAME$ inválido.", + "message": "Domínio inválido do $SERVICENAME$.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -7713,7 +7749,7 @@ } }, "forwarderNoUrl": { - "message": "URL $SERVICENAME$ inválida.", + "message": "URL inválido do $SERVICENAME$.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -7723,7 +7759,7 @@ } }, "forwarderUnknownError": { - "message": "Ocorreu um erro $SERVICENAME$ desconhecido.", + "message": "Ocorreu um erro desconhecido do $SERVICENAME$.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -7743,23 +7779,23 @@ } }, "hostname": { - "message": "Hostname", + "message": "Nome do servidor", "description": "Part of a URL." }, "deviceVerification": { - "message": "Verificação do dispositivo" + "message": "Verificação de dispositivo" }, "enableDeviceVerification": { - "message": "Habilitar Verificação do Dispositivo" + "message": "Ativar verificação de dispositivo" }, "deviceVerificationDesc": { - "message": "Quando ativado, são enviados códigos de verificação para o seu endereço de e-mail ao fazer login a partir de um dispositivo não reconhecido" + "message": "Códigos de recuperação serão enviados ao seu endereço de e-mail ao se conectar a partir de um dispositivo desconhecido" }, "updatedDeviceVerification": { - "message": "Verificação do Dispositivo Atualizada" + "message": "Verificação de dispositivo atualizada" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { - "message": "Tem certeza de que deseja habilitar o Verificação do Dispositivo? O código de verificação de e-mail chegarão em: $EMAIL$", + "message": "Tem certeza de que deseja ativar a verificação de dispositivo? Os e-mails dos códigos de verificação chegarão em: $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -7768,41 +7804,41 @@ } }, "premiumSubcriptionRequired": { - "message": "Assinatura Premium necessária" + "message": "Plano Premium necessário" }, "scim": { "message": "Provisionamento de SCIM", "description": "The text, 'SCIM', is an acronym and should not be translated." }, "scimDescription": { - "message": "Provisionar automaticamente usuários e grupos com seu provedor de identidade preferido via provisionamento de SCIM", + "message": "Provisione automaticamente usuários e grupos com seu provedor de identidade preferido via provisionamento de SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimIntegrationDescription": { - "message": "Provisionar automaticamente usuários e grupos com seu provedor de identidade preferido via provisionamento de SCIM. Encontre suporte para as integrações", + "message": "Provisione automaticamente usuários e grupos com seu provedor de identidade preferido via provisionamento de SCIM. Procure integrações suportadas", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimEnabledCheckboxDesc": { - "message": "Habilitar SCIM", + "message": "Ativar SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimEnabledCheckboxDescHelpText": { - "message": "Configure seu provedor de identidade preferido, configurando a URL e a chave de API SCIM", + "message": "Configure seu provedor de identidade preferido, configurando a URL e a chave da API do SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimApiKeyHelperText": { "message": "Esta chave de API tem acesso para gerenciar os usuários da sua organização. Deve ser mantida em segredo." }, "copyScimKey": { - "message": "Copie a chave API do SCIM para a área de transferência", + "message": "Copie a chave da API do SCIM para a área de transferência", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateScimKey": { - "message": "Rotacionar a chave API SCIM", + "message": "Rotacionar a chave de API do SCIM", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateScimKeyWarning": { - "message": "Você tem certeza que deseja rotacionar a chave de API SCIM? A chave atual não funcionará mais para quaisquer integrações existentes.", + "message": "Você tem certeza que deseja rotacionar a chave de API do SCIM? A chave atual não funcionará mais para quaisquer integrações existentes.", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateKey": { @@ -7813,7 +7849,7 @@ "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "copyScimUrl": { - "message": "Copie a URL do endpoint SCIM para a área de transferência", + "message": "Copie a URL do endpoint do SCIM para a área de transferência", "description": "the text, 'SCIM' and 'URL', are acronyms and should not be translated." }, "scimUrl": { @@ -7821,11 +7857,11 @@ "description": "the text, 'SCIM' and 'URL', are acronyms and should not be translated." }, "scimApiKeyRotated": { - "message": "A chave de API SCIM foi rotacionada com sucesso", + "message": "A chave da API do SCIM foi rotacionada com sucesso", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "scimSettingsSaved": { - "message": "As configurações de SCIM foram salvas com sucesso", + "message": "Configurações do SCIM salvas", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "inputRequired": { @@ -7892,7 +7928,7 @@ } }, "fieldsNeedAttention": { - "message": "$COUNT$ campos(s) acima precisam de sua atenção.", + "message": "$COUNT$ campo(s) acima precisam de sua atenção.", "placeholders": { "count": { "content": "$1", @@ -7904,7 +7940,7 @@ "message": "1 campo precisa de sua atenção." }, "multipleFieldsNeedAttention": { - "message": "Campos $COUNT$ precisam de sua atenção.", + "message": "$COUNT$ campos precisam da sua atenção.", "placeholders": { "count": { "content": "$1", @@ -7919,34 +7955,34 @@ "message": "A autenticação em duas etapas do Duo é necessária para sua conta." }, "duoTwoFactorRequiredPageSubtitle": { - "message": "A autenticação de dois fatores do Duo é necessária para sua conta. Siga os passos abaixo para conseguir entrar." + "message": "A autenticação de duas etapas do Duo é necessária para sua conta. Siga os passos abaixo para conseguir se conectar." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Siga os passos abaixo para finalizar o login." + "message": "Siga os passos abaixo para terminar de se conectar." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Follow the steps below to finish logging in with your security key." + "message": "Siga os passos abaixo para terminar de se conectar com a sua chave de segurança." }, "launchDuo": { - "message": "Abrir o Duo" + "message": "Abrir Duo" }, "turnOn": { - "message": "Ligar" + "message": "Ativar" }, "on": { - "message": "Ligado" + "message": "Ativado" }, "off": { - "message": "Desligado" + "message": "Desativado" }, "connected": { - "message": "Connected" + "message": "Conectado" }, "members": { "message": "Membros" }, "reporting": { - "message": "Reportando" + "message": "Relatórios" }, "numberOfUsers": { "message": "Número de usuários" @@ -7997,16 +8033,16 @@ "message": "-- Digite para filtrar --" }, "multiSelectLoading": { - "message": "Carrgando Opções..." + "message": "Carrgando opções..." }, "multiSelectNotFound": { "message": "Nenhum item encontrado" }, "multiSelectClearAll": { - "message": "Limpar todos" + "message": "Limpar tudo" }, "toggleCharacterCount": { - "message": "Alternar contagem de caracteres", + "message": "Habilitar contagem de caracteres", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "passwordCharacterCount": { @@ -8041,18 +8077,18 @@ "description": "Action to copy the value of a secret to the system's clipboard." }, "deleteSecret": { - "message": "Excluir segredo", + "message": "Apagar segredo", "description": "Action to delete a single secret from the system." }, "deleteSecrets": { - "message": "Excluir segredos", + "message": "Apagar segredos", "description": "The action to delete multiple secrets from the system." }, "hardDeleteSecret": { - "message": "Excluir permanentemente o segredo" + "message": "Apagar segredo permanentemente" }, "hardDeleteSecrets": { - "message": "Excluir permanentemente os segredos" + "message": "Apagar segredos permanentemente" }, "secretProjectAssociationDescription": { "message": "Selecione os projetos aos quais o segredo será associado. Somente os usuários da organização com acesso a estes projetos poderão ver o segredo.", @@ -8063,11 +8099,11 @@ "description": "A label for a type-to-filter input field to choose projects." }, "searchProjects": { - "message": "Pesquisar projetos", + "message": "Buscar nos projetos", "description": "Label for the search bar used to search projects." }, "project": { - "message": "Projecto", + "message": "Projeto", "description": "Similar to collections, projects can be used to group secrets." }, "editProject": { @@ -8079,11 +8115,11 @@ "description": "The action to view details of a project." }, "deleteProject": { - "message": "Excluir projeto", + "message": "Apagar projeto", "description": "The action to delete a project from the system." }, "deleteProjects": { - "message": "Excluir projetos", + "message": "Apagar projetos", "description": "The action to delete multiple projects from the system." }, "secret": { @@ -8095,7 +8131,7 @@ "description": "A machine user which can be used to automate processes and access secrets in the system." }, "serviceAccounts": { - "message": "Contas de Serviço", + "message": "Contas de serviço", "description": "The title for the section that deals with service accounts." }, "secrets": { @@ -8103,11 +8139,11 @@ "description": "The title for the section of the application that deals with secrets." }, "nameValuePair": { - "message": "Par de Nome/Valor", + "message": "Par de nome/valor", "description": "Title for a name/ value pair. Secrets typically consist of a name and value pair." }, "secretEdited": { - "message": "Secreto editado", + "message": "Segredo editado", "description": "Notification for the successful editing of a secret." }, "secretCreated": { @@ -8123,7 +8159,7 @@ "description": "Title for creating a new service account." }, "secretsNoItemsTitle": { - "message": "Sem segredos para mostrar", + "message": "Nenhum segredo para mostrar", "description": "Empty state to indicate that there are no secrets to display." }, "secretsNoItemsMessage": { @@ -8131,10 +8167,10 @@ "description": "Message to encourage the user to start adding secrets." }, "secretsTrashNoItemsMessage": { - "message": "Não há segredos no lixo." + "message": "Não há segredos na lixeira." }, "serviceAccountsNoItemsMessage": { - "message": "Crie uma nova conta de serviço para começar a automatizar o acesso secreto.", + "message": "Crie uma nova conta de serviço para começar a automatizar o acesso aos segredos.", "description": "Message to encourage the user to start creating service accounts." }, "serviceAccountsNoItemsTitle": { @@ -8142,15 +8178,15 @@ "description": "Title to indicate that there are no service accounts to display." }, "searchSecrets": { - "message": "Pesquisar segredos", + "message": "Buscar nos segredos", "description": "Placeholder text for searching secrets." }, "deleteServiceAccounts": { - "message": "Excluir contas de serviço", + "message": "Apagar contas de serviço", "description": "Title for the action to delete one or multiple service accounts." }, "deleteServiceAccount": { - "message": "Excluir conta de serviço", + "message": "Apagar conta de serviço", "description": "Title for the action to delete a single service account." }, "viewServiceAccount": { @@ -8158,7 +8194,7 @@ "description": "Action to view the details of a service account." }, "deleteServiceAccountDialogMessage": { - "message": "Excluir conta de serviço $SERVICE_ACCOUNT$ é permanente e irreversível.", + "message": "Apagar a conta de serviço $SERVICE_ACCOUNT$ é permanente e irreversível.", "placeholders": { "service_account": { "content": "$1", @@ -8167,10 +8203,10 @@ } }, "deleteServiceAccountsDialogMessage": { - "message": "A exclusão de projetos é permanente e irreversível." + "message": "O apagamento de contas de serviço é permanente e irreversível." }, "deleteServiceAccountsConfirmMessage": { - "message": "Excluir $COUNT$ contas de serviço", + "message": "Apagar $COUNT$ contas de serviço", "placeholders": { "count": { "content": "$1", @@ -8179,13 +8215,13 @@ } }, "deleteServiceAccountToast": { - "message": "Conta de serviço excluída" + "message": "Conta de serviço apagada" }, "deleteServiceAccountsToast": { - "message": "Contas de serviço excluídas" + "message": "Contas de serviço apagadas" }, "searchServiceAccounts": { - "message": "Pesquisar contas de serviço", + "message": "Buscar nas contas de serviço", "description": "Placeholder text for searching service accounts." }, "editServiceAccount": { @@ -8217,7 +8253,7 @@ "description": "Title for creating a new project." }, "softDeleteSecretWarning": { - "message": "Excluir segredos pode afetar integrações existentes.", + "message": "Apagar segredos pode afetar integrações existentes.", "description": "Warns that deleting secrets can have consequences on integrations" }, "softDeletesSuccessToast": { @@ -8225,13 +8261,13 @@ "description": "Notifies that the selected secrets have been moved to the trash" }, "hardDeleteSecretConfirmation": { - "message": "Tem certeza de que deseja excluir permanentemente este segredo?" + "message": "Tem certeza de que deseja apagar permanentemente este segredo?" }, "hardDeleteSecretsConfirmation": { - "message": "Tem certeza que deseja excluir estes segredos permanentemente?" + "message": "Tem certeza que deseja apagar estes segredos permanentemente?" }, "hardDeletesSuccessToast": { - "message": "Segredos excluídos permanentemente" + "message": "Segredos apagados permanentemente" }, "smAccess": { "message": "Acesso", @@ -8254,7 +8290,7 @@ "description": "Notifies that a service account has been updated" }, "typeOrSelectProjects": { - "message": "Type or select projects", + "message": "Digite ou selecione projetos", "description": "Instructions for selecting projects for a service account" }, "newSaTypeToFilter": { @@ -8262,15 +8298,15 @@ "description": "Instructions for filtering a list of projects or secrets" }, "deleteProjectsToast": { - "message": "Projetos excluídos", + "message": "Projetos apagados", "description": "Notifies that the selected projects have been deleted" }, "deleteProjectToast": { - "message": "O projeto e todos os segredos associados foram excluídos", + "message": "Projeto apagado", "description": "Notifies that a project has been deleted" }, "deleteProjectDialogMessage": { - "message": "Excluir o projeto $PROJECT$ é permanente e irreversível.", + "message": "Apagar o projeto $PROJECT$ é permanente e irreversível.", "description": "Informs users that projects are hard deleted and not sent to trash", "placeholders": { "project": { @@ -8290,7 +8326,7 @@ } }, "deleteProjectConfirmMessage": { - "message": "Excluir $PROJECT$", + "message": "Apagar $PROJECT$", "description": "Confirmation prompt to delete a specific project, where '$PROJECT$' is a placeholder for the name of the project.", "placeholders": { "project": { @@ -8300,7 +8336,7 @@ } }, "deleteProjectsConfirmMessage": { - "message": "Excluir $COUNT$ Projetos", + "message": "Apagar $COUNT$ projetos", "description": "Confirmation prompt to delete multiple projects, where '$COUNT$' is a placeholder for the number of projects to be deleted.", "placeholders": { "count": { @@ -8310,7 +8346,7 @@ } }, "deleteProjectsDialogMessage": { - "message": "A exclusão de projetos é permanente e irreversível.", + "message": "Apagar projetos é permanente e irreversível.", "description": "This message is displayed in a dialog box as a warning before proceeding with project deletion." }, "projectsNoItemsTitle": { @@ -8326,7 +8362,7 @@ "description": "Indicates that user confirmation is required for an action to proceed." }, "bulkDeleteProjectsErrorMessage": { - "message": "Os seguintes projetos não puderam ser excluídos:", + "message": "Os seguintes projetos não puderam ser apagados:", "description": "Message to be displayed when there is an error during bulk project deletion." }, "softDeleteSuccessToast": { @@ -8334,14 +8370,14 @@ "description": "Notification to be displayed when a secret is successfully sent to the trash." }, "hardDeleteSuccessToast": { - "message": "Segredo excluído permanentemente" + "message": "Segredo apagado permanentemente" }, "accessTokens": { "message": "Tokens de acesso", "description": "Title for the section displaying access tokens." }, "createAccessToken": { - "message": "Create access token", + "message": "Criar token de acesso", "description": "Button label for creating a new access token." }, "expires": { @@ -8353,7 +8389,7 @@ "description": "Label for the access level of an access token (Read only)." }, "accessTokensNoItemsTitle": { - "message": "Nenhum token de acesso para exibir", + "message": "Nenhum token de acesso para mostrar", "description": "Title to be displayed when there are no access tokens to display in the list." }, "accessTokensNoItemsDesc": { @@ -8381,7 +8417,7 @@ "description": "A unique string that gives a client application (eg. CLI) access to a secret or set of secrets." }, "accessTokenExpirationRequired": { - "message": "Data de validade requerida", + "message": "Data de validade necessária", "description": "Error message indicating that an expiration date for the access token must be set." }, "accessTokenCreatedAndCopied": { @@ -8410,10 +8446,10 @@ "message": "Submenu" }, "from": { - "message": "De" + "message": "Desde" }, "to": { - "message": "Para" + "message": "Até" }, "member": { "message": "Membro" @@ -8422,7 +8458,7 @@ "message": "Atualizar" }, "plusNMore": { - "message": "+ $QUANTITY$ mais", + "message": "+ mais $QUANTITY$", "placeholders": { "quantity": { "content": "$1", @@ -8434,34 +8470,34 @@ "message": "Informações do grupo" }, "editGroupMembersDesc": { - "message": "Conceder acesso às coleções atribuídas ao grupo." + "message": "Conceda acesso dos conjuntos atribuídos do grupo aos membros." }, "editGroupCollectionsDesc": { - "message": "Conceder acesso às coleções adicionando-as a este grupo." + "message": "Conceda acesso aos conjuntos adicionando-os a este grupo." }, "restrictedCollectionAssignmentDesc": { - "message": "Você só pode atribuir coleções que você gerencia." + "message": "Você só pode atribuir conjuntos que você gerencia." }, "selectMembers": { "message": "Selecionar membros" }, "selectCollections": { - "message": "Selecionar coleções" + "message": "Selecionar conjuntos" }, "role": { - "message": "Função" + "message": "Cargo" }, "removeMember": { "message": "Remover membro" }, "collection": { - "message": "Coleção" + "message": "Conjunto" }, "noCollection": { - "message": "Sem coleções" + "message": "Sem conjunto" }, "noCollectionsAdded": { - "message": "Nenhuma coleção adicionada" + "message": "Nenhum conjunto adicionado" }, "noMembersAdded": { "message": "Nenhum membro adicionado" @@ -8473,7 +8509,7 @@ "message": "Grupo" }, "domainVerification": { - "message": "Verificação de Domínio" + "message": "Verificação de domínio" }, "newDomain": { "message": "Novo domínio" @@ -8482,13 +8518,13 @@ "message": "Sem domínio" }, "noDomainsSubText": { - "message": "Conectar um domínio permite que os membros pulem o campo identificador SSO durante o Login com SSO." + "message": "Conectar um domínio permite que os membros pulem o campo do identificador de SSO durante a autenticação única." }, "copyDnsTxtRecord": { - "message": "Copiar registro do DNS TXT" + "message": "Copiar registro TXT do DNS" }, "dnsTxtRecord": { - "message": "Registro DNS TXT" + "message": "Registro TXT do DNS" }, "dnsTxtRecordInputHint": { "message": "Copie e cole o registro TXT no seu provedor de DNS." @@ -8521,10 +8557,10 @@ "message": "Nome" }, "domainStatusTh": { - "message": "Status" + "message": "Estado" }, "lastChecked": { - "message": "Última verificação:" + "message": "Última verificação" }, "editDomain": { "message": "Editar domínio" @@ -8551,25 +8587,25 @@ } }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verificação necessária para esta ação. Defina um PIN para continuar." + "message": "Verificação necessária para esta ação. Configure um PIN para continuar." }, "setPin": { - "message": "Definir PIN" + "message": "Configurar PIN" }, "verifyWithBiometrics": { - "message": "Desbloquear com a biometria" + "message": "Verificar com biometria" }, "awaitingConfirmation": { "message": "Aguardando confirmação" }, "couldNotCompleteBiometrics": { - "message": "Não foi possível completar a biometria." + "message": "Não foi possível concluir a biometria." }, "needADifferentMethod": { "message": "Precisa de um método diferente?" }, "useMasterPassword": { - "message": "Usar a senha principal" + "message": "Usar senha principal" }, "usePin": { "message": "Usar PIN" @@ -8578,7 +8614,7 @@ "message": "Usar biometria" }, "enterVerificationCodeSentToEmail": { - "message": "Digite o código de verificação que foi enviado para o seu e-mail." + "message": "Digite o código de verificação enviado para o seu e-mail." }, "resendCode": { "message": "Reenviar código" @@ -8590,40 +8626,40 @@ "message": "Grupo/Membro" }, "selectGroupsAndMembers": { - "message": "Selecione grupos e membros" + "message": "Selecionar grupos e membros" }, "selectGroups": { - "message": "Selecione grupos" + "message": "Selecionar grupos" }, "userPermissionOverrideHelperDesc": { - "message": "Permissões definidas para um membro substituirão as permissões definidas pelo grupo desse membro." + "message": "As permissões configuradas para um membro substituirão as permissões configuradas pelo grupo desse membro." }, "noMembersOrGroupsAdded": { "message": "Nenhum membro ou grupo adicionado" }, "deleted": { - "message": "Excluído" + "message": "Apagado" }, "memberStatusFilter": { - "message": "Filtro de status de membros" + "message": "Filtro do estado de membros" }, "inviteMember": { "message": "Convidar membro" }, "addSponsorship": { - "message": "Add sponsorship" + "message": "Adicionar financiamento" }, "needsConfirmation": { "message": "Precisa de confirmação" }, "memberRole": { - "message": "Função de membro" + "message": "Cargo do membro" }, "moreFromBitwarden": { "message": "Mais do Bitwarden" }, "switchProducts": { - "message": "Trocar Produtos" + "message": "Trocar de produto" }, "freeOrgInvLimitReachedManageBilling": { "message": "Organizações gratuitas podem ter até $SEATCOUNT$ membros. Faça upgrade para um plano pago para convidar mais membros.", @@ -8644,7 +8680,7 @@ } }, "teamsStarterPlanInvLimitReachedManageBilling": { - "message": "Planos de Times Starter podem ter até $SEATCOUNT$ membros. Atualize para o seu plano para convidar mais membros.", + "message": "Planos do Equipes Iniciantes podem ter até $SEATCOUNT$ membros. Faça upgrade do seu plano para convidar mais membros.", "placeholders": { "seatcount": { "content": "$1", @@ -8653,7 +8689,7 @@ } }, "teamsStarterPlanInvLimitReachedNoManageBilling": { - "message": "Planos para Equipes Iniciantes podem ter até $SEATCOUNT$ membros. Entre em contato com o proprietário da organização para melhorar o seu plano e convidar mais membros.", + "message": "Planos do Equipes Iniciantes podem ter até $SEATCOUNT$ membros. Entre em contato com o proprietário da organização para fazer upgrade do seu plano e convidar mais membros.", "placeholders": { "seatcount": { "content": "$1", @@ -8662,7 +8698,7 @@ } }, "freeOrgMaxCollectionReachedManageBilling": { - "message": "Organizações gratuitas podem ter até $COLLECTIONCOUNT$ coleções. Faça o upgrade para um plano pago para adicionar mais coleções.", + "message": "Organizações gratuitas podem ter até $COLLECTIONCOUNT$ conjuntos. Faça upgrade para um plano pago para adicionar mais conjuntos.", "placeholders": { "COLLECTIONCOUNT": { "content": "$1", @@ -8671,7 +8707,7 @@ } }, "freeOrgMaxCollectionReachedNoManageBilling": { - "message": "Organizações gratuitas podem ter até $COLLECTIONCOUNT$ membros. Entre em contato com o proprietário da sua organização para fazer upgrade.", + "message": "Organizações gratuitas podem ter até $COLLECTIONCOUNT$ conjuntos. Entre em contato com o proprietário da sua organização para fazer upgrade.", "placeholders": { "COLLECTIONCOUNT": { "content": "$1", @@ -8686,10 +8722,10 @@ "message": "Exportar dados" }, "exportingOrganizationSecretDataTitle": { - "message": "Exportando dados secretos da organização" + "message": "Exportando dados de segredos da organização" }, "exportingOrganizationSecretDataDescription": { - "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Itens do cofre pessoal e itens de outras organizações não serão incluídos.", + "message": "Apenas os dados do Gerenciador de Segredos associados com $ORGANIZATION$ serão exportados. Itens em outros produtos ou de outras organizações não serão incluídos.", "placeholders": { "ORGANIZATION": { "content": "$1", @@ -8698,28 +8734,28 @@ } }, "fileUpload": { - "message": "Enviar de arquivo" + "message": "Envio de arquivo" }, "upload": { - "message": "Fazer upload" + "message": "Enviar" }, "acceptedFormats": { - "message": "Formatos Aceitos:" + "message": "Formatos aceitos:" }, "copyPasteImportContents": { - "message": "Copiar e colar conteúdo para importação:" + "message": "Copie e cole o conteúdo de importação:" }, "or": { "message": "ou" }, "unlockWithBiometrics": { - "message": "Desbloquear com a biometria" + "message": "Desbloquear com biometria" }, "unlockWithPin": { - "message": "Desbloquear com o PIN" + "message": "Desbloquear com PIN" }, "unlockWithMasterPassword": { - "message": "Desbloquear com a senha principal" + "message": "Desbloquear com senha principal" }, "licenseAndBillingManagement": { "message": "Gerenciamento da licença e faturamento" @@ -8728,10 +8764,10 @@ "message": "Sincronização automática" }, "manualUpload": { - "message": "Upload manual" + "message": "Envio manual" }, "manualBillingTokenUploadDesc": { - "message": "Se você não deseja optar pela sincronização de faturamento, carregue sua licença manualmente aqui. Isto não desbloqueará automaticamente os patrocinadores das Famílias." + "message": "Se você não deseja optar pela sincronização de faturamento, envie sua licença manualmente aqui. Isto não desbloqueará automaticamente os financiamentos do Famílias." }, "syncLicense": { "message": "Sincronizar licença" @@ -8746,13 +8782,13 @@ "message": "Última sincronização de licença" }, "billingSyncHelp": { - "message": "Ajuda da Sincronização de faturamento" + "message": "Ajuda com sincronização de faturamento" }, "licensePaidFeaturesHelp": { - "message": "Ajuda dos recursos de licença paga" + "message": "Ajuda com recursos de licença paga" }, "selfHostGracePeriodHelp": { - "message": "Após a expiração da assinatura, você tem 60 dias para aplicar um arquivo de licença atualizado à sua organização. Fim do período de carência $GRACE_PERIOD_END_DATE$.", + "message": "Após a expiração da assinatura, você tem 60 dias para aplicar um arquivo de licença atualizado à sua organização. O fim do período de tolerância é em $GRACE_PERIOD_END_DATE$.", "placeholders": { "GRACE_PERIOD_END_DATE": { "content": "$1", @@ -8764,13 +8800,13 @@ "message": "Enviar licença" }, "projectPeopleDescription": { - "message": "Conceder acesso a este projeto ou grupos de pessoas." + "message": "Conceda acesso ao projeto para grupos ou pessoas." }, "projectPeopleSelectHint": { "message": "Digite ou selecione pessoas ou grupos" }, "projectServiceAccountsDescription": { - "message": "Conceder acesso a contas de serviço a este projeto." + "message": "Conceda acesso a contas de serviço a este projeto." }, "projectServiceAccountsSelectHint": { "message": "Digite ou selecione contas de serviço" @@ -8782,13 +8818,13 @@ "message": "Adicione contas de serviço para conceder acesso" }, "serviceAccountPeopleDescription": { - "message": "Conceder acesso a esta conta de serviço a grupos ou pessoas." + "message": "Conceda acesso a esta conta de serviço a grupos ou pessoas." }, "serviceAccountProjectsDescription": { - "message": "Atribuir projetos para esta conta de serviço. " + "message": "Atribua projetos a esta conta de serviço. " }, "serviceAccountEmptyProjectAccesspolicies": { - "message": "Adicionar projetos para conceder acesso" + "message": "Adicione projetos para conceder acesso" }, "canReadWrite": { "message": "Pode ler e gravar" @@ -8797,22 +8833,22 @@ "message": "Grupo/Usuário" }, "kdfSettingsChangeLogoutWarning": { - "message": "O processo desconectará você de todas as sessões ativas. Você precisará iniciar a sessão novamente e concluir o login em duas etapas, se houver. Recomendamos exportar seu cofre antes de alterar suas configurações de criptografia para evitar perda de dados." + "message": "O processo desconectará você de todas as sessões ativas. Você precisará se conectar novamente, e fazer a autenticação em duas etapas, se configurada. Recomendamos exportar seu cofre antes de alterar suas configurações de criptografia para evitar perda de dados." }, "secretsManager": { "message": "Gerenciador de Segredos" }, "secretsManagerAccessDescription": { - "message": "Ative o acesso do usuário ao Gerenciador Secretos." + "message": "Ative o acesso de usuários ao Gerenciador de Segredos." }, "userAccessSecretsManagerGA": { - "message": "Este usuário pode acessar o gerente secreto" + "message": "Este usuário pode acessar o Gerenciador de Segredos" }, "important": { "message": "Importante:" }, "viewAll": { - "message": "Visualizar tudo" + "message": "Ver tudo" }, "showingPortionOfTotal": { "message": "Mostrando $PORTION$ de $TOTAL$", @@ -8837,7 +8873,7 @@ "message": "Ocorreu um erro ao tentar ler o arquivo de importação" }, "accessedSecretWithId": { - "message": "Accessed a secret with identifier: $SECRET_ID$", + "message": "Acessou um segredo com o identificador: $SECRET_ID$", "placeholders": { "secret_id": { "content": "$1", @@ -8846,7 +8882,7 @@ } }, "accessedSecret": { - "message": "$SECRET_ID$ secreto acessado.", + "message": "Acessou o segredo $SECRET_ID$.", "placeholders": { "secret_id": { "content": "$1", @@ -8855,7 +8891,7 @@ } }, "editedSecretWithId": { - "message": "Edited a secret with identifier: $SECRET_ID$", + "message": "Editou um segredo com o identificador: $SECRET_ID$", "placeholders": { "secret_id": { "content": "$1", @@ -8864,7 +8900,7 @@ } }, "deletedSecretWithId": { - "message": "Deleted a secret with identifier: $SECRET_ID$", + "message": "Apagou um segredo com o identificador: $SECRET_ID$", "placeholders": { "secret_id": { "content": "$1", @@ -8873,7 +8909,7 @@ } }, "permanentlyDeletedSecretWithId": { - "message": "Permanently deleted a secret with identifier: $SECRET_ID$", + "message": "Apagou permanentemente um segredo com o identificador: $SECRET_ID$", "placeholders": { "secret_id": { "content": "$1", @@ -8882,7 +8918,7 @@ } }, "restoredSecretWithId": { - "message": "Restored a secret with identifier: $SECRET_ID$", + "message": "Restaurou o segredo com o identificador: $SECRET_ID$", "placeholders": { "secret_id": { "content": "$1", @@ -8891,7 +8927,7 @@ } }, "createdSecretWithId": { - "message": "Created a new secret with identifier: $SECRET_ID$", + "message": "Criou um segredo com o identificador: $SECRET_ID$", "placeholders": { "secret_id": { "content": "$1", @@ -8900,7 +8936,7 @@ } }, "accessedProjectWithIdentifier": { - "message": "Accessed a project with identifier: $PROJECT_ID$.", + "message": "Acessou um projeto com o identificador: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8909,7 +8945,7 @@ } }, "nameUnavailableProjectDeleted": { - "message": "Deleted project Id: $PROJECT_ID$", + "message": "Apagou o projeto com ID: $PROJECT_ID$", "placeholders": { "project_id": { "content": "$1", @@ -8918,7 +8954,7 @@ } }, "nameUnavailableSecretDeleted": { - "message": "Deleted secret Id: $SECRET_ID$", + "message": "Apagou segredo com o ID: $SECRET_ID$", "placeholders": { "secret_id": { "content": "$1", @@ -8927,7 +8963,7 @@ } }, "nameUnavailableServiceAccountDeleted": { - "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "message": "Apagou conta de máquina com o ID: $SERVICE_ACCOUNT_ID$", "placeholders": { "service_account_id": { "content": "$1", @@ -8936,7 +8972,7 @@ } }, "editedProjectWithId": { - "message": "Edited a project with identifier: $PROJECT_ID$", + "message": "Editou um projeto com o identificador: $PROJECT_ID$", "placeholders": { "project_id": { "content": "$1", @@ -8945,7 +8981,7 @@ } }, "addedUserToServiceAccountWithId": { - "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "Adicionou o usuário: $USER_ID$ a conta de máquina com identificador: $SERVICE_ACCOUNT_ID$", "placeholders": { "user_id": { "content": "$1", @@ -8958,7 +8994,7 @@ } }, "removedUserToServiceAccountWithId": { - "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "Removeu o usuário: $USER_ID$ da conta de máquina com identificador: $SERVICE_ACCOUNT_ID$", "placeholders": { "user_id": { "content": "$1", @@ -8971,7 +9007,7 @@ } }, "removedGroupFromServiceAccountWithId": { - "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "Removeu o grupo: $GROUP_ID$ da conta de máquina com identificador: $SERVICE_ACCOUNT_ID$", "placeholders": { "group_id": { "content": "$1", @@ -8984,7 +9020,7 @@ } }, "serviceAccountCreatedWithId": { - "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "Criou a conta de máquina com identificador: $SERVICE_ACCOUNT_ID$", "placeholders": { "service_account_id": { "content": "$1", @@ -8993,7 +9029,7 @@ } }, "addedGroupToServiceAccountId": { - "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "Adicionou o grupo: $GROUP_ID$ a conta de máquina com identificador: $SERVICE_ACCOUNT_ID$", "placeholders": { "group_id": { "content": "$1", @@ -9006,7 +9042,7 @@ } }, "serviceAccountDeletedWithId": { - "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "Apagou a conta de máquina com identificador: $SERVICE_ACCOUNT_ID$", "placeholders": { "service_account_id": { "content": "$1", @@ -9015,7 +9051,7 @@ } }, "deletedProjectWithId": { - "message": "Deleted a project with identifier: $PROJECT_ID$", + "message": "Apagou um projeto com o identificador: $PROJECT_ID$", "placeholders": { "project_id": { "content": "$1", @@ -9024,7 +9060,7 @@ } }, "createdProjectWithId": { - "message": "Created a new project with identifier: $PROJECT_ID$", + "message": "Criou um projeto com o identificador: $PROJECT_ID$", "placeholders": { "project_id": { "content": "$1", @@ -9040,13 +9076,13 @@ "message": "Criar uma conta" }, "createSecret": { - "message": "Crie um segredo" + "message": "Criar um segredo" }, "createProject": { "message": "Criar um projeto" }, "createServiceAccount": { - "message": "Criar conta de serviço" + "message": "Criar uma conta de serviço" }, "downloadThe": { "message": "Baixe o", @@ -9062,7 +9098,7 @@ "message": "Vamos começar" }, "complete": { - "message": "$COMPLETED$/$TOTAL$ Completos", + "message": "$COMPLETED$ concluídos dos $TOTAL$", "placeholders": { "COMPLETED": { "content": "$1", @@ -9102,13 +9138,13 @@ "message": "Remover pessoas de uma conta de serviço não remove os tokens de acesso que criaram. Para a melhor prática de segurança, é recomendável revogar os tokens de acesso criados por pessoas removidas de uma conta de serviço." }, "smAccessRemovalWarningProjectTitle": { - "message": "Remover acesso para esse projeto" + "message": "Remova o acesso a esse projeto" }, "smAccessRemovalWarningProjectMessage": { "message": "Esta ação removerá seu acesso ao projeto." }, "smAccessRemovalWarningSaTitle": { - "message": "Remover acesso a essa conta de serviço" + "message": "Remova o acesso a essa conta de serviço" }, "smAccessRemovalWarningSaMessage": { "message": "Essa ação removerá seu acesso à conta de serviço." @@ -9117,16 +9153,16 @@ "message": "Remover acesso" }, "checkForBreaches": { - "message": "Verificar se essa senha aparece em vazamento de dados conhecidos" + "message": "Conferir vazamentos de dados conhecidos por esta senha" }, "exposedMasterPassword": { "message": "Senha principal exposta" }, "exposedMasterPasswordDesc": { - "message": "A senha foi encontrada em violação de dados. Use uma senha única para proteger sua conta. Tem certeza de que deseja usar uma senha exposta?" + "message": "Senha encontrada em um vazamento de dados. Use uma senha única para proteger sua conta. Tem certeza que quer usar uma senha exposta?" }, "weakAndExposedMasterPassword": { - "message": "Senha Mestra Fraca e Exposta" + "message": "Senha principal fraca e comprometida" }, "weakAndBreachedMasterPasswordDesc": { "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" @@ -9157,31 +9193,31 @@ "message": "Descartar" }, "notAvailableForFreeOrganization": { - "message": "Este recurso não está disponível para organizações gratuitas. Entre em contato com o seu proprietário para atualizar." + "message": "Este recurso não está disponível para organizações gratuitas. Entre em contato com o seu proprietário para fazer upgrade." }, "smProjectSecretsNoItemsNoAccess": { "message": "Entre em contato com o administrador da sua organização para gerenciar segredos para este projeto.", "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": "Exigir que os membros existentes alterem suas senhas" + "message": "Exija que membros existentes alterem suas senhas" }, "smProjectDeleteAccessRestricted": { - "message": "Você não tem permissão para excluir este projeto", + "message": "Você não tem permissão para apagar este projeto", "description": "The individual description shown to the user when the user doesn't have access to delete a project." }, "smProjectsDeleteBulkConfirmation": { - "message": "Os projetos a seguir não podem ser excluídos. Deseja continuar?", + "message": "Os projetos a seguir não podem ser apagados. Deseja continuar?", "description": "The message shown to the user when bulk deleting projects and the user doesn't have access to some projects." }, "updateKdfSettings": { - "message": "Atualizar as definições do KDF" + "message": "Atualizar configurações da KDF" }, "loginInitiated": { "message": "Autenticação iniciada" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Lembre-se deste dispositivo para permanecer conectado" + "message": "Lembrar deste dispositivo para tornar futuras autenticações simples" }, "deviceApprovalRequired": { "message": "Aprovação do dispositivo necessária. Selecione uma opção de aprovação abaixo:" @@ -9205,35 +9241,35 @@ "message": "Solicitar aprovação do administrador" }, "unableToCompleteLogin": { - "message": "Unable to complete login" + "message": "Não é possível concluir a autenticação" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { - "message": "You need to log in on a trusted device or ask your administrator to assign you a password." + "message": "Você precisa se conectar com um dispositivo confiado ou solicitar ao administrador que lhe atribua uma senha." }, "trustedDeviceEncryption": { - "message": "Criptografia de dispositivo confiável" + "message": "Criptografia de dispositivo confiado" }, "trustedDevices": { - "message": "Dispositivos confiáveis" + "message": "Dispositivos confiados" }, "memberDecryptionOptionTdeDescPart1": { - "message": "Os membros não precisarão de uma senha principal ao fazer login com SSO. A senha principal é substituída por uma chave de criptografia armazenada no dispositivo, fazendo com que esse dispositivo seja confiável. O primeiro dispositivo que um membro cria sua conta e os logins serão confiáveis. Novos dispositivos precisarão ser aprovados por um dispositivo confiável existente ou por um administrador. O", + "message": "Os membros não precisarão de uma senha principal ao se conectar com SSO. A senha principal é substituída por uma chave de criptografia armazenada no dispositivo, fazendo com que esse dispositivo seja confiado. O primeiro dispositivo que um membro cria sua conta e se conecta serão confiados. Novos dispositivos precisarão ser aprovados por um dispositivo confiado 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.'" }, "memberDecryptionOptionTdeDescLink1": { - "message": "organização única", + "message": "política 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.'" }, "memberDecryptionOptionTdeDescPart2": { - "message": "política,", + "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 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink2": { - "message": "Necessário SSO", + "message": "política 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.'" }, "memberDecryptionOptionTdeDescPart3": { - "message": "política, e", + "message": "SSO obrigatória, e a política 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.'" }, "memberDecryptionOptionTdeDescLink3": { @@ -9241,19 +9277,19 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device 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": "política será ativada quando esta opção for utilizada.", + "message": "ativarão 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 que você defina uma senha principal.", + "message": "As permissões da sua organização foram atualizadas, exigindo que você configure uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Sua organização requer que você defina uma senha principal.", + "message": "Sua organização requer que você configure uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "fora do $TOTAL$", + "message": "dos $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -9262,7 +9298,7 @@ } }, "notFound": { - "message": "$RESOURCE$ não encontrado", + "message": "$RESOURCE$ não encontrado(a)", "placeholders": { "resource": { "content": "$1", @@ -9288,7 +9324,7 @@ "message": "Ativar acesso" }, "bulkEnableSecretsManagerDescription": { - "message": "Conceda aos seguintes membros acesso ao Gerenciador de Segredos. A função concedida no Gerenciador de Senhas será aplicada ao Gerenciador de Segredos.", + "message": "Conceda aos seguintes membros acesso ao Gerenciador de Segredos. O cargo concedido no Gerenciador de Senhas será aplicado ao Gerenciador de Segredos.", "description": "This description is shown to an admin when they are attempting to add more users to Secrets Manager." }, "activateSecretsManager": { @@ -9299,10 +9335,10 @@ "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": "Aprovações do dispositivo" + "message": "Aprovações de dispositivo" }, "deviceApprovalsDesc": { - "message": "Aprovar as solicitações de login abaixo para permitir que o membro solicitante termine o login. Solicitações não aprovadas expiram após 1 semana. Verifique as informações do membro antes de aprovar." + "message": "Aprove as solicitações de acesso abaixo para permitir que o membro solicitante se conecte. Solicitações não aprovadas expiram após 1 semana. Verifique as informações do membro antes de aprovar." }, "deviceInfo": { "message": "Informações do dispositivo" @@ -9314,7 +9350,7 @@ "message": "Negar todas as solicitações" }, "denyRequest": { - "message": "Negar pedido" + "message": "Negar solicitação" }, "approveRequest": { "message": "Aprovar solicitação" @@ -9332,25 +9368,25 @@ "message": "Tem certeza de que deseja remover este dispositivo?" }, "noDeviceRequests": { - "message": "Nenhum pedido de dispositivo" + "message": "Nenhuma solicitação de dispositivo" }, "noDeviceRequestsDesc": { - "message": "Os pedidos de aprovação de dispositivo de membro aparecerão aqui" + "message": "As solicitações de aprovação de dispositivo dos membros aparecerão aqui" }, "loginRequestDenied": { - "message": "Solicitação de login negada" + "message": "Solicitação de acesso negada" }, "allLoginRequestsDenied": { - "message": "Todas as solicitações de login negadas" + "message": "Todas as solicitações de autenticação foram negadas" }, "loginRequestApproved": { - "message": "Solicitação de login aprovada" + "message": "Solicitação de acesso aprovada" }, "removeOrgUserNoMasterPasswordTitle": { "message": "A conta não tem uma senha principal" }, "removeOrgUserNoMasterPasswordDesc": { - "message": "Remover $USER$ sem definir uma senha principal pode restringir o acesso deles à conta toda. Tem certeza de que deseja continuar?", + "message": "Remover $USER$ sem configurar uma senha principal para eles pode restringir o acesso à conta toda. Tem certeza de que deseja continuar?", "placeholders": { "user": { "content": "$1", @@ -9359,10 +9395,10 @@ } }, "noMasterPassword": { - "message": "Nenhuma senha principal" + "message": "Sem senha principal" }, "removeMembersWithoutMasterPasswordWarning": { - "message": "Remover membros que não têm senhas principais sem definir uma para eles pode restringir o acesso à sua conta completa." + "message": "Remover membros que não têm senhas principais sem configurar uma para eles pode restringir o acesso à conta toda." }, "approvedAuthRequest": { "message": "Dispositivo aprovado para $ID$.", @@ -9386,10 +9422,10 @@ "message": "Aprovação do dispositivo solicitada." }, "tdeOffboardingPasswordSet": { - "message": "Usuário definiu uma senha principal durante o offboard TDE." + "message": "Usuário configurou uma senha principal durante o offboarding da criptografia de dispositivo confiado." }, "startYour7DayFreeTrialOfBitwardenFor": { - "message": "Comece o seu período de teste gratuito de 7 dias do Bitwarden para $ORG$", + "message": "Comece o seu teste grátis de 7 dias do Bitwarden para $ORG$", "placeholders": { "org": { "content": "$1", @@ -9398,7 +9434,7 @@ } }, "startYour7DayFreeTrialOfBitwardenSecretsManagerFor": { - "message": "Inicie o seu período de teste gratuito de 7 dias do Bitwarden Secrets Manager para $ORG$", + "message": "Comece o seu teste grátis de 7 dias do Bitwarden Gerenciador de Segredos para $ORG$", "placeholders": { "org": { "content": "$1", @@ -9410,10 +9446,10 @@ "message": "Avançar" }, "ssoLoginIsRequired": { - "message": "Login SSO é obrigatório" + "message": "Autenticação com SSO é necessário" }, "selectedRegionFlag": { - "message": "Sinalização de região selecionada" + "message": "Bandeira da região selecionada" }, "accountSuccessfullyCreated": { "message": "Conta criada com sucesso!" @@ -9422,10 +9458,10 @@ "message": "Aprovação do administrador necessária" }, "adminApprovalRequestSentToAdmins": { - "message": "Seu pedido foi enviado para seu administrador." + "message": "Sua solicitação foi enviada para seu administrador." }, "troubleLoggingIn": { - "message": "Problemas em efetuar login?" + "message": "Problemas para acessar?" }, "loginApproved": { "message": "Autenticação aprovada" @@ -9434,16 +9470,16 @@ "message": "E-mail do usuário ausente" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Endereço eletrônico de usuário ativo não encontrado. Você será desconectado" + "message": "E-mail do usuário ativo não encontrado. Desconectando você." }, "deviceTrusted": { - "message": "Dispositivo confiável" + "message": "Dispositivo confiado" }, "inviteUsers": { "message": "Convidar usuários" }, "secretsManagerForPlan": { - "message": "Gerenciador de segredos para $PLAN$", + "message": "Gerenciador de Segredos para $PLAN$", "placeholders": { "plan": { "content": "$1", @@ -9455,7 +9491,7 @@ "message": "Para equipes de engenharia e DevOps gerenciar segredos durante todo o ciclo de vida do desenvolvimento de software." }, "free2PersonOrganization": { - "message": "Organizações gratuitas com 2 pessoas" + "message": "Organizações grátis de 2 pessoas" }, "unlimitedSecrets": { "message": "Segredos ilimitados" @@ -9491,10 +9527,10 @@ } }, "subscribeToSecretsManager": { - "message": "Assine o Gerenciador de Segredos" + "message": "Assinar o Gerenciador de Segredos" }, "addSecretsManagerUpgradeDesc": { - "message": "Adicione o Gerenciador de Segredos ao seu plano atualizado para manter o acesso a todos os segredos criados com seu plano anterior." + "message": "Adicione o Gerenciador de Segredos ao seu plano com upgrade para manter o acesso a todos os segredos criados com seu plano anterior." }, "additionalServiceAccounts": { "message": "Contas de serviço adicionais" @@ -9518,94 +9554,94 @@ } }, "collectionManagement": { - "message": "Gerenciamento da coleção" + "message": "Gerenciamento de conjuntos" }, "collectionManagementDescription": { - "message": "Configure the collection behavior for the organization" + "message": "Configure o comportamento dos conjuntos da organização" }, "allowAdminAccessToAllCollectionItemsDescription": { - "message": "Allow owners and admins to manage all collections and items from the Admin Console" + "message": "Permitir que proprietários e administradores gerenciem todos os conjuntos e itens pelo painel de administração" }, "restrictCollectionCreationDescription": { - "message": "Restrict collection creation to owners and admins" + "message": "Restringir a criação de conjuntos a proprietários e administradores" }, "restrictCollectionDeletionDescription": { - "message": "Restrict collection deletion to owners and admins" + "message": "Restringir o apagamento de conjuntos a proprietários e administradores" }, "restrictItemDeletionDescriptionStart": { - "message": "Restrict item deletion to members with the ", + "message": "Restringir o apagamento de itens a membros com a permissão de ", "description": "This will be used as part of a larger sentence, broken up to allow styling of the middle portion. Full sentence: 'Restrict item deletion to members with the [Manage collection] permission'" }, "restrictItemDeletionDescriptionEnd": { - "message": " permission", + "message": "‎", "description": "This will be used as part of a larger sentence, broken up to allow styling of the middle portion. Full sentence: 'Restrict item deletion to members with the [Manage collection] permission'" }, "updatedCollectionManagement": { - "message": "Configuração da gestão de coleção atualizada" + "message": "Configuração de gerenciamento de conjuntos atualizada" }, "passwordManagerPlanPrice": { - "message": "Preço do plano do Gerenciador de Senha" + "message": "Preço do plano do Gerenciador de Senhas" }, "secretsManagerPlanPrice": { "message": "Preço do plano do Gerenciador de Segredos" }, "passwordManager": { - "message": "Gerenciador de senha" + "message": "Gerenciador de Senhas" }, "freeOrganization": { - "message": "Organização gratuita" + "message": "Organização Grátis" }, "limitServiceAccounts": { "message": "Limitar contas de serviço (opcional)" }, "limitServiceAccountsDesc": { - "message": "Defina um limite para suas contas de máquina. Quando este limite for atingido, você não poderá criar novas contas de máquina." + "message": "Configure um limite para suas contas de serviço. Quando este limite for atingido, você não poderá criar novas contas de serviço." }, "serviceAccountLimit": { "message": "Limite de contas de serviço (opcional)" }, "maxServiceAccountCost": { - "message": "Custo máximo da conta de serviço potencial" + "message": "Custo potencial máximo de contas de serviço" }, "loggedInExclamation": { "message": "Conectado!" }, "assignCollectionAccess": { - "message": "Atribuir acesso à coleção" + "message": "Atribuir acesso a conjuntos" }, "editedCollections": { - "message": "Coleções editadas" + "message": "Conjuntos editados" }, "baseUrl": { - "message": "URL do Servidor" + "message": "URL do servidor" }, "selfHostBaseUrl": { - "message": "URL do servidor auto-host", + "message": "URL do servidor auto-hospedado", "description": "Label for field requesting a self-hosted integration service URL" }, "alreadyHaveAccount": { "message": "Já tem uma conta?" }, "toggleSideNavigation": { - "message": "Ativar/desativar navegação lateral" + "message": "Habilitar navegação lateral" }, "skipToContent": { - "message": "Pular para o conteúdo" + "message": "Ir para o conteúdo" }, "managePermissionRequired": { - "message": "Pelo menos um membro ou grupo deve ter poder gerenciar a permissão." + "message": "Pelo menos um membro ou grupo deve ter permissões de gerenciamento." }, "typePasskey": { "message": "Chave de acesso" }, "passkeyNotCopied": { - "message": "A senha não será copiada" + "message": "A chave de acesso não será copiada" }, "passkeyNotCopiedAlert": { - "message": "A senha não será copiada para o item clonado. Deseja continuar clonando este item?" + "message": "A chave de acesso não será copiada para o item clonado. Deseja continuar clonando este item?" }, "modifiedCollectionManagement": { - "message": "Definição de gerenciamento da coleção $ID$ modificada.", + "message": "Modificou a configuração de gerenciamento de conjuntos $ID$.", "placeholders": { "id": { "content": "$1", @@ -9614,7 +9650,7 @@ } }, "limitCollectionCreationEnabled": { - "message": "Turned on Restrict collection creation setting $ID$.", + "message": "Ativou a configuração de restrição de criação de conjuntos $ID$.", "placeholders": { "id": { "content": "$1", @@ -9623,7 +9659,7 @@ } }, "limitCollectionCreationDisabled": { - "message": "Turned off Restrict collection creation setting $ID$.", + "message": "Desativou a configuração de restrição de criação de conjuntos $ID$.", "placeholders": { "id": { "content": "$1", @@ -9632,7 +9668,7 @@ } }, "limitCollectionDeletionEnabled": { - "message": "Turned on Restrict collection deletion setting $ID$.", + "message": "Ativou a configuração de restrição de apagamento de conjuntos $ID$.", "placeholders": { "id": { "content": "$1", @@ -9641,7 +9677,7 @@ } }, "limitCollectionDeletionDisabled": { - "message": "Turned off Restrict collection deletion setting $ID$.", + "message": "Desativou a configuração de restrição de apagamento de conjuntos $ID$.", "placeholders": { "id": { "content": "$1", @@ -9650,7 +9686,7 @@ } }, "limitItemDeletionEnabled": { - "message": "Turned on Restrict item deletion setting $ID$.", + "message": "Ativou a configuração de restrição de apagamento de itens $ID$.", "placeholders": { "id": { "content": "$1", @@ -9659,7 +9695,7 @@ } }, "limitItemDeletionDisabled": { - "message": "Turned off Restrict item deletion setting $ID$.", + "message": "Desativou a configuração de restrição de apagamento de itens $ID$.", "placeholders": { "id": { "content": "$1", @@ -9668,7 +9704,7 @@ } }, "allowAdminAccessToAllCollectionItemsEnabled": { - "message": "Turned on Allow owners and admins to manage all collections and items setting $ID$.", + "message": "Ativou a configuração de permitir que todos os proprietários e administradores gerenciem todos os conjuntos e itens $ID$.", "placeholders": { "id": { "content": "$1", @@ -9677,7 +9713,7 @@ } }, "allowAdminAccessToAllCollectionItemsDisabled": { - "message": "Turned off Allow owners and admins to manage all collections and items setting $ID$.", + "message": "Desativou a configuração de permitir que todos os proprietários e administradores gerenciem todos os conjuntos e itens $ID$.", "placeholders": { "id": { "content": "$1", @@ -9690,80 +9726,80 @@ "description": "This is followed a by a hyperlink to the help website." }, "installBrowserExtension": { - "message": "Instalar extensões de navegador" + "message": "Instalar extensão de navegador" }, "installBrowserExtensionDetails": { - "message": "Use a extensão para salvar rapidamente credenciais e formulários de autopreenchimento sem abrir o aplicativo web." + "message": "Use a extensão para salvar credenciais e preencher automaticamente formulários de forma rápida, sem abrir o aplicativo web." }, "projectAccessUpdated": { "message": "Acesso ao projeto atualizado" }, "unexpectedErrorSend": { - "message": "Ocorreu um erro inesperado ao carregar este Envio. Tente novamente mais tarde." + "message": "Ocorreu um erro inesperado ao carregar este Send. Tente novamente mais tarde." }, "seatLimitReached": { - "message": "Limite de lugares foi atingido" + "message": "Limite de vagas foi atingido" }, "contactYourProvider": { - "message": "Entre em contato com seu provedor para comprar lugares adicionais." + "message": "Entre em contato com seu provedor para comprar vagas adicionais." }, "seatLimitReachedContactYourProvider": { - "message": "O limite de lugares foi atingido. Entre em contato com seu provedor para comprar lugares adicionais." + "message": "O limite de vagas foi atingido. Entre em contato com seu provedor para comprar vagas adicionais." }, "collectionAccessRestricted": { - "message": "O acesso à coleção está restrito" + "message": "O acesso ao conjunto está restrito" }, "readOnlyCollectionAccess": { - "message": "Você não tem acesso para gerenciar esta coleção." + "message": "Você não tem acesso para gerenciar este conjunto." }, "grantManageCollectionWarningTitle": { - "message": "Faltando Permissões para Gerenciar Coleção" + "message": "Permissões de gerenciamento de conjunto ausentes" }, "grantManageCollectionWarning": { - "message": "Conceda permissões de Gerenciamento de Coleções para permitir o gerenciamento completo da coleção, incluindo a exclusão da coleção." + "message": "Conceda permissões de gerenciamento de conjuntos para permitir o gerenciamento completo do conjunto, incluindo o apagamento do conjunto." }, "grantCollectionAccess": { - "message": "Conceder acesso de grupos ou membros a esta coleção." + "message": "Conceda o acesso de grupos ou membros a este conjunto." }, "grantCollectionAccessMembersOnly": { - "message": "Conceder acesso a essa coleção." + "message": "Conceda acesso de membros a este conjunto." }, "adminCollectionAccess": { - "message": "Os administradores podem acessar e gerenciar coleções." + "message": "Os administradores podem acessar e gerenciar conjuntos." }, "serviceAccountAccessUpdated": { - "message": "Acesso à conta de serviço atualizado" + "message": "Acesso da conta de serviço atualizado" }, "commonImportFormats": { "message": "Formatos comuns", "description": "Label indicating the most common import formats" }, "uriMatchDefaultStrategyHint": { - "message": "URI match detection is how Bitwarden identifies autofill suggestions.", + "message": "Detecção de correspondência de URI é como o Bitwarden identifica sugestões de preenchimento automático.", "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, "regExAdvancedOptionWarning": { - "message": "\"Regular expression\" is an advanced option with increased risk of exposing credentials.", + "message": "\"Expressão regular\" é uma opção avançada com maior risco de exposição das credenciais.", "description": "Content for dialog which warns a user when selecting 'regular expression' matching strategy as a cipher match strategy" }, "startsWithAdvancedOptionWarning": { - "message": "\"Starts with\" is an advanced option with increased risk of exposing credentials.", + "message": "\"Começa com\" é uma opção avançada com maior risco de exposição de credenciais.", "description": "Content for dialog which warns a user when selecting 'starts with' matching strategy as a cipher match strategy" }, "uriMatchWarningDialogLink": { - "message": "More about match detection", + "message": "Mais sobre a detecção de correspondência", "description": "Link to match detection docs on warning dialog for advance match strategy" }, "uriAdvancedOption": { - "message": "Advanced options", + "message": "Opções avançadas", "description": "Advanced option placeholder for uri option component" }, "warningCapitalized": { - "message": "Warning", + "message": "Atenção", "description": "Warning (should maintain locale-relevant capitalization)" }, "maintainYourSubscription": { - "message": "Para manter sua assinatura para $ORG$, ", + "message": "Para manter sua assinatura de $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": { @@ -9777,13 +9813,13 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { - "message": "Informação da Organização" + "message": "Informações da organização" }, "confirmationDetails": { "message": "Detalhes da confirmação" }, "smFreeTrialThankYou": { - "message": "Obrigado por se inscrever no Bitwarden Secrets Manager!" + "message": "Obrigado por se inscrever no Bitwarden Gerenciador de Segredos!" }, "smFreeTrialConfirmationEmail": { "message": "Enviamos um e-mail de confirmação para seu e-mail em " @@ -9805,7 +9841,7 @@ "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "movingToAnotherTool": { - "message": "Trocando de ferramenta", + "message": "Migrando para outra ferramenta", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooDifficultToUse": { @@ -9813,21 +9849,29 @@ "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "notUsingEnough": { - "message": "Não está usando o suficiente", + "message": "Falta de uso", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooExpensive": { "message": "Muito caro", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Migrando para o plano grátis", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Migrando para uma organização grátis", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Grátis por 1 ano" }, "newWebApp": { - "message": "Bem-vindo ao novo e melhorado aplicativo da web. Saiba mais sobre o que mudou." + "message": "Boas-vindas ao novo e melhorado aplicativo web. Saiba mais sobre o que mudou." }, "releaseBlog": { - "message": "Ler o blog do lançamento" + "message": "Ler o post do lançamento no blog" }, "adminConsole": { "message": "Painel de administração" @@ -9842,37 +9886,37 @@ "message": "Você não pode se adicionar aos grupos." }, "cannotAddYourselfToCollections": { - "message": "Você não pode se adicionar às coleções." + "message": "Você não pode se adicionar aos conjuntos." }, "assign": { "message": "Atribuir" }, "assignTasks": { - "message": "Assign tasks" + "message": "Atribuir tarefas" }, "assignSecurityTasksToMembers": { - "message": "Send notifications to change passwords" + "message": "Envie notificações para alteração de senhas" }, "assignToCollections": { - "message": "Atribuir à coleções" + "message": "Atribuir a conjuntos" }, "assignToTheseCollections": { - "message": "Atribuir a estas coleções" + "message": "Atribuir a estes conjuntos" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Apenas membros da organização com acesso a essas coleções poderão ver o item." + "message": "Apenas membros da organização com acesso a estes conjuntos poderão ver o item." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Apenas membros da organização com acesso à essas coleções poderão ver os itens." + "message": "Apenas membros da organização com acesso a estes conjuntos poderão ver os itens." }, "selectCollectionsToAssign": { - "message": "Selecione as coleções para atribuir" + "message": "Selecione conjuntos a atribuir" }, "noCollectionsAssigned": { - "message": "Nenhuma coleção foi atribuída" + "message": "Nenhum conjunto foi atribuído" }, "successfullyAssignedCollections": { - "message": "Coleções atribuídas com sucesso" + "message": "Conjuntos atribuídos com sucesso" }, "bulkCollectionAssignmentWarning": { "message": "Você selecionou $TOTAL_COUNT$ itens. Você não pode atualizar $READONLY_COUNT$ destes itens porque você não tem permissão para edição.", @@ -9897,44 +9941,44 @@ "message": "Itens" }, "assignedSeats": { - "message": "Lugares Atribuídos" + "message": "Vagas atribuídas" }, "assigned": { - "message": "Atribuído" + "message": "Atribuídas" }, "used": { - "message": "Utilizado" + "message": "Usadas" }, "remaining": { - "message": "Restante" + "message": "Restantes" }, "unlinkOrganization": { "message": "Desvincular organização" }, "manageSeats": { - "message": "GERENCIAR LUGARES" + "message": "GERENCIAR VAGAS" }, "manageSeatsDescription": { - "message": "Os ajustes nos lugares serão refletidos no próximo ciclo de faturamento." + "message": "Os ajustes nas vagas serão refletidos no próximo ciclo de faturamento." }, "unassignedSeatsDescription": { - "message": "Assinatura de assentos desvinculada" + "message": "Vagas não atribuídas" }, "purchaseSeatDescription": { - "message": "Assentos adicionais comprados" + "message": "Vagas adicionais compradas" }, "assignedSeatCannotUpdate": { - "message": "Os assentos atribuídos não podem ser atualizados. Por favor, entre em contato com o proprietário da sua organização para obter assistência." + "message": "Os assentos atribuídos não podem ser atualizados. Entre em contato com o proprietário da sua organização para obter assistência." }, "subscriptionUpdateFailed": { "message": "Atualização da assinatura falhou" }, "trial": { - "message": "Avaliação", + "message": "Teste grátis", "description": "A subscription status label." }, "pastDue": { - "message": "Atrasado", + "message": "Atrasada", "description": "A subscription status label" }, "subscriptionExpired": { @@ -9942,7 +9986,7 @@ "description": "The date header used when a subscription is past due." }, "pastDueWarningForChargeAutomatically": { - "message": "Você tem um período de carência de $DAYS$ dias a contar da data de expiração para manter sua assinatura. Por favor, resolva as faturas vencidas até $SUSPENSION_DATE$.", + "message": "Você tem um período de tolerância de $DAYS$ dias a contar da data de expiração para manter sua assinatura. Resolva as faturas atrasadas até $SUSPENSION_DATE$.", "placeholders": { "days": { "content": "$1", @@ -9956,7 +10000,7 @@ "description": "A warning shown to the user when their subscription is past due and they are charged automatically." }, "pastDueWarningForSendInvoice": { - "message": "Você tem um período de licença de $DAYS$, contados a partir da data de vencimento da sua primeira fatura em aberto para manter a sua assinatura. Por favor, resolva as faturas vencidas até $SUSPENSION_DATE$.", + "message": "Você tem um período de tolerância de $DAYS$ dias para manter a sua assinatura, contados a partir da data de vencimento da sua primeira fatura em aberto. Resolva as faturas atrasadas até $SUSPENSION_DATE$.", "placeholders": { "days": { "content": "$1", @@ -9974,7 +10018,7 @@ "description": "The header of a warning box shown to a user whose subscription is unpaid." }, "toReactivateYourSubscription": { - "message": "Para reativar sua assinatura, por favor resolva as faturas vencidas.", + "message": "Para reativar sua assinatura, resolva as faturas atrasadas.", "description": "The body of a warning box shown to a user whose subscription is unpaid." }, "cancellationDate": { @@ -9997,19 +10041,19 @@ "description": "Title for creating a new machine account." }, "machineAccountsNoItemsMessage": { - "message": "Crie uma nova conta de máquina para começar a automatizar o acesso secreto.", + "message": "Crie uma conta de máquina para começar a automatizar o acesso aos segredos.", "description": "Message to encourage the user to start creating machine accounts." }, "machineAccountsNoItemsTitle": { - "message": "Ainda não há nada a ser exibido", + "message": "Nada para mostrar ainda", "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { - "message": "Excluir contas de máquina", + "message": "Apagar contas de serviço", "description": "Title for the action to delete one or multiple machine accounts." }, "deleteMachineAccount": { - "message": "Excluir conta de máquina", + "message": "Apagar conta de serviço", "description": "Title for the action to delete a single machine account." }, "viewMachineAccount": { @@ -10017,7 +10061,7 @@ "description": "Action to view the details of a machine account." }, "deleteMachineAccountDialogMessage": { - "message": "A exclusão da conta de máquina $MACHINE_ACCOUNT$ é permanente e irreversível.", + "message": "Apagar a conta de máquina $MACHINE_ACCOUNT$ é permanente e irreversível.", "placeholders": { "machine_account": { "content": "$1", @@ -10026,10 +10070,10 @@ } }, "deleteMachineAccountsDialogMessage": { - "message": "Excluir contas de máquina é permanente e irreversível." + "message": "Apagar contas de máquina é permanente e irreversível." }, "deleteMachineAccountsConfirmMessage": { - "message": "Excluir $COUNT$ contas de máquina", + "message": "Apagar $COUNT$ contas de máquina", "placeholders": { "count": { "content": "$1", @@ -10038,17 +10082,17 @@ } }, "deleteMachineAccountToast": { - "message": "Conta de máquina excluída" + "message": "Conta de máquina apagada" }, "deleteMachineAccountsToast": { - "message": "Contas de máquina excluídas" + "message": "Contas de máquina apagadas" }, "searchMachineAccounts": { - "message": "Pesquisar contas de máquina", + "message": "Buscar contas de máquina", "description": "Placeholder text for searching machine accounts." }, "editMachineAccount": { - "message": "Editar conta da máquina", + "message": "Editar conta de máquina", "description": "Title for editing a machine account." }, "machineAccountName": { @@ -10064,7 +10108,7 @@ "description": "Notifies that a machine account has been updated" }, "projectMachineAccountsDescription": { - "message": "Conceder acesso a contas de máquina a este projeto." + "message": "Conceda acesso do projeto a contas de máquina." }, "projectMachineAccountsSelectHint": { "message": "Digite ou selecione contas de máquina" @@ -10073,10 +10117,10 @@ "message": "Adicione contas de máquina para conceder acesso" }, "machineAccountPeopleDescription": { - "message": "Conceder acesso de grupos ou pessoas a esta conta de máquina." + "message": "Conceda acesso a esta conta de máquina a grupos ou pessoas." }, "machineAccountProjectsDescription": { - "message": "Atribuir projetos a esta conta de máquina. " + "message": "Atribua projetos a esta conta de máquina. " }, "createMachineAccount": { "message": "Criar uma conta de máquina" @@ -10091,7 +10135,7 @@ "message": "Esta ação irá remover seu acesso à conta de máquina." }, "machineAccountsIncluded": { - "message": "$COUNT$ contas de serviço incluídas", + "message": "$COUNT$ contas de máquina incluídas", "placeholders": { "count": { "content": "$1", @@ -10100,7 +10144,7 @@ } }, "additionalMachineAccountCost": { - "message": "$COST$ mensal para contas de máquina adicionais", + "message": "$COST$ mensais por contas de máquina adicionais", "placeholders": { "cost": { "content": "$1", @@ -10121,7 +10165,7 @@ } }, "addAdditionalMachineAccounts": { - "message": "Você pode adicionar contas de máquina extras por $COST$ mensais.", + "message": "Você pode adicionar contas de máquina adicionais por $COST$ mensais.", "placeholders": { "cost": { "content": "$1", @@ -10133,28 +10177,28 @@ "message": "Limitar contas de máquina (opcional)" }, "limitMachineAccountsDesc": { - "message": "Defina um limite para suas contas de máquina. Quando este limite for atingido, você não poderá criar novas contas de máquina." + "message": "Configure um limite para suas contas de máquina. Quando este limite for atingido, você não poderá criar contas de máquina." }, "machineAccountLimit": { - "message": "Limite de conta de máquina (opcional)" + "message": "Limite de contas de máquina (opcional)" }, "maxMachineAccountCost": { - "message": "Custo máximo de conta de máquina" + "message": "Custo potencial máximo de contas de máquina" }, "machineAccountAccessUpdated": { - "message": "Acesso a conta de máquina atualizado" + "message": "Acesso da conta de máquina atualizado" }, "restrictedGroupAccessDesc": { - "message": "Você não pode adicionar você mesmo a um grupo." + "message": "Você não pode se adicionar a um grupo." }, "deleteProvider": { - "message": "Excluir Provedor" + "message": "Apagar provedor" }, "deleteProviderConfirmation": { - "message": "A exclusão de um provedor é permanente e irreversível. Digite sua senha principal para confirmar a exclusão do provedor e de todos os dados associados." + "message": "Apagar um provedor é permanente e irreversível. Digite sua senha principal para confirmar o apagamento do provedor e de todos os dados associados." }, "deleteProviderName": { - "message": "Não é possível excluir $ID$", + "message": "Não é possível apagar $ID$", "placeholders": { "id": { "content": "$1", @@ -10163,7 +10207,7 @@ } }, "deleteProviderWarningDescription": { - "message": "Você deve desvincular todos os clientes antes de excluir $ID$.", + "message": "Você deve desvincular todos os clientes antes de apagar $ID$.", "placeholders": { "id": { "content": "$1", @@ -10172,19 +10216,19 @@ } }, "providerDeleted": { - "message": "Provedor excluído" + "message": "Provedor apagado" }, "providerDeletedDesc": { - "message": "O provedor e todos os dados associados foram excluídos." + "message": "O provedor e todos os dados associados foram apagados." }, "deleteProviderRecoverConfirmDesc": { - "message": "Você pediu para excluir este Provedor. Clique no botão abaixo para confirmar." + "message": "Você pediu para apagar este provedor. Clique no botão abaixo para confirmar." }, "deleteProviderWarning": { - "message": "A exclusão do seu provedor é permanente. Não pode ser desfeita." + "message": "O apagamento do seu provedor é permanente. Não pode ser desfeito." }, "errorAssigningTargetCollection": { - "message": "Erro ao atribuir coleção de destino." + "message": "Erro ao atribuir conjunto de destino." }, "errorAssigningTargetFolder": { "message": "Erro ao atribuir pasta de destino." @@ -10197,20 +10241,20 @@ "message": "Integrações" }, "integrationsDesc": { - "message": "Sincronize automaticamente segredos do Bitwarden Secrets Manager para um serviço de terceiros." + "message": "Sincronize segredos do Bitwarden Gerenciador de Segredos automaticamente para um serviço de terceiros." }, "sdks": { "message": "SDKs" }, "sdksDesc": { - "message": "Utilize o Bitwarden Secrets Manager SDK nas seguintes linguagens de programação para construir seus próprios aplicativos." + "message": "Utilize o SDK do Bitwarden Gerenciador de Segredos nas seguintes linguagens de programação para construir seus próprios aplicativos." }, "ssoDescStart": { - "message": "Configurar", + "message": "Configure a", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "ssoDescEnd": { - "message": "Para o Bitwarden usar o guia de implementação Para o seu provedor de identidade.", + "message": "para o BItwarden usando o guia de implementação do seu provedor de identidade.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "userProvisioning": { @@ -10220,63 +10264,63 @@ "message": "SCIM" }, "scimIntegrationDescStart": { - "message": "Configurar", + "message": "Configure o", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "scimIntegrationDescEnd": { - "message": "(Sistema de Gerenciamento de Identidade de Domínio) para fornecer automaticamente usuários e grupos ao Bitwarden usando o guia de implementação do seu Provedor de Identidade.", + "message": "(Sistema de Gerenciamento de Identidade Entre Domínios) para provisionar automaticamente usuários e grupos ao Bitwarden usando o guia de implementação do seu provedor de identidade.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "bwdc": { - "message": "Conector de Diretório Bitwarden" + "message": "Bitwarden Conector de Diretório" }, "bwdcDesc": { - "message": "Configure o conector de diretório do Bitwarden para fornecer automaticamente usuários e grupos usando o guia de implementação para o seu provedor de identidade." + "message": "Configure o Bitwarden Conector de Diretório para provisionar automaticamente usuários e grupos usando o guia de implementação para o seu provedor de identidade." }, "eventManagement": { - "message": "Gerenciador de eventos" + "message": "Gerenciamento de eventos" }, "eventManagementDesc": { - "message": "Integre os logs de eventos do Bitwarden ao seu sistema SIEM (informações do sistema e gerenciamento de eventos) utilizando o guia de implementação da sua plataforma." + "message": "Integre os registros de eventos do Bitwarden ao seu sistema SIEM (gerenciamento de eventos e informações do sistema) utilizando o guia de implementação da sua plataforma." }, "deviceManagement": { - "message": "Gerenciamento do dispositivo" + "message": "Gerenciamento de dispositivos" }, "deviceManagementDesc": { "message": "Configure o gerenciamento de dispositivos para o Bitwarden usando o guia de implementação da sua plataforma." }, "crowdstrikeEventIntegrationDesc": { - "message": "Send event data to your Logscale instance" + "message": "Envie dados de eventos a sua instância do Logscale" }, "datadogEventIntegrationDesc": { - "message": "Send vault event data to your Datadog instance" + "message": "Envie dados de eventos do cofre a sua instância do Datadog" }, "failedToSaveIntegration": { - "message": "Failed to save integration. Please try again later." + "message": "Falha ao salvar a integração. Tente novamente mais tarde." }, "mustBeOrgOwnerToPerformAction": { - "message": "You must be the organization owner to perform this action." + "message": "Você precisa ser o proprietário da organização para executar esta ação." }, "failedToDeleteIntegration": { - "message": "Failed to delete integration. Please try again later." + "message": "Falha ao apagar a integração. Tente novamente mais tarde." }, "deviceIdMissing": { "message": "ID do dispositivo está faltando" }, "deviceTypeMissing": { - "message": "O tipo de dispositivo está faltando" + "message": "O tipo do dispositivo está faltando" }, "deviceCreationDateMissing": { - "message": "Data de criação do dispositivo ausente" + "message": "A data de criação do dispositivo está faltando" }, "desktopRequired": { - "message": "Desktop necessário" + "message": "Computador necessário" }, "reopenLinkOnDesktop": { - "message": "Reabra este link a partir do seu e-mail em um desktop." + "message": "Reabra este link a partir do seu e-mail em um computador." }, "connectIntegrationButtonDesc": { - "message": "Connect $INTEGRATION$", + "message": "Conectar $INTEGRATION$", "placeholders": { "integration": { "content": "$1", @@ -10285,7 +10329,7 @@ } }, "updateIntegrationButtonDesc": { - "message": "Update $INTEGRATION$", + "message": "Atualizar $INTEGRATION$", "placeholders": { "integration": { "content": "$1", @@ -10294,7 +10338,7 @@ } }, "integrationCardTooltip": { - "message": "Inicie o guia de implementação $INTEGRATION$.", + "message": "Abra o guia de implementação do $INTEGRATION$.", "placeholders": { "integration": { "content": "$1", @@ -10303,7 +10347,7 @@ } }, "smIntegrationTooltip": { - "message": "Configure $INTEGRATION$.", + "message": "Configure o $INTEGRATION$.", "placeholders": { "integration": { "content": "$1", @@ -10312,7 +10356,7 @@ } }, "smSdkTooltip": { - "message": "Visualizar repositório Rust", + "message": "Ver repositório do $SDK$", "placeholders": { "sdk": { "content": "$1", @@ -10321,7 +10365,7 @@ } }, "integrationCardAriaLabel": { - "message": "Abrir guia de implementação $INTEGRATION$ em uma nova aba.", + "message": "abra o guia de implementação do $INTEGRATION$ em uma nova aba.", "placeholders": { "integration": { "content": "$1", @@ -10330,7 +10374,7 @@ } }, "smSdkAriaLabel": { - "message": "Veja o repositório $SDK$ em uma nova guia.", + "message": "veja o repositório do $SDK$ em uma nova guia.", "placeholders": { "sdk": { "content": "$1", @@ -10339,7 +10383,7 @@ } }, "smIntegrationCardAriaLabel": { - "message": "Configurar guia de implementação $INTEGRATION$ em uma nova aba.", + "message": "configure o guia de implementação do $INTEGRATION$ em uma nova aba.", "placeholders": { "integration": { "content": "$1", @@ -10348,25 +10392,25 @@ } }, "createNewClientToManageAsProvider": { - "message": "Crie uma nova organização de cliente para gerenciar como um Provedor. Posições adicionais serão refletidas no próximo ciclo de faturamento." + "message": "Crie uma nova organização de cliente para gerenciar como um provedor. Vagas adicionais serão refletidas no próximo ciclo de faturamento." }, "url": { "message": "URL" }, "bearerToken": { - "message": "Bearer Token" + "message": "Token de portador" }, "repositoryNameHint": { - "message": "Name of the repository to ingest into" + "message": "Nome do repositório que irá ser ingerido" }, "index": { - "message": "Index" + "message": "Índice" }, "selectAPlan": { "message": "Selecione um plano" }, "thirtyFivePercentDiscount": { - "message": "35% de Desconto" + "message": "Desconto de 35%" }, "monthPerMember": { "message": "mês por membro" @@ -10375,10 +10419,10 @@ "message": "mês por membro cobrado anualmente" }, "seats": { - "message": "Lugares" + "message": "Vagas" }, "addOrganization": { - "message": "Adicionar Organização" + "message": "Adicionar organização" }, "createdNewClient": { "message": "Novo cliente criado com sucesso" @@ -10387,49 +10431,49 @@ "message": "Sem acesso" }, "collectionAdminConsoleManaged": { - "message": "Esta coleção só é acessível a partir do console de administração" + "message": "Este conjunto só é acessível a partir do painel de administração" }, "organizationOptionsMenu": { - "message": "Alternar Menu da Organização" + "message": "Habilitar menu da organização" }, "vaultItemSelect": { "message": "Selecionar item do cofre" }, "collectionItemSelect": { - "message": "Selecionar item da coleção" + "message": "Selecionar item do conjunto" }, "manageBillingFromProviderPortalMessage": { - "message": "Gerenciar faturamento a partir do Portal do Provedor" + "message": "Gerenciar faturamento pelo Portal do Provedor" }, "continueSettingUp": { - "message": "Continue setting up Bitwarden" + "message": "Continuar configurando o Bitwarden" }, "continueSettingUpFreeTrial": { - "message": "Continue a configurar a sua avaliação gratuita do Bitwarden" + "message": "Continuar configurando o seu teste grátis do Bitwarden" }, "continueSettingUpPasswordManager": { - "message": "Continue setting up Bitwarden Password Manager" + "message": "Continuar configurando o Bitwarden Gerenciador de Senhas" }, "continueSettingUpFreeTrialPasswordManager": { - "message": "Continue a configurar sua avaliação gratuita do Gerenciador de Senhas do Bitwarden" + "message": "Continuar configurando o teste grátis do Bitwarden Gerenciador de Senhas" }, "continueSettingUpSecretsManager": { - "message": "Continue setting up Bitwarden Secrets Manager" + "message": "Continuar configurando o Bitwarden Gerenciador de Segredos" }, "continueSettingUpFreeTrialSecretsManager": { - "message": "Continue configurando seu teste gratuito do Gerenciador de Segredos do Bitwarden" + "message": "Continuar configurando seu teste grátis do Bitwarden Gerenciador de Segredos" }, "enterTeamsOrgInfo": { - "message": "Insira as informações da organização de suas equipes" + "message": "Digite as informações da organização do Equipes" }, "enterFamiliesOrgInfo": { - "message": "Digite as informações da organização das suas famílias" + "message": "Digite as informações da organização do Famílias" }, "enterEnterpriseOrgInfo": { - "message": "Insira suas informações da organização empresarial" + "message": "Digite as informações da sua organização do Empresarial" }, "viewItemsIn": { - "message": "Visualizar itens em $NAME$", + "message": "Ver itens em $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -10466,10 +10510,10 @@ "message": "Ver informações" }, "viewAccess": { - "message": "Acesso à visualização" + "message": "Ver acesso" }, "noCollectionsSelected": { - "message": "Você não selecionou nenhuma coleção." + "message": "Você não selecionou nenhum conjunto." }, "updateName": { "message": "Atualizar nome" @@ -10481,13 +10525,13 @@ "message": "Provedor de Serviços Gerenciados" }, "managedServiceProvider": { - "message": "Provedor de serviço gerenciado" + "message": "Provedor de serviços gerenciados" }, "multiOrganizationEnterprise": { - "message": "multi-empresa organizacional" + "message": "Empresa multi-organizacional" }, "orgSeats": { - "message": "Lugares da Organização" + "message": "Vagas da organização" }, "providerDiscount": { "message": "Desconto de $AMOUNT$%", @@ -10505,16 +10549,16 @@ "message": "Proteja sua família ou empresa" }, "upgradeOrganizationCloseSecurityGaps": { - "message": "Fechar lacunas de segurança com relatórios de monitoramento" + "message": "Resolva problemas de segurança com relatórios de monitoramento" }, "upgradeOrganizationCloseSecurityGapsDesc": { - "message": "Fique à frente das vulnerabilidades de segurança atualizando para um plano pago para um monitoramento melhorado." + "message": "Fique à frente das vulnerabilidades de segurança fazendo upgrade para um plano pago para um monitoramento aprimorado." }, "approveAllRequests": { "message": "Aprovar todas as solicitações" }, "allLoginRequestsApproved": { - "message": "Todas as solicitações de login aprovadas" + "message": "Todas as solicitações de acesso aprovadas" }, "payPal": { "message": "PayPal" @@ -10526,13 +10570,13 @@ "message": "Informações fiscais atualizadas" }, "billingInvalidTaxIdError": { - "message": "CPF/CNPJ inválido, se você acredita que isto é um erro, contate o suporte." + "message": "ID de imposto (tax ID) inválido, se você acredita que isto é um erro, entre em contato com o suporte." }, "billingTaxIdTypeInferenceError": { - "message": "Não foi possível validar seu CPF/CNPJ, se você acredita que isto é um erro, contate o suporte." + "message": "Não foi possível validar seu ID de imposto (tax ID), se você acredita que isto é um erro, entre em contato com o suporte." }, "billingPreviewInvalidTaxIdError": { - "message": "CPF/CNPJ inválido, se você acredita que isto é um erro, contate o suporte." + "message": "ID de imposto (tax ID) inválido, se você acredita que isto é um erro, entre em contato com o suporte." }, "billingPreviewInvoiceError": { "message": "Ocorreu um erro ao pré-visualizar a fatura. Tente novamente mais tarde." @@ -10554,10 +10598,10 @@ "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": "Identifique os riscos de segurança ao verificar o acesso de membros" + "message": "Identifique riscos de segurança verificando o acesso dos membros" }, "onlyAvailableForEnterpriseOrganization": { - "message": "Visualize rapidamente o acesso de membros em toda a organização atualizando para um plano empresarial." + "message": "Veja o acesso de membros em toda a organização de forma rápida fazendo upgrade para um plano Empresarial." }, "date": { "message": "Data" @@ -10566,55 +10610,55 @@ "message": "Exportar relatório do cliente" }, "memberAccessReport": { - "message": "Acesso de membros" + "message": "Acesso dos membros" }, "memberAccessReportDesc": { - "message": "Garanta que membros tenham acesso às credenciais certas e que suas contas estejam seguras. Utilize este relatório para obter um CSV do acesso de membros e configurações de conta." + "message": "Garanta que membros tenham acesso às credenciais certas e que suas contas estejam seguras. Utilize este relatório para obter um CSV do acesso e configurações das contas dos membros." }, "memberAccessReportPageDesc": { - "message": "Avalie o acesso de membros da organização entre grupos, coleções e itens de coleções. O exporte CSV fornece informações detalhadas por membro, incluindo informações sobre permissões de coleção e configurações de conta." + "message": "Fiscalize o acesso dos membros da organização entre grupos, conjuntos e os itens dos conjuntos. A exportação em CSV fornece informações detalhadas por membro, incluindo informações sobre permissões de conjuntos e configurações de conta." }, "memberAccessReportNoCollection": { - "message": "(Sem coleção)" + "message": "(Sem conjunto)" }, "memberAccessReportNoCollectionPermission": { - "message": "(Nenhuma permissão de coleção)" + "message": "(Sem permissão de conjunto)" }, "memberAccessReportNoGroup": { - "message": "(Sem Grupo)" + "message": "(Sem grupo)" }, "memberAccessReportTwoFactorEnabledTrue": { - "message": "Ligado" + "message": "Ativada" }, "memberAccessReportTwoFactorEnabledFalse": { - "message": "Desligado" + "message": "Desativada" }, "memberAccessReportAuthenticationEnabledTrue": { - "message": "Ligado" + "message": "Ativada" }, "memberAccessReportAuthenticationEnabledFalse": { - "message": "Desligado" + "message": "Desativada" }, "kdfIterationRecommends": { "message": "Recomendamos 600.000 ou mais" }, "providerReinstate": { - "message": " Contate o Atendimento ao Cliente para restabelecer sua assinatura." + "message": " Contate o suporte ao cliente para restabelecer sua assinatura." }, "secretPeopleDescription": { - "message": "Conceder acesso a este segredo a grupos ou pessoas. As permissões definidas para pessoas irão substituir as permissões definidas por grupos." + "message": "Conceda acesso a este segredo a grupos ou pessoas. As permissões configuradas para pessoas substituirão as permissões configuradas por grupos." }, "secretPeopleEmptyMessage": { "message": "Adicione pessoas ou grupos para compartilhar o acesso a este segredo" }, "secretMachineAccountsDescription": { - "message": "Conceda contas de máquinas acesso a este segredo." + "message": "Conceda contas de máquina acesso a este segredo." }, "secretMachineAccountsEmptyMessage": { "message": "Adicione contas de máquina para conceder acesso a este segredo" }, "smAccessRemovalWarningSecretTitle": { - "message": "Remover acesso a este segredo" + "message": "Remova acesso a este segredo" }, "smAccessRemovalSecretMessage": { "message": "Esta ação removerá seu acesso a este segredo." @@ -10633,18 +10677,18 @@ "description": "A message showing how many unassigned seats are available for a provider." }, "contactYourProviderForAdditionalSeats": { - "message": "Entre em contato com o administrador do seu provedor para comprar lugares adicionais." + "message": "Entre em contato com o administrador do seu provedor para comprar vagas adicionais." }, "open": { - "message": "Abrir", + "message": "Em aberto", "description": "The status of an invoice." }, "uncollectible": { - "message": "Incolecionável", + "message": "Incobrável", "description": "The status of an invoice." }, "clientDetails": { - "message": "Detalhes de cliente" + "message": "Detalhes do cliente" }, "downloadCSV": { "message": "Baixar CSV" @@ -10656,15 +10700,15 @@ "message": "Os ajustes à sua assinatura resultarão em cobranças rateadas em um ciclo de faturamento mensal. " }, "billingHistoryDescription": { - "message": "Baixe um CSV para obter detalhes do cliente para cada data de faturamento. As taxas pagas não estão incluídas no CSV e podem variar da factura vinculada. Para obter dados de faturamento mais precisos, consulte suas faturas mensais.", + "message": "Baixe um CSV para obter detalhes do cliente para cada data de faturamento. As taxas rateadas não estão incluídas no CSV e podem variar da fatura vinculada. Para obter dados de faturamento mais precisos, consulte suas faturas mensais.", "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": "Não há itens para listar", + "message": "Não há faturas para listar", "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": "Aviso: No final deste mês, a privacidade do cofre cliente será melhorada e os membros do provedor não terão mais acesso direto aos itens do cofre do cliente. Para dúvidas,", + "message": "Atenção: No final deste mês, a privacidade do cofre dos clientes será melhorada e os membros do provedor não terão mais acesso direto aos itens do cofre do cliente. Para dúvidas,", "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": { @@ -10672,10 +10716,10 @@ "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": "Patrocinado" + "message": "Financiado" }, "licenseAndBillingManagementDesc": { - "message": "Após fazer atualizações no servidor da nuvem do Bitwarden, envie o arquivo de licença para aplicar as alterações mais recentes." + "message": "Após fazer atualizações no servidor da nuvem do Bitwarden, envie o arquivo da sua licença para aplicar as alterações mais recentes." }, "addToFolder": { "message": "Adicionar à pasta" @@ -10684,10 +10728,10 @@ "message": "Selecionar pastas" }, "personalItemTransferWarningSingular": { - "message": "1 item será transferido permanentemente para a organização selecionada. Você não irá mais possuir este item." + "message": "1 item será transferido permanentemente para a organização selecionada. Você não irá mais ser o proprietário deste item." }, "personalItemsTransferWarningPlural": { - "message": "Itens $PERSONAL_ITEMS_COUNT$ serão transferidos permanentemente para a organização selecionada. Você não irá mais possuir esses itens.", + "message": "$PERSONAL_ITEMS_COUNT$ itens serão transferidos permanentemente para a organização selecionada. Você não irá mais ser o proprietário desses itens.", "placeholders": { "personal_items_count": { "content": "$1", @@ -10696,7 +10740,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item será transferido permanentemente para $ORG$. Você não irá mais possuir este item.", + "message": "1 item será transferido permanentemente para $ORG$. Você não irá mais ser o proprietário deste item.", "placeholders": { "org": { "content": "$1", @@ -10705,7 +10749,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "Os itens $PERSONAL_ITEMS_COUNT$ serão transferidos permanentemente para $ORG$. Você não irá mais possuir esses itens.", + "message": "$PERSONAL_ITEMS_COUNT$ itens serão transferidos permanentemente para $ORG$. Você não irá mais ser o proprietário desses itens.", "placeholders": { "personal_items_count": { "content": "$1", @@ -10718,55 +10762,55 @@ } }, "data": { - "message": "Dado" + "message": "Dados" }, "purchasedSeatsRemoved": { - "message": "assentos comprados removidos" + "message": "vagas compradas removidas" }, "environmentVariables": { - "message": "Variáveis de ambiente" + "message": "Variáveis do ambiente" }, "organizationId": { - "message": "ID da Organização" + "message": "ID da organização" }, "projectIds": { "message": "IDs do projeto" }, "projectId": { - "message": "ID do Projeto" + "message": "ID do projeto" }, "projectsAccessedByMachineAccount": { "message": "Os seguintes projetos podem ser acessados por esta conta de máquina." }, "config": { - "message": "Configuraçao" + "message": "Configuração" }, "learnMoreAboutEmergencyAccess": { - "message": "Saiba mais sobre acesso de emergência" + "message": "Saiba mais sobre o acesso de emergência" }, "learnMoreAboutMatchDetection": { - "message": "Saiba mais sobre a detecção de partidas" + "message": "Saiba mais sobre a detecção de correspondência" }, "learnMoreAboutMasterPasswordReprompt": { - "message": "Saiba mais sobre a redefinição da senha principal" + "message": "Saiba mais sobre a resolicitação da senha principal" }, "learnMoreAboutSearchingYourVault": { - "message": "Saiba mais sobre como pesquisar no seu cofre" + "message": "Saiba mais sobre buscar no seu cofre" }, "learnMoreAboutYourAccountFingerprintPhrase": { - "message": "Saiba mais sobre a sua frase biométrica" + "message": "Saiba mais sobre a frase biométrica da sua conta" }, "impactOfRotatingYourEncryptionKey": { - "message": "Impacto ao girar sua chave de criptografia" + "message": "Consequências de rotacionar sua chave de criptografia" }, "learnMoreAboutEncryptionAlgorithms": { "message": "Saiba mais sobre os algoritmos de criptografia" }, "learnMoreAboutKDFIterations": { - "message": "Aprenda mais sobre iterações KDF" + "message": "Saiba mais sobre as iterações da KDF" }, "learnMoreAboutLocalization": { - "message": "Saiba mais sobre a localização" + "message": "Saiba mais sobre a tradução" }, "learnMoreAboutWebsiteIcons": { "message": "Saiba mais sobre como usar os ícones dos sites" @@ -10775,22 +10819,22 @@ "message": "Saiba mais sobre o acesso aos usuários" }, "learnMoreAboutMemberRoles": { - "message": "Saiba mais sobre os papéis e permissões dos membros" + "message": "Saiba mais sobre os cargos e permissões dos membros" }, "whatIsACvvNumber": { - "message": "O que é um número CVVV?" + "message": "O que é um número de CVV?" }, "learnMoreAboutApi": { "message": "Saiba mais sobre a API do Bitwarden" }, "fileSends": { - "message": "Arquivos enviados" + "message": "Sends de arquivo" }, "textSends": { - "message": "Texto enviado" + "message": "Sends de texto" }, "includesXMembers": { - "message": "para $COUNT$ membro", + "message": "por $COUNT$ membro(s)", "placeholders": { "count": { "content": "$1", @@ -10811,7 +10855,7 @@ "message": "Hospedagem local opcional" }, "upgradeFreeOrganization": { - "message": "Atualize sua organização $NAME$ ", + "message": "Faça upgrade da sua organização do $NAME$ ", "placeholders": { "name": { "content": "$1", @@ -10820,10 +10864,10 @@ } }, "includeSsoAuthenticationMessage": { - "message": "Autenticação SSO" + "message": "Autenticação única" }, "familiesPlanInvLimitReachedManageBilling": { - "message": "Organizações familiares podem ter até membros da $SEATCOUNT$. Faça o upgrade para um plano pago para convidar mais membros.", + "message": "Organizações familiares podem ter até $SEATCOUNT$ membros. Faça upgrade para um plano pago para convidar mais membros.", "placeholders": { "seatcount": { "content": "$1", @@ -10832,7 +10876,7 @@ } }, "familiesPlanInvLimitReachedNoManageBilling": { - "message": "Organizações familiares podem ter até membros $SEATCOUNT$. Entre em contato com o proprietário da sua organização para atualizar.", + "message": "Organizações familiares podem ter até $SEATCOUNT$ membros. Entre em contato com o proprietário da sua organização para fazer upgrade.", "placeholders": { "seatcount": { "content": "$1", @@ -10841,7 +10885,7 @@ } }, "upgradePlans": { - "message": "Atualize o seu plano para convidar membros e experimentar poderosos recursos de segurança." + "message": "Faça upgrade do seu plano para convidar membros e experimentar poderosos recursos de segurança." }, "upgradeDiscount": { "message": "Economize $AMOUNT$%", @@ -10853,25 +10897,25 @@ } }, "enterprisePlanUpgradeMessage": { - "message": "Recursos avançados para organizações maiores" + "message": "Capacidades avançadas para organizações maiores" }, "teamsPlanUpgradeMessage": { - "message": "Proteção residencial para o cultivo das equipes" + "message": "Proteção resiliente para o crescimento das equipes" }, "teamsInviteMessage": { - "message": "Convide membros ilimitados" + "message": "Convide ilimitados membros" }, "accessToCreateGroups": { "message": "Acesso para criar grupos" }, "syncGroupsAndUsersFromDirectory": { - "message": "Sincronizar grupos e usuários de um diretório" + "message": "Sincronize grupos e usuários de um diretório" }, "familyPlanUpgradeMessage": { - "message": "Proteja seus logins familiares" + "message": "Proteja as credenciais da sua família" }, "accessToPremiumFeatures": { - "message": "Acesso às funcionalidades Premium" + "message": "Acesso aos recursos do Premium" }, "additionalStorageGbMessage": { "message": "GB de armazenamento adicional" @@ -10901,40 +10945,40 @@ "message": "ED25519" }, "sshKeyAlgorithmRSA2048": { - "message": "RSA 2048-Bit" + "message": "RSA de 2048 bits" }, "sshKeyAlgorithmRSA3072": { - "message": "RSA 3072-Bit" + "message": "RSA de 3072 bits" }, "sshKeyAlgorithmRSA4096": { - "message": "RSA 4096-Bit" + "message": "RSA de 4096 bits" }, "premiumAccounts": { - "message": "6 contas premium" + "message": "6 contas Premium" }, "unlimitedSharing": { "message": "Compartilhamento ilimitado" }, "unlimitedCollections": { - "message": "Coleções ilimitadas" + "message": "Conjuntos ilimitados" }, "secureDataSharing": { - "message": "Compartilhamento de dados seguro" + "message": "Compartilhamento seguro de dados" }, "eventLogMonitoring": { "message": "Monitoramento do registro de eventos" }, "directoryIntegration": { - "message": "Integração de diretório" + "message": "Integração de diretórios" }, "passwordLessSso": { - "message": "SSO Senha-Chaves" + "message": "Autenticação sem senha" }, "accountRecovery": { "message": "Recuperação de conta" }, "customRoles": { - "message": "Funções personalizadas" + "message": "Cargos personalizados" }, "unlimitedSecretsStorage": { "message": "Armazenamento ilimitado de segredos" @@ -10952,13 +10996,13 @@ "message": "Atual" }, "secretsManagerSubscriptionInfo": { - "message": "Sua assinatura do Gerenciador de Segredos será atualizada com base no plano selecionado" + "message": "Sua assinatura do Gerenciador de Segredos terá o upgrade feito de acordo com o plano selecionado" }, "bitwardenPasswordManager": { - "message": "Gerenciador de Senhas Bitwarden" + "message": "Bitwarden Gerenciador de Senhas" }, "secretsManagerComplimentaryPasswordManager": { - "message": "Sua cortesia de um ano de assinatura do Gerenciador de Senhas vai atualizar para o plano selecionado. Você não será cobrado até que o período complementar acabe." + "message": "Sua assinatura de cortesia de um ano do Gerenciador de Senhas terá o upgrade feito para o plano selecionado. Você não será cobrado até que o período de cortesia acabe." }, "fileSavedToDevice": { "message": "Arquivo salvo no dispositivo. Gerencie a partir das transferências do seu dispositivo." @@ -10968,28 +11012,28 @@ "description": "The text, 'API', is an acronym and should not be translated." }, "showCharacterCount": { - "message": "Mostrar contagem de caracteres" + "message": "Mostrar número de caracteres" }, "hideCharacterCount": { - "message": "Esconder contagem de caracteres" + "message": "Ocultar número de caracteres" }, "editAccess": { "message": "Editar acesso" }, "textHelpText": { - "message": "Utilize campos de texto para dados como perguntas de segurança" + "message": "Use campos de texto para dados como questões de segurança" }, "hiddenHelpText": { - "message": "Use campos ocultos para dados confidenciais como uma senha" + "message": "Use campos ocultos para dados sensíveis como senhas" }, "checkBoxHelpText": { - "message": "Use caixas de seleção se gostaria de preencher automaticamente a caixa de seleção de um formulário, como um e-mail de lembrança" + "message": "Use caixas de seleção se gostaria de preencher automaticamente a caixa de seleção de um formulário, como um lembrar e-mail" }, "linkedHelpText": { "message": "Use um campo vinculado quando estiver enfrentando problemas com o preenchimento automático em um site específico." }, "linkedLabelHelpText": { - "message": "Digite o Id html do campo, nome, nome aria-label, ou marcador de posição" + "message": "Digite o ID html, nome, aria-label, ou placeholder do campo." }, "uppercaseDescription": { "message": "Incluir caracteres maiúsculos", @@ -11012,7 +11056,7 @@ "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { - "message": "0 – 9", + "message": "0-9", "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { @@ -11026,26 +11070,26 @@ "message": "O tamanho máximo do arquivo é 500 MB" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Tem certeza de que deseja excluir permanentemente esse anexo?" + "message": "Tem certeza de que deseja apagar esse anexo permanentemente?" }, "manageSubscriptionFromThe": { - "message": "Gerenciar assinatura do", + "message": "Gerenciar assinatura pelo", "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": "Para hospedar o Bitwarden no seu próprio servidor, você precisará enviar seu arquivo de licença. Para apoiar planos de famílias gratuitas e recursos avançados de faturamento para sua organização auto-hospedada, você precisará configurar a sincronização automática em sua organização auto-hospedada." + "message": "Para hospedar o Bitwarden no seu próprio servidor, você precisará enviar o arquivo da sua licença. Para apoiar planos grátis do Famílias e recursos avançados de faturamento para sua organização auto-hospedada, você precisará configurar a sincronização automática nela." }, "selfHostingTitleProper": { - "message": "Auto-hospedado" + "message": "Auto-hospedagem" }, "claim-domain-single-org-warning": { - "message": "Reivindicar um domínio ligará a política de organização única." + "message": "Reivindicar um domínio ativará a política de organização única." }, "single-org-revoked-user-warning": { "message": "Membros não conformes serão revogados. Os administradores podem restaurar os membros assim que saírem de todas as outras organizações." }, "deleteOrganizationUser": { - "message": "Excluir $NAME$", + "message": "Apagar $NAME$", "placeholders": { "name": { "content": "$1", @@ -11055,7 +11099,7 @@ } }, "deleteOrganizationUserWarningDesc": { - "message": "Isto irá excluir permanentemente todos os itens pertencentes a $NAME$. Os itens de coleção não são impactados.", + "message": "Isto apagará permanentemente todos os itens pertencentes a $NAME$. Os itens de conjuntos não são impactados.", "description": "Warning description for the delete organization user dialog", "placeholders": { "name": { @@ -11065,11 +11109,11 @@ } }, "deleteManyOrganizationUsersWarningDesc": { - "message": "Isto irá excluir permanentemente todos os itens pertencentes a $NAME$. Os itens de coleção não são impactados.", + "message": "Isso apagará permanentemente todos os itens pertencentes aos seguintes membros. Os itens de conjuntos não são impactados.", "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "$NAME$ Excluído", + "message": "Apagou $NAME$", "placeholders": { "name": { "content": "$1", @@ -11078,10 +11122,10 @@ } }, "organizationUserDeletedDesc": { - "message": "O usuário foi removido da organização e todos os dados de usuários associados foram excluídos." + "message": "O usuário foi removido da organização e todos os dados de usuários associados foram apagados." }, "deletedUserIdEventMessage": { - "message": "Deleted user $ID$", + "message": "Apagou o usuário $ID$", "placeholders": { "id": { "content": "$1", @@ -11099,7 +11143,7 @@ } }, "suspendedOrganizationTitle": { - "message": "O $ORGANIZATION$ está suspenso", + "message": "A $ORGANIZATION$ está suspensa", "placeholders": { "organization": { "content": "$1", @@ -11114,31 +11158,31 @@ "message": "Para recuperar o acesso à sua organização, adicione um método de pagamento." }, "deleteMembers": { - "message": "Excluir membros" + "message": "Apagar membros" }, "noSelectedMembersApplicable": { "message": "Esta ação não se aplica a nenhum dos membros selecionados." }, "deletedSuccessfully": { - "message": "Excluído com sucesso" + "message": "Apagado com sucesso" }, "freeFamiliesSponsorship": { - "message": "Remova o patrocínio das famílias do Bitwarden" + "message": "Remover financiamento do Bitwarden Famílias grátis" }, "freeFamiliesSponsorshipPolicyDesc": { - "message": "Não permita que os membros resgatem um plano de Famílias por meio desta organização." + "message": "Não permita que os membros resgatem um plano do Famílias por meio desta organização." }, "verifyBankAccountWithStatementDescriptorWarning": { - "message": "O pagamento com uma conta bancária só está disponível para clientes nos Estados Unidos. Você será obrigado a verificar sua conta bancária. Nós faremos um micro-depósito nos próximos 1-2 dias úteis. Digite o código do descritor da instrução a partir deste depósito na página de faturamento da organização para verificar a conta bancária. A não verificação da conta bancária resultará em um pagamento não atendido e sua assinatura será suspensa." + "message": "O pagamento com uma conta bancária só está disponível para clientes nos Estados Unidos. Você precisará verificar sua conta bancária. Faremos um micro-depósito nos próximos 1-2 dias úteis. Digite o código descritor do extrato deste depósito na página de faturamento da organização para verificar a conta bancária. A falha na verificação da conta bancária resultará em um pagamento atrasado e a sua assinatura será suspensa." }, "verifyBankAccountWithStatementDescriptorInstructions": { - "message": "Fizemos um microdepósito em sua conta bancária (isso pode levar 1-2 dias úteis). Digite o código de seis dígitos começando com 'SM' encontrado na descrição de depósito. A não verificação da conta bancária resultará em um pagamento não atendido e sua assinatura será suspensa." + "message": "Fizemos um micro-depósito em sua conta bancária (isso pode levar 1-2 dias úteis). Digite o código de seis dígitos começando com 'SM' encontrado na descrição do depósito. A falha da verificação da conta bancária resultará em um pagamento atrasado, e a sua assinatura será suspensa." }, "descriptorCode": { - "message": "Código do descritor" + "message": "Código descritor" }, "cannotRemoveViewOnlyCollections": { - "message": "Você não pode remover coleções com permissões de Somente leitura: $COLLECTIONS$", + "message": "Você não pode remover conjuntos com permissões de Apenas ver: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -11147,7 +11191,7 @@ } }, "removeMembers": { - "message": "Remover membro?" + "message": "Remover membros" }, "devices": { "message": "Dispositivos" @@ -11168,16 +11212,16 @@ "message": "Recuperar domínio" }, "claimDomainNameInputHint": { - "message": "Exemplo: meudominio.com. Subdomínios requerem entradas separadas para serem reivindicadas." + "message": "Exemplo: meudominio.com. Subdomínios requerem entradas separadas para serem reivindicados." }, "automaticClaimedDomains": { - "message": "Domínios Reivindicados Automaticamente" + "message": "Domínios reivindicados automaticamente" }, "automaticDomainClaimProcess": { - "message": "O Bitwarden tentará reivindicar o domínio 3 vezes durante as primeiras 72 horas. Se o domínio não puder ser reivindicado, verifique o registro DNS no seu host e reivindique manualmente. O domínio será removido da sua organização em 7 dias, se não for reivindicado." + "message": "O Bitwarden tentará reivindicar o domínio 3 vezes durante as primeiras 72 horas. Se o domínio não poder ser reivindicado, confira o registro de DNS no seu servidor e reivindique manualmente. Se não for reivindicado, o domínio será removido da sua organização em 7 dias." }, "domainNotClaimed": { - "message": "$DOMAIN$ não reivindicado. Verifique seus registros DNS.", + "message": "$DOMAIN$ não reivindicado. Confira os seus registros de DNS.", "placeholders": { "DOMAIN": { "content": "$1", @@ -11192,10 +11236,10 @@ "message": "Em verificação" }, "claimedDomainsDescription": { - "message": "Claim a domain to own member accounts. The SSO identifier page will be skipped during login for members with claimed domains and administrators will be able to delete claimed accounts." + "message": "Reivindique um domínio para ser o proprietário das contas dos membros. A página do identificador do SSO será pulada durante a autenticação dos membros com os domínios reivindicados, e os administradores poderão apagar contas reivindicadas." }, "invalidDomainNameClaimMessage": { - "message": "Entrada não é um formato válido. Formato: meudominio.com. Subdomínios requerem entradas separadas para serem reivindicados." + "message": "A entrada não está em um formato válido. Formato: meudominio.com. Subdomínios requerem entradas separadas a serem reivindicadas." }, "domainClaimedEvent": { "message": "$DOMAIN$ reivindicado", @@ -11216,7 +11260,7 @@ } }, "updatedRevokeSponsorshipConfirmationForSentSponsorship": { - "message": "Se você remover $EMAIL$, o pagamento deste plano Família não poderá ser resgatado. Tem certeza que deseja continuar?", + "message": "Se você remover $EMAIL$, o pagamento deste plano familiar não poderá ser resgatado. Tem certeza que deseja continuar?", "placeholders": { "email": { "content": "$1", @@ -11225,7 +11269,7 @@ } }, "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { - "message": "Se remover $EMAIL$, o pagamento para este plano Família terminará e o método de pagamento salvo será cobrado por US$ 40 + imposto aplicável em $DATE$. Você não poderá resgatar um novo pagamento até $DATE$. Tem certeza que deseja continuar?", + "message": "Se remover $EMAIL$, o financiamento deste plano familiar terminará e o método de pagamento salvo será cobrado por 40$ USD + impostos aplicáveis em $DATE$. Você não poderá resgatar um novo financiamento até $DATE$. Tem certeza que deseja continuar?", "placeholders": { "email": { "content": "$1", @@ -11241,13 +11285,13 @@ "message": "Domínio reivindicado" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Item adicionado aos favoritos" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Item removido dos favoritos" }, "copyNote": { - "message": "Copy note" + "message": "Copiar anotação" }, "organizationNameMaxLength": { "message": "O nome da organização não pode exceder 50 caracteres." @@ -11262,49 +11306,49 @@ "message": "Confiar e confirmar usuário" }, "trustOrganization": { - "message": "Organização de confiança" + "message": "Confiar na organização" }, "trust": { - "message": "Confiança" + "message": "Confiar" }, "doNotTrust": { "message": "Não confiar" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "Organização não é confiada" }, "emergencyAccessTrustWarning": { - "message": "Para a segurança de sua conta, só confirme se você concedeu acesso de emergência a este usuário e sua impressão digital corresponde ao que é exibido na conta dele" + "message": "Para a segurança da sua conta, apenas confirme que você permitiu o acesso de emergência a esse usuário e se a frase biométrica dele coincide com a que é exibida na conta deles" }, "orgTrustWarning": { - "message": "Para a segurança da sua conta, prossiga somente se você for um membro desta organização, tem a recuperação de conta ativada, e a impressão digital exibida abaixo corresponde à impressão digital da organização." + "message": "Para a segurança da sua conta, prossiga apenas se você for um membro dessa organização, tem a recuperação de conta ativa, e a frase biométrica exibida abaixo corresponde com a da organização." }, "orgTrustWarning1": { - "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + "message": "Esta organização tem uma política empresarial que lhe inscreverá na recuperação de conta. A inscrição permitirá que os administradores da organização alterem sua senha. Prossiga somente se você reconhecer esta organização e se a frase biométrica exibida abaixo corresponde com a da organização." }, "trustUser": { "message": "Confiar no usuário" }, "sshKeyWrongPassword": { - "message": "A senha está incorreta." + "message": "A senha que você digitou está incorreta." }, "importSshKey": { "message": "Importar" }, "confirmSshKeyPassword": { - "message": "Confirme a senha" + "message": "Confirmar senha" }, "enterSshKeyPasswordDesc": { - "message": "Insira a senha da chave SSH." + "message": "Digite a senha da chave SSH." }, "enterSshKeyPassword": { - "message": "Insira a senha" + "message": "Digitar senha" }, "invalidSshKey": { "message": "A chave SSH é inválida" }, "sshKeyTypeUnsupported": { - "message": "O tipo de chave SSH não é compatível" + "message": "O tipo de chave SSH não é suportado" }, "importSshKeyFromClipboard": { "message": "Importar chave da área de transferência" @@ -11316,36 +11360,28 @@ "message": "Copiar chave privada" }, "openingExtension": { - "message": "Abrir a extensão do navegador Bitwarden" + "message": "Abrindo a extensão de navegador do Bitwarden" }, "somethingWentWrong": { - "message": "Ocorreu um problema..." + "message": "Algo deu errado..." }, "openingExtensionError": { - "message": "Tivemos problemas ao abrir a extensão Bitwarden. Clique no botão para abri-la agora." + "message": "Tivemos problemas ao abrir a extensão de navegador do Bitwarden. Clique no botão para abri-la agora." }, "openExtension": { "message": "Abrir extensão" }, "doNotHaveExtension": { - "message": "Não tem a extensão do Bitwarden?" + "message": "Não tem a extensão de navegador do Bitwarden?" }, "installExtension": { - "message": "Instalar a extensão" + "message": "Instalar extensão" }, "openedExtension": { - "message": "Abriu a extensão do navegador" + "message": "Extensão de navegador foi aberta" }, "openedExtensionViewAtRiskPasswords": { - "message": "A extensão do Bitwarden foi aberta com sucesso. Agora você pode rever suas senhas em risco." - }, - "openExtensionManuallyPart1": { - "message": "Tivemos problemas ao abrir a extensão 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.'" + "message": "A extensão de navegador do Bitwarden foi aberta com sucesso. Agora você pode revisar suas senhas em risco." }, "resellerRenewalWarningMsg": { "message": "Sua assinatura será renovada em breve. Para garantir o serviço sem interrupções, entre em contato com $RESELLER$ para confirmar sua renovação antes de $RENEWAL_DATE$.", @@ -11361,7 +11397,7 @@ } }, "resellerOpenInvoiceWarningMgs": { - "message": "Uma fatura para sua assinatura foi emitida em $ISSUED_DATE$. Para garantir o serviço ininterrupto, entre em contato com $RESELLER$ para confirmar a sua renovação antes de $DUE_DATE$.", + "message": "Uma fatura para sua assinatura foi emitida em $ISSUED_DATE$. Para garantir o serviço sem interrupções, entre em contato com $RESELLER$ para confirmar a sua renovação antes de $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -11378,7 +11414,7 @@ } }, "resellerPastDueWarningMsg": { - "message": "A fatura da sua assinatura não foi paga. Para garantir o serviço ininterrupto, entre em contato com $RESELLER$ para confirmar sua renovação antes de $GRACE_PERIOD_END$.", + "message": "A fatura da sua assinatura não foi paga. Para garantir o serviço sem interrupções, entre em contato com $RESELLER$ para confirmar sua renovação antes de $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -11397,7 +11433,7 @@ "message": "Reinicie sua assinatura" }, "suspendedManagedOrgMessage": { - "message": "Entre em contato com $PROVIDER$ para assistência.", + "message": "Entre em contato com $PROVIDER$ para ter assistência.", "placeholders": { "provider": { "content": "$1", @@ -11406,16 +11442,16 @@ } }, "accountDeprovisioningNotification": { - "message": "Os administradores agora têm a capacidade de excluir contas de membros que pertencem a um domínio reivindicado." + "message": "Os administradores agora conseguem apagar contas de membros que pertencem a um domínio reivindicado." }, "deleteManagedUserWarningDesc": { - "message": "Esta ação irá excluir a conta de membro, incluindo todos os itens do seu cofre. Isso substitui a ação de Remover anterior." + "message": "Esta ação irá apagar a conta do membro, incluindo todos os itens do seu cofre. Isso substitui a ação anterior de Remover." }, "deleteManagedUserWarning": { - "message": "Excluir é uma nova ação!" + "message": "Apagar é uma nova ação!" }, "seatsRemaining": { - "message": "Você tem $REMAINING$ assentos restantes de $TOTAL$ atribuídos a essa organização. Entre em contato com seu provedor para gerenciar sua assinatura.", + "message": "Você tem $REMAINING$ vagas restantes das $TOTAL$ atribuídas para esta organização. Entre em contato com seu provedor para gerenciar sua assinatura.", "placeholders": { "remaining": { "content": "$1", @@ -11431,7 +11467,7 @@ "message": "Organização existente" }, "selectOrganizationProviderPortal": { - "message": "Selecione uma organização para adicionar ao seu Portal do Provedor." + "message": "Selecione uma organização para adicionar ao seu portal de provedor." }, "noOrganizations": { "message": "Não há organizações para listar" @@ -11452,10 +11488,10 @@ "message": "Organização existente adicionada" }, "assignedExceedsAvailable": { - "message": "Os assentos atribuídos excedem os disponíveis." + "message": "As vagas atribuídas excedem as disponíveis." }, "userkeyRotationDisclaimerEmergencyAccessText": { - "message": "Frase de impressão digital para contatos $NUM_USERS$ para os quais você ativou o acesso de emergência.", + "message": "A frase biométrica dos $NUM_USERS$ contatos para os quais você ativou o acesso de emergência.", "placeholders": { "num_users": { "content": "$1", @@ -11464,7 +11500,7 @@ } }, "userkeyRotationDisclaimerAccountRecoveryOrgsText": { - "message": "Frase de impressão digital para a organização $ORG_NAME$ para a qual você ativou a recuperação de conta.", + "message": "A frase biométrica da organização $ORG_NAME$ para a qual você ativou a recuperação de conta.", "placeholders": { "org_name": { "content": "$1", @@ -11473,95 +11509,95 @@ } }, "userkeyRotationDisclaimerDescription": { - "message": "Girar suas chaves de criptografia exigirá que você confie nas chaves de qualquer organização que possa recuperar sua conta, e quaisquer contatos que você ativou o acesso de emergência. Para continuar, certifique-se de que pode verificar o seguinte:" + "message": "Rotacionar suas chaves de criptografia exigirá que você confie nas chaves de quaisquer organizações que possam recuperar sua conta, e quaisquer contatos que você ativou o acesso de emergência. Para continuar, certifique-se de que pode verificar o seguinte:" }, "userkeyRotationDisclaimerTitle": { - "message": "Chaves criptográficas não confiáveis" + "message": "Chaves criptográficas não confiadas" }, "changeAtRiskPassword": { - "message": "Alterar senhas vulneráveis" + "message": "Alterar senhas em risco" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "This login is at-risk and missing a website. Add a website and change the password for stronger security." + "message": "Esta credencial está em risco e está sem um site. Adicione um site e altere a senha para segurança melhor." }, "missingWebsite": { - "message": "Missing website" + "message": "Site ausente" }, "removeUnlockWithPinPolicyTitle": { - "message": "Remover desbloqueio com o PIN" + "message": "Remover desbloqueio com PIN" }, "removeUnlockWithPinPolicyDesc": { - "message": "Não permitir que os membros desbloqueiem sua conta com um PIN." + "message": "Não permita que os membros desbloqueiem sua conta com um PIN." }, "upgradeForFullEventsMessage": { - "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." + "message": "O registro de eventos não é armazenado para a sua organização. Faça upgrade para um plano Equipes ou Empresarial para ter acesso completo ao registro de eventos da organização." }, "upgradeEventLogTitleMessage": { - "message": "Upgrade to see event logs from your organization." + "message": "Faça upgrade para ver o registro de eventos da sua organização." }, "upgradeEventLogMessage": { "message": "Esses eventos são apenas exemplos e não refletem eventos reais na sua organização do Bitwarden." }, "viewEvents": { - "message": "View Events" + "message": "Ver eventos" }, "cannotCreateCollection": { - "message": "Organizações gratuitas podem ter até duas coleções. Faça o upgrade para um plano pago para adicionar mais coleções." + "message": "Organizações gratuitas podem ter até dois conjuntos. Faça upgrade para um plano pago para adicionar mais conjuntos." }, "searchArchive": { - "message": "Search archive" + "message": "Buscar no arquivo" }, "archiveNoun": { - "message": "Archive", + "message": "Arquivo", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arquivar", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "Desarquivar" }, "itemsInArchive": { - "message": "Items in archive" + "message": "Itens no arquivo" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "Nenhum item no arquivo" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados gerais de busca e das sugestões de preenchimento automático." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "O item foi enviado para o arquivo" }, "itemsWereSentToArchive": { - "message": "Items were sent to archive" + "message": "Itens foram enviados para o arquivo" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "O item foi desarquivado" }, "bulkArchiveItems": { - "message": "Items archived" + "message": "Itens arquivados" }, "bulkUnarchiveItems": { - "message": "Items unarchived" + "message": "Itens desarquivados" }, "archiveItem": { - "message": "Archive item", + "message": "Arquivar item", "description": "Verb" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "Itens arquivados são excluídos dos resultados gerais de busca e das sugestões de preenchimento automático. Tem certeza de que deseja arquivar este item?" }, "archiveBulkItems": { - "message": "Archive items", + "message": "Arquivar itens", "description": "Verb" }, "archiveBulkItemsConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive these items?" + "message": "Itens arquivados são excluídos dos resultados gerais de busca e das sugestões de preenchimento automático. Tem certeza de que deseja arquivar esses itens?" }, "businessUnit": { - "message": "Unidades de Negócio" + "message": "Unidade de négocio" }, "businessUnits": { "message": "Unidades de Negócio" @@ -11570,141 +11606,146 @@ "message": "Nova unidade de negócio" }, "sendsTitleNoItems": { - "message": "Send sensitive information safely", + "message": "Envie informações sensíveis com segurança", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { - "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "message": "Compartilhe dados e arquivos com segurança com qualquer pessoa, em qualquer plataforma. Suas informações permanecerão criptografadas de ponta a ponta, limitando a exposição.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "generatorNudgeTitle": { - "message": "Quickly create passwords" + "message": "Crie senhas de forma rápida" }, "generatorNudgeBodyOne": { - "message": "Easily create strong and unique passwords by clicking on", + "message": "Crie senhas únicas e fortes com facilidade clicando em", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyTwo": { - "message": "to help you keep your logins secure.", + "message": "para ajudá-lo a manter suas credenciais seguras.", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "message": "Crie senhas únicas e fortes com facilidade clicando no botão de gerar senha para ajudá-lo a manter suas credenciais seguras.", "description": "Aria label for the body content of the generator nudge" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "Economize tempo com o preenchimento automático" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "Inclua um", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Site", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "para que esta credencial apareça como uma sugestão de preenchimento automático.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newCardNudgeTitle": { - "message": "Seamless online checkout" + "message": "Pagamento on-line simplificado" }, "newCardNudgeBody": { - "message": "With cards, easily autofill payment forms securely and accurately." + "message": "Preencha automaticamente formulários de pagamento com cartões de forma segura e precisa." }, "newIdentityNudgeTitle": { - "message": "Simplify creating accounts" + "message": "Simplifique a criação de contas" }, "newIdentityNudgeBody": { - "message": "With identities, quickly autofill long registration or contact forms." + "message": "Com identidades, preencha formulários de cadastro ou contato longos rapidamente, de forma automática." }, "newNoteNudgeTitle": { - "message": "Keep your sensitive data safe" + "message": "Mantenha seus dados sensíveis seguros" }, "newNoteNudgeBody": { - "message": "With notes, securely store sensitive data like banking or insurance details." + "message": "Com anotações, armazene com segurança dados sensíveis como detalhes de informações bancárias ou de seguro." }, "newSshNudgeTitle": { - "message": "Developer-friendly SSH access" + "message": "Acesso SSH amigável para desenvolvedores" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "Armazene suas chaves e conecte com o agente SSH para uma autenticação rápida e criptografada.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "Saiba mais sobre o agente SSH", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "setupExtensionPageTitle": { - "message": "Autofill your passwords securely with one click" + "message": "Preencha suas senhas com segurança em um clique" }, "setupExtensionPageDescription": { - "message": "Get the Bitwarden browser extension and start autofilling today" + "message": "Baixe a extensão de navegador do Bitwarden e use o preenchimento automático hoje" }, "getTheExtension": { - "message": "Get the extension" + "message": "Baixar extensão" }, "addItLater": { - "message": "Add it later" + "message": "Adicionar mais tarde" }, "cannotAutofillPasswordsWithoutExtensionTitle": { - "message": "You can't autofill passwords without the browser extension" + "message": "Você não pode preencher senhas automaticamente sem a extensão de navegador" }, "cannotAutofillPasswordsWithoutExtensionDesc": { - "message": "Are you sure you don't want to add the extension now?" + "message": "Você tem certeza de que não quer adicionar a extensão agora?" }, "skipToWebApp": { - "message": "Skip to web app" + "message": "Ir para o aplicativo web" }, "bitwardenExtensionInstalled": { - "message": "Bitwarden extension installed!" + "message": "Extensão do Bitwarden instalada!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "A extensão do Bitwarden está instalada!" }, "openExtensionToAutofill": { - "message": "Open the extension to log in and start autofilling." + "message": "Abra a extensão, se conecte, e comece a usar o preenchimento automático." }, "openBitwardenExtension": { - "message": "Open Bitwarden extension" + "message": "Abrir extensão do Bitwarden" }, "gettingStartedWithBitwardenPart1": { - "message": "For tips on getting started with Bitwarden visit the", + "message": "Para dicas em começar a usar o Bitwarden, visite o", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, "gettingStartedWithBitwardenPart2": { - "message": "Learning Center", + "message": "Centro de Aprendizagem", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Se a extensão não abrir, você pode precisar abrir o Bitwarden pelo ícone ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " na 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { - "message": "Help Center", + "message": "Central de Ajuda", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, "setupExtensionContentAlt": { - "message": "With the Bitwarden browser extension you can easily create new logins, access your saved logins directly from your browser toolbar, and sign in to accounts quickly using Bitwarden autofill." + "message": "Com a extensão de navegador do Bitwarden, você pode criar credenciais, acessar suas credenciais salvas diretamente pela barra do seu navegador, e se conectar a contas rapidamente usando o preenchimento automático do Bitwarden." }, "restart": { - "message": "Restart" + "message": "Reiniciar" }, "verifyProviderBankAccountWithStatementDescriptorWarning": { - "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 provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "O pagamento com uma conta bancária só está disponível para clientes nos Estados Unidos. Você precisará verificar sua conta bancária. Faremos um micro-depósito nos próximos 1-2 dias úteis. Digite o código descritor do extrato deste depósito na página de assinatura do provedor para verificar a conta bancária. A falha na verificação da conta bancária resultará em um pagamento atrasado e a sua assinatura será suspensa." }, "clickPayWithPayPal": { - "message": "Please click the Pay with PayPal button to add your payment method." + "message": "Clique no botão de Pagar com PayPal para adicionar seu método de pagamento." }, "revokeActiveSponsorshipConfirmation": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "message": "Se remover $EMAIL$, o financiamento deste plano familiar terminará. Uma vaga na sua organização ficará disponível para membros ou financiamentos após a data de renovação da organização financiada em $DATE$.", "placeholders": { "email": { "content": "$1", @@ -11717,56 +11758,56 @@ } }, "billingAddressRequiredToAddCredit": { - "message": "Billing address required to add credit.", + "message": "O endereço de faturamento é necessário para adicionar créditos.", "description": "Error message shown when trying to add credit to a trialing organization without a billing address." }, "aboutThisSetting": { - "message": "About this setting" + "message": "Sobre esta configuração" }, "permitCipherDetailsDescription": { - "message": "Bitwarden will use saved login URIs to identify which icon or change password URL should be used to improve your experience. No information is collected or saved when you use this service." + "message": "O Bitwarden usará URIs de credenciais salvas para identificar qual ícone ou URL de alteração de senha deverá ser usado para melhorar sua experiência. Nenhuma informação é coletada ou salva quando você utiliza este serviço." }, "billingAddress": { - "message": "Billing address" + "message": "Endereço de faturamento" }, "addBillingAddress": { - "message": "Add billing address" + "message": "Adicionar endereço de faturamento" }, "editBillingAddress": { - "message": "Edit billing address" + "message": "Editar endereço de faturamento" }, "noBillingAddress": { - "message": "No address on file." + "message": "Nenhum endereço cadastrado." }, "billingAddressUpdated": { - "message": "Your billing address has been updated." + "message": "Seu endereço de faturamento foi atualizado." }, "paymentDetails": { - "message": "Payment details" + "message": "Detalhes de pagamento" }, "paymentMethodUpdated": { - "message": "Your payment method has been updated." + "message": "Seu método de pagamento foi atualizado." }, "bankAccountVerified": { - "message": "Your bank account has been verified." + "message": "Sua conta bancária foi verificada." }, "availableCreditAppliedToInvoice": { - "message": "Any available credit will be automatically applied towards invoices generated for this account." + "message": "Qualquer crédito disponível será automaticamente aplicado em faturas geradas para esta conta." }, "mustBePositiveNumber": { - "message": "Must be a positive number" + "message": "Deve ser um número positivo" }, "cardSecurityCode": { - "message": "Card security code" + "message": "Código de segurança do cartão" }, "cardSecurityCodeDescription": { - "message": "Card security code, also known as CVV or CVC, is typically a 3 digit number printed on the back of your credit card or 4 digit number printed on the front above your card number." + "message": "O código de segurança do cartão, também conhecido como CVV ou CVC, normalmente é um número de 3 dígitos impresso no verso do seu cartão de crédito ou um número de 4 dígitos impresso na frente acima do número do seu cartão." }, "verifyBankAccountWarning": { - "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the Payment Details page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "O pagamento com uma conta bancária só está disponível para clientes nos Estados Unidos. Você precisará verificar sua conta bancária. Faremos um micro-depósito nos próximos 1-2 dias úteis. Digite o código descritor do extrato deste depósito na página de detalhes de pagamento para verificar a conta bancária. A falha na verificação da conta bancária resultará em um pagamento atrasado e a sua assinatura será suspensa." }, "taxId": { - "message": "Tax ID: $TAX_ID$", + "message": "ID de imposto (Tax ID): $TAX_ID$", "placeholders": { "tax_id": { "content": "$1", @@ -11775,14 +11816,14 @@ } }, "unpaidInvoices": { - "message": "Unpaid invoices" + "message": "Faturas não pagas" }, "unpaidInvoicesForServiceUser": { - "message": "Your subscription has not been paid. Contact your provider administrator to restore service to you and your clients.", + "message": "Sua assinatura não foi paga. Contate o administrador do seu provedor para restaurar o serviço a você e aos seus clientes.", "description": "A message shown in a non-dismissible dialog to service users of unpaid providers." }, "providerSuspended": { - "message": "$PROVIDER$ is suspended", + "message": "$PROVIDER$ está suspenso", "placeholders": { "provider": { "content": "$1", @@ -11791,11 +11832,11 @@ } }, "restoreProviderPortalAccessViaCustomerSupport": { - "message": "To restore access to your provider portal, contact Bitwarden Customer Support to renew your subscription.", + "message": "Para restaurar o acesso ao portal do seu provedor, contacte o suporte ao cliente do Bitwarden para renovar sua assinatura.", "description": "A message shown in a non-dismissible dialog to any user of a suspended providers." }, "restoreProviderPortalAccessViaPaymentMethod": { - "message": "Your subscription has not been paid. To restore service to you and your clients, add a payment method by $CANCELLATION_DATE$.", + "message": "Sua assinatura não foi paga. Para restaurar o serviço a você e aos seus clientes, adicione um método de pagamento até $CANCELLATION_DATE$.", "placeholders": { "cancellation_date": { "content": "$1", @@ -11805,7 +11846,7 @@ "description": "A message shown in a non-dismissible dialog to admins of unpaid providers." }, "subscribetoEnterprise": { - "message": "Subscribe to $PLAN$", + "message": "Assinar o $PLAN$", "placeholders": { "plan": { "content": "$1", @@ -11814,7 +11855,7 @@ } }, "subscribeEnterpriseSubtitle": { - "message": "Your 7-day $PLAN$ trial starts today. Add a payment method now to continue using these features after your trial ends: ", + "message": "O seu teste grátis de 7 dias do $PLAN$ começa hoje. Adicione um método de pagamento agora para continuar usando esses recursos quando o teste terminar: ", "placeholders": { "plan": { "content": "$1", @@ -11823,13 +11864,13 @@ } }, "unlimitedSecretsAndProjects": { - "message": "Unlimited secrets and projects" + "message": "Segredos e projetos ilimitados" }, "providersubscriptionCanceled": { - "message": "Subscription canceled" + "message": "Assinatura cancelada" }, "providersubCanceledmessage": { - "message": "To resubscribe, contact Bitwarden Customer Support." + "message": "Para assinar novamente, entre em contato com o suporte ao cliente do Bitwarden." }, "showMore": { "message": "Mostrar mais" @@ -11838,44 +11879,44 @@ "message": "Mostrar menos" }, "missingTaxId": { - "message": "Missing Tax ID" + "message": "ID de imposto (Tax ID) ausente" }, "missingTaxIdWarning": { - "message": "Action required: You're missing a Tax ID number in payment details. If a Tax ID is not added, your invoices may include additional tax." + "message": "Ação necessária: está faltando o número do ID de imposto (Tax ID) nos detalhes de pagamento. Se um ID de imposto não for adicionado, suas faturas podem incluir impostos adicionais." }, "moreBreadcrumbs": { - "message": "More breadcrumbs", + "message": "Mais trilhas", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." }, "addTaxId": { - "message": "Add a Tax ID" + "message": "Adicione um ID de imposto" }, "missingTaxIdCalloutTitle": { - "message": "Action required: Missing Tax ID" + "message": "Ação necessária: ID de imposto ausente" }, "missingTaxIdCalloutDescription": { - "message": "If a Tax ID is not added, your invoices may include additional tax." + "message": "Se um ID de imposto não for adicionado, as suas faturas podem incluir impostos adicionais." }, "unverifiedTaxIdWarning": { - "message": "Action required: Your Tax ID number is unverified. If your Tax ID is left unverified, your invoices may include additional tax." + "message": "Ação necessária: O número do seu ID de imposto não foi verificado. Se o seu ID de imposto não for verificado, as suas faturas podem incluir impostos adicionais." }, "editTaxId": { - "message": "Edit your Tax ID" + "message": "Edite seu ID de imposto" }, "unverifiedTaxIdCalloutTitle": { - "message": "Tax ID unverified" + "message": "ID de imposto não verificado" }, "unverifiedTaxIdCalloutDescription": { - "message": "Check your Tax ID to verify the format is correct and there are no typos." + "message": "Confira o seu ID de imposto para verificar se o formato está correto e não há erros de digitação." }, "pendingVerification": { - "message": "Pending verification" + "message": "Verificação pendente" }, "checkInputFormat": { - "message": "Check input format for typos." + "message": "Confira o formato de entrada por erros de digitação." }, "exampleTaxIdFormat": { - "message": "Example $CODE$ format: $EXAMPLE$", + "message": "Exemplo do formato de $CODE$: $EXAMPLE$", "placeholders": { "code": { "content": "$1", @@ -11888,46 +11929,46 @@ } }, "confirmKeyConnectorDomain": { - "message": "Confirm Key Connector domain" + "message": "Confirmar domínio do Key Connector" }, "requiredToVerifyBankAccountWithStripe": { - "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. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "O pagamento com uma conta bancária só está disponível para clientes nos Estados Unidos. Você precisará verificar sua conta bancária. Faremos um micro-depósito nos próximos 1-2 dias úteis. A falha na verificação da conta bancária resultará em um pagamento atrasado e a sua assinatura será suspensa." }, "verifyBankAccountWithStripe": { - "message": "We have made a micro-deposit to your bank account. This may take 1-2 business days. When you see the deposit in your account, you can verify your bank account. Failure to verify your bank account will result in a missed payment and your subscription will be suspended." + "message": "Fizemos um micro-depósito em sua conta bancária. Isso pode levar 1-2 dias úteis. Quando ver o depósito na sua conta, você poderá verificar a sua conta bancária. A falha da verificação da conta bancária resultará em um pagamento atrasado, e a sua assinatura será suspensa." }, "verifyNow": { - "message": "Verify now." + "message": "Verifique agora." }, "additionalStorageGB": { - "message": "Additional storage GB" + "message": "GB de armazenamento adicional" }, "additionalServiceAccountsV2": { - "message": "Additional machine accounts" + "message": "Contas de máquina adicionais" }, "secretsManagerSeats": { - "message": "Secrets Manager seats" + "message": "Vagas do Gerenciador de Segredos" }, "additionalStorage": { - "message": "Additional Storage" + "message": "Armazenamento adicional" }, "expandPurchaseDetails": { - "message": "Expand purchase details" + "message": "Expandir detalhes da compra" }, "collapsePurchaseDetails": { - "message": "Collapse purchase details" + "message": "Recolher detalhes da compra" }, "familiesMembership": { - "message": "Families membership" + "message": "Assinatura do Famílias" }, "planDescPremium": { - "message": "Complete online security" + "message": "Segurança on-line completa" }, "planDescFamiliesV2": { - "message": "Premium security for your family" + "message": "Segurança Premium para a sua família" }, "planDescFreeV2": { - "message": "Share with $COUNT$ other user", + "message": "Compartilhe com $COUNT$ usuário(s)", "placeholders": { "count": { "content": "$1", @@ -11936,37 +11977,37 @@ } }, "planDescEnterpriseV2": { - "message": "Advanced capabilities for any organization" + "message": "Capacidades avançadas para qualquer organização" }, "planNameCustom": { - "message": "Custom plan" + "message": "Plano personalizado" }, "planDescCustom": { - "message": "Bitwarden scales with businesses of all sizes to secure passwords and sensitive information. If you're part of a large enterprise, contact sales to request a quote." + "message": "O Bitwarden escala com empresas de todos os tamanos para proteger senhas e informações sensíveis. Se faz parte de uma grande empresa, entre em contato com o setor financeiro para solicitar um orçamento." }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "Autenticador integrado" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "Monitoramento de vazamentos" }, "andMoreFeatures": { - "message": "And more!" + "message": "E mais!" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "Armazenamento seguro de arquivos" }, "familiesUnlimitedSharing": { - "message": "Unlimited sharing - choose who sees what" + "message": "Compartilhamento ilimitado - escolha quem vê o quê" }, "familiesUnlimitedCollections": { - "message": "Unlimited family collections" + "message": "Conjuntos familiares ilimitados" }, "familiesSharedStorage": { - "message": "Shared storage for important family info" + "message": "Armazenamento compartilhado para informações importantes da família" }, "limitedUsersV2": { - "message": "Up to $COUNT$ members", + "message": "Até $COUNT$ membros", "placeholders": { "count": { "content": "$1", @@ -11975,7 +12016,7 @@ } }, "limitedCollectionsV2": { - "message": "Up to $COUNT$ collections", + "message": "Até $COUNT$ conjuntos", "placeholders": { "count": { "content": "$1", @@ -11984,13 +12025,13 @@ } }, "alwaysFree": { - "message": "Always free" + "message": "Grátis para sempre" }, "twoSecretsIncluded": { - "message": "2 secrets" + "message": "2 segredos" }, "projectsIncludedV2": { - "message": "$COUNT$ project(s)", + "message": "$COUNT$ projeto(s)", "placeholders": { "count": { "content": "$1", @@ -11999,13 +12040,13 @@ } }, "secureItemSharing": { - "message": "Secure item sharing" + "message": "Compartilhamento seguro de itens" }, "scimSupport": { - "message": "SCIM support" + "message": "Suporte a SCIM" }, "includedMachineAccountsV2": { - "message": "$COUNT$ machine accounts", + "message": "$COUNT$ contas de máquina", "placeholders": { "count": { "content": "$1", @@ -12014,142 +12055,139 @@ } }, "enterpriseSecurityPolicies": { - "message": "Enterprise security policies" + "message": "Políticas empresariais de segurança" }, "selfHostOption": { - "message": "Self-host option" + "message": "Opção auto-hospedada" }, "complimentaryFamiliesPlan": { - "message": "Complimentary families plan for all users" + "message": "Plano de cortesia para famílias de todos os usuários" }, "strengthenCybersecurity": { - "message": "Strengthen cybersecurity" + "message": "Fortaleça a cibersegurança" }, "boostProductivity": { - "message": "Boost productivity" + "message": "Aumente a produtividade" }, "seamlessIntegration": { - "message": "Seamless integration" + "message": "Integração harmoniosa" }, "families": { - "message": "Families" + "message": "Famílias" }, "upgradeToFamilies": { - "message": "Upgrade to Families" + "message": "Fazer upgrade para o Famílias" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Fazer upgrade para o Premium" }, "familiesUpdated": { - "message": "You've upgraded to Families!" + "message": "Você fez upgrade para o Famílias!" }, "taxCalculationError": { - "message": "There was an error calculating tax for your location. Please try again." + "message": "Houve um erro ao calcular o imposto para a sua localização. Tente novamente." }, "individualUpgradeWelcomeMessage": { - "message": "Welcome to Bitwarden" + "message": "Boas-vindas ao Bitwarden" }, "individualUpgradeDescriptionMessage": { - "message": "Unlock more security features with Premium, or start sharing items with Families" + "message": "Desbloqueie mais recursos de segurança com o Premium, ou comece a compartilhar itens com o Famílias" }, "individualUpgradeTaxInformationMessage": { - "message": "Prices exclude tax and are billed annually." + "message": "Os preços excluem os impostos e são cobrados anualmente." }, "organizationNameDescription": { - "message": "Your organization name will appear in invitations you send to members." + "message": "O nome da sua organização aparecerá nos convites que você enviar para os membros." }, "continueWithoutUpgrading": { - "message": "Continue without upgrading" + "message": "Continuar sem fazer upgrade" }, "upgradeYourPlan": { - "message": "Upgrade your plan" + "message": "Fazer upgrade" }, "upgradeNow": { - "message": "Upgrade now" + "message": "Fazer upgrade agora" }, "formWillCreateNewFamiliesOrgMessage": { - "message": "Completing this form will create a new Families organization. You can upgrade your Free organization from the Admin Console." + "message": "Completar este formulário criará uma organização do Famílias. Você pode fazer upgrade da sua organização grátis pelo painel de administração." }, "upgradeErrorMessage": { - "message": "We encountered an error while processing your upgrade. Please try again." + "message": "Encontramos um erro ao processar seu upgrade. Tente novamente." }, "bitwardenFreeplanMessage": { - "message": "You have the Bitwarden Free plan" + "message": "Você tem o plano grátis do Bitwarden" }, "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "message": "Faça upgrade para segurança completa" }, "viewbusinessplans": { - "message": "View business plans" + "message": "Ver planos empresariais" }, "updateEncryptionSettings": { - "message": "Update encryption settings" + "message": "Atualizar configurações de criptografia" }, "updateYourEncryptionSettings": { - "message": "Update your encryption settings" - }, - "updateSettings": { - "message": "Update settings" + "message": "Atualize suas configurações de criptografia" }, "algorithm": { - "message": "Algorithm" + "message": "Algoritmo" }, "encryptionKeySettingsHowShouldWeEncryptYourData": { - "message": "Choose how Bitwarden should encrypt your vault data. All options are secure, but stronger methods offer better protection - especially against brute-force attacks. Bitwarden recommends the default setting for most users." + "message": "Escolha como que o Bitwarden deve criptografar os dados do seu cofre. Todas as opções são seguras, mas métodos mais fortes oferecem proteção melhor - especialmente contra ataques de força bruta. O Bitwarden recomenda a pré-configuração para a maioria dos usuários." }, "encryptionKeySettingsIncreaseImproveSecurity": { - "message": "Increasing the values above the default will improve security, but your vault may take longer to unlock as a result." + "message": "Aumentar os valores acima do padrão melhorará a segurança, mas como resultado, o seu cofre pode demorar mais tempo para ser desbloqueado." }, "encryptionKeySettingsAlgorithmPopoverTitle": { - "message": "About encryption algorithms" + "message": "Sobre algoritmos de criptografia" }, "encryptionKeySettingsAlgorithmPopoverPBKDF2": { - "message": "PBKDF2-SHA256 is a well-tested encryption method that balances security and performance. Good for all users." + "message": "O PBKDF2-SHA256 é um método de criptografia bem testado que equilibra a segurança e o desempenho. Bom para todos os usuários." }, "encryptionKeySettingsAlgorithmPopoverArgon2Id": { - "message": "Argon2id offers stronger protection against modern attacks. Best for advanced users with powerful devices." + "message": "O Argon2id oferece uma proteção mais forte contra ataques modernos. É melhor para usuários avançados com dispositivos robustos." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "CEP / Código postal" }, "cardNumberLabel": { - "message": "Card number" + "message": "Número do cartão" }, "startFreeFamiliesTrial": { - "message": "Start free Families trial" + "message": "Iniciar teste grátis do Famílias" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "Bloquear criação de conta para domínios reivindicados" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "Impeça que os usuários criem contas fora da sua organização usando endereços de e-mail de domínios reivindicados." }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "Um domínio deve ser reivindicado antes de ativar esta política." }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "Configure um método de desbloqueio para alterar a ação do tempo limite do cofre." }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "Os requisitos de política empresarial foram aplicados às suas opções de tempo limite" }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "O tempo limite do seu cofre excede as restrições estabelecidas pela sua organização." }, "neverLockWarning": { - "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + "message": "Você tem certeza que deseja usar a opção \"Nunca\"? Ao usar o \"Nunca\", a chave de criptografia do seu cofre é armazenada no seu dispositivo. Se você usar esta opção, deve garantir que mantém seu dispositivo devidamente protegido." }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Ação do tempo limite" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Tempo limite da sessão" }, "appearance": { - "message": "Appearance" + "message": "Aparência" }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "O tempo limite excede a restrição configurada pela sua organização: máximo de $HOURS$ hora(s) e $MINUTES$ minuto(s)", "placeholders": { "hours": { "content": "$1", @@ -12162,9 +12200,12 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Nenhum aplicativo crítico foi selecionado" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Tem certeza que deseja continuar?" + }, + "userVerificationFailed": { + "message": "Falha na verificação do usuário." } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 4e240bafc19..73ba923b416 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1375,7 +1375,7 @@ "message": "Iniciar sessão com o dispositivo" }, "loginWithDeviceEnabledNote": { - "message": "O início de sessão com o dispositivo deve ser ativado nas definições da aplicação Bitwarden. Precisa de outra opção?" + "message": "O início de sessão com o dispositivo deve ser ativado nas definições da app Bitwarden. Precisa de outra opção?" }, "needAnotherOptionV1": { "message": "Precisa de outra opção?" @@ -3050,7 +3050,7 @@ "message": "O crédito da sua conta pode ser utilizado para efetuar compras. Qualquer crédito disponível será automaticamente aplicado às faturas geradas para esta conta." }, "goPremium": { - "message": "Tornar-se Premium", + "message": "Torne-se Premium", "description": "Another way of saying \"Get a Premium membership\"" }, "premiumUpdated": { @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB de armazenamento encriptado para anexos de ficheiros." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ de armazenamento encriptado para anexos de ficheiros.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Opções proprietárias de verificação de dois passos, como YubiKey e Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "A sua subscrição Premium terminou" + }, + "premiumSubscriptionEndedDesc": { + "message": "Para recuperar o acesso ao seu arquivo, reinicie a sua subscrição Premium. Se editar os detalhes de um item arquivado antes de reiniciar, ele será movido de volta para o seu cofre." + }, + "restartPremium": { + "message": "Reiniciar Premium" + }, "additionalStorageGb": { "message": "Armazenamento adicional (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Saber mais" }, + "migrationsFailed": { + "message": "Ocorreu um erro ao atualizar as definições de encriptação." + }, + "updateEncryptionSettingsTitle": { + "message": "Atualize as suas definições de encriptação" + }, + "updateEncryptionSettingsDesc": { + "message": "As novas definições de encriptação recomendadas irão melhorar a segurança da sua conta. Introduza a sua palavra-passe mestra para atualizar agora." + }, + "confirmIdentityToContinue": { + "message": "Confirme a sua identidade para continuar" + }, + "enterYourMasterPassword": { + "message": "Introduza a sua palavra-passe mestra" + }, + "updateSettings": { + "message": "Atualizar definições" + }, "deleteRecoverDesc": { "message": "Introduza o seu endereço de e-mail abaixo para recuperar e eliminar a sua conta." }, @@ -9820,6 +9856,14 @@ "message": "Demasiado caro", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "A passar para o plano gratuito", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "A passar para uma organização gratuita", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Gratuito durante 1 ano" }, @@ -10766,7 +10810,7 @@ "message": "Saiba mais sobre as iterações do KDF" }, "learnMoreAboutLocalization": { - "message": "Saiba mais sobre a localização" + "message": "Saiba mais sobre a tradução" }, "learnMoreAboutWebsiteIcons": { "message": "Saiba mais sobre a utilização de ícones de sites" @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Extensão Bitwarden instalada!" }, - "openTheBitwardenExtension": { - "message": "Abrir a extensão Bitwarden" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "A extensão Bitwarden está instalada! Abra a extensão para iniciar sessão e começar o preenchimento automático." + "bitwardenExtensionIsInstalled": { + "message": "A extensão Bitwarden está instalada!" }, "openExtensionToAutofill": { "message": "Abra a extensão para iniciar sessão e começar o preenchimento automático." @@ -11687,6 +11720,14 @@ "message": "Centro de aprendizagem", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Se a extensão não tiver sido aberta, poderá ter de abrir o Bitwarden através do ícone ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " na 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Centro de ajuda", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Atualize as suas definições de encriptação" }, - "updateSettings": { - "message": "Atualizar definições" - }, "algorithm": { "message": "Algoritmo" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Tem a certeza de que deseja continuar?" + }, + "userVerificationFailed": { + "message": "Falha na verificação do utilizador." } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 5b03a1b7440..8efd88e49aa 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB stocare criptată pentru fișiere atașate." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Stocare adițională (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Aflați mai multe" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Introduceți adresa de e-mail mai jos pentru a vă recupera și șterge contul." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index f2b9856e97a..0691b4e654b 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 ГБ зашифрованного хранилища для вложенных файлов." }, + "premiumSignUpStorageV2": { + "message": "Зашифрованного хранилища для вложенных файлов: $SIZE$", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Проприетарные варианты двухэтапной аутентификации, такие как YubiKey или Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Ваша подписка Премиум закончилась" + }, + "premiumSubscriptionEndedDesc": { + "message": "Чтобы восстановить доступ к своему архиву, подключите подписку Премиум повторно. Если вы измените сведения об архивированном элементе перед переподключением, он будет перемещен обратно в ваше хранилище." + }, + "restartPremium": { + "message": "Переподключить Премиум" + }, "additionalStorageGb": { "message": "Дополнительное место (ГБ)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Подробнее" }, + "migrationsFailed": { + "message": "Произошла ошибка при обновлении настроек шифрования." + }, + "updateEncryptionSettingsTitle": { + "message": "Обновите настройки шифрования" + }, + "updateEncryptionSettingsDesc": { + "message": "Новые рекомендуемые настройки шифрования повысят безопасность вашего аккаунта. Введите мастер-пароль, чтобы обновить сейчас." + }, + "confirmIdentityToContinue": { + "message": "Подтвердите вашу личность, чтобы продолжить" + }, + "enterYourMasterPassword": { + "message": "Введите ваш мастер-пароль" + }, + "updateSettings": { + "message": "Обновить настройки" + }, "deleteRecoverDesc": { "message": "Введите свой адрес email, чтобы восстановить и удалить ваш аккаунт." }, @@ -9820,6 +9856,14 @@ "message": "Слишком дорого", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Переход на бесплатный план", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Переключение на бесплатную организацию", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Бесплатно на 1 год" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Расширение Bitwarden установлено!" }, - "openTheBitwardenExtension": { - "message": "Открыть расширение Bitwarden" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Расширение Bitwarden установлено! Откройте расширение, чтобы авторизоваться и начать автозаполнение." + "bitwardenExtensionIsInstalled": { + "message": "Расширение Bitwarden установлено!" }, "openExtensionToAutofill": { "message": "Откройте расширение, чтобы авторизоваться и начать использовать автозаполнение." @@ -11687,6 +11720,14 @@ "message": "База знаний", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " на панели инструментов.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Центр поддержки", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Обновите настройки шифрования" }, - "updateSettings": { - "message": "Обновить настройки" - }, "algorithm": { "message": "Алгоритм" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Вы действительно хотите продолжить?" + }, + "userVerificationFailed": { + "message": "Проверка пользователя не удалась." } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 65838aec607..d4c4d143600 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 933a3494404..70578e5bf59 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB šifrovaného úložiska pre prílohy." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietárne možnosti dvojstupňového prihlásenia ako napríklad YubiKey a Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Dodatočné úložisko (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Zistiť viac" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Zadajte vašu emailovú adresu ak chcete obnoviť a zrušiť vaše konto." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Rozšírenie Bitwarden nainštalované!" }, - "openTheBitwardenExtension": { - "message": "Otvoriť rozšírenie Bitwarden" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Rozšírenie Bitwarden je nainštalované! Otvorte rozšírenie, prihláste sa a začnite automaticky vypĺňať." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Otvorte rozšírenie, prihláste sa a začnite automatické vypĺňanie." @@ -11687,6 +11720,14 @@ "message": "Vzdelávacie centrum", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Centrum pomoci", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Ste si istí, že chcete pokračovať?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index b1d5da3db34..10c734ec6b7 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Dodatna hramba (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Več o tem" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 9052035c174..0f8a2760370 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Saznaj više" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index c7dd63b5805..f1f7dd3ca73 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1ГБ шифровано складиште за прилоге." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Приоритарне опције пријаве у два корака као што су YubiKey и Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Додатно складиште (ГБ)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Сазнај више" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Ажурирај подешавања" + }, "deleteRecoverDesc": { "message": "Унесите свој имејл да бисте опоравили и избрисали налог." }, @@ -9820,6 +9856,14 @@ "message": "Превише скупо", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Бесплатно 1 годину" }, @@ -11339,14 +11383,6 @@ "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": "Ваша претплата ће ускоро бити обновљена. Да бисте обезбедили непрекинуто пружање услуге, контактирајте $RESELLER$ ради потврде обновљења пре $RENEWAL_DATE$.", "placeholders": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden екстензија инсталираана!" }, - "openTheBitwardenExtension": { - "message": "Отворити Bitwarden екстензију" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Отворите екстензију да бисте се пријавили и започели ауто-попуњавање." @@ -11687,6 +11720,14 @@ "message": "Центар за учење", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Центар за помоћ", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Ажурирајте своје поставке за шифровање" }, - "updateSettings": { - "message": "Ажурирај подешавања" - }, "algorithm": { "message": "Алгоритам" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index a90524b95bc..7f1275ee75c 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -236,7 +236,7 @@ "message": "Applikationer markerade som kritiska" }, "criticalApplicationsMarkedSuccess": { - "message": "$COUNT$ applications marked as critical", + "message": "$COUNT$ applikationer markerade som kritiska", "placeholders": { "count": { "content": "$1", @@ -269,7 +269,7 @@ "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { - "message": "Members with at-risk passwords" + "message": "Medlemmar med lösenord i riskzonen" }, "membersWillReceiveSecurityTask": { "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB krypterad lagring." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Premium-alternativ för tvåstegsverifiering, såsom YubiKey och Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Starta om Premium" + }, "additionalStorageGb": { "message": "Ytterligare lagring (GB)" }, @@ -4482,7 +4500,7 @@ "message": "Sammanställer insikter..." }, "loadingProgress": { - "message": "Loading progress" + "message": "Inläsningsförlopp" }, "thisMightTakeFewMinutes": { "message": "Detta kan ta några minuter." @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Läs mer" }, + "migrationsFailed": { + "message": "Ett fel inträffade när krypteringsinställningarna skulle uppdateras." + }, + "updateEncryptionSettingsTitle": { + "message": "Uppdatera dina krypteringsinställningar" + }, + "updateEncryptionSettingsDesc": { + "message": "De nya rekommenderade krypteringsinställningarna kommer att förbättra säkerheten för ditt konto. Ange ditt huvudlösenord för att uppdatera nu." + }, + "confirmIdentityToContinue": { + "message": "Bekräfta din identitet för att fortsätta" + }, + "enterYourMasterPassword": { + "message": "Ange ditt huvudlösenord" + }, + "updateSettings": { + "message": "Uppdatera inställningar" + }, "deleteRecoverDesc": { "message": "Ange din e-postadress nedan för att återställa och radera ditt konto." }, @@ -6614,7 +6650,7 @@ "message": "Ditt huvudlösenord uppfyller inte en eller flera av organisationens policyer. För att få tillgång till valvet måste du uppdatera ditt huvudlösenord nu. Om du fortsätter loggas du ut från din nuvarande session och måste logga in igen. Aktiva sessioner på andra enheter kan fortsätta att vara aktiva i upp till en timme." }, "automaticAppLoginWithSSO": { - "message": "Automatic login with SSO" + "message": "Automatisk inloggning med SSO" }, "automaticAppLoginWithSSODesc": { "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." @@ -7393,7 +7429,7 @@ "message": "Åtkomst nekad. Du har inte behörighet att visa den här sidan." }, "noPageAccess": { - "message": "You do not have access to this page" + "message": "Du har inte tillgång till denna sida" }, "masterPassword": { "message": "Huvudlösenord" @@ -9820,6 +9856,14 @@ "message": "För dyrt", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Växlar till kostnadsfri plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Växlar till kostnadsfri organisation", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Gratis i 1 år" }, @@ -11339,14 +11383,6 @@ "openedExtensionViewAtRiskPasswords": { "message": "Framgångsrikt öppnat webbläsartillägget Bitwarden. Du kan nu granska dina risklösenord." }, - "openExtensionManuallyPart1": { - "message": "Vi hade problem med att öppna webbläsartillägget Bitwarden. Öppna Bitwarden-ikonen", - "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": "från verktygsfältet.", - "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": "Din prenumeration kommer snart att förnyas. För att säkerställa oavbruten service, kontakta $RESELLER$ för att bekräfta din förnyelse före $RENEWAL_DATE$.", "placeholders": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden-tillägg installerat!" }, - "openTheBitwardenExtension": { - "message": "Öppna Bitwarden-tillägget" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Bitwarden-tillägget är installerat! Öppna tillägget för att logga in och starta autofyllning." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden-tillägget är installerat!" }, "openExtensionToAutofill": { "message": "Öppna tillägget för att logga in och starta autofyllning." @@ -11687,6 +11720,14 @@ "message": "Lärcentrum", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Om tillägget inte öppnades kan du behöva öppna Bitwarden från ikonen ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " på verktygsfältet.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Hjälpcenter", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Uppdatera dina krypteringsinställningar" }, - "updateSettings": { - "message": "Uppdatera inställningar" - }, "algorithm": { "message": "Algoritm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Är du säker på att du vill fortsätta?" + }, + "userVerificationFailed": { + "message": "Verifiering av användare misslyckades." } } diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index eabda4c7611..98970962d69 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "கோப்பு இணைப்புகளுக்கு 1 GB என்க்ரிப்ட் செய்யப்பட்ட சேமிப்பகம்." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "YubiKey மற்றும் Duo போன்ற தனியுரிம இரண்டு-படி உள்நுழைவு விருப்பங்கள்." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "கூடுதல் சேமிப்பகம் (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "மேலும் அறிக" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "உங்கள் கணக்கை மீட்டு நீக்க, கீழே உங்கள் மின்னஞ்சல் முகவரியை உள்ளிடவும்." }, @@ -9820,6 +9856,14 @@ "message": "மிகவும் விலை உயர்ந்தது", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "1 வருடத்திற்கு இலவசம்" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden நீட்டிப்பு நிறுவப்பட்டது!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "உள்நுழைந்து தானாக நிரப்பத் தொடங்க நீட்டிப்பைத் திறக்கவும்." @@ -11687,6 +11720,14 @@ "message": "கற்றல் மையம்", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "உதவி மையம்", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index b23e2e3cbac..608215f8155 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index bcaada3bea5..c20cc2e5d8c 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Additional storage (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Learn more" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Enter your email address below to recover and delete your account." }, @@ -9820,6 +9856,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11687,6 +11720,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 82ca8d37b61..829c0ca17dc 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "Dosya ekleri için 1 GB şifrelenmiş depolama." }, + "premiumSignUpStorageV2": { + "message": "Dosya ekleri için $SIZE$ şifrelenmiş depolama.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "YubiKey ve Duo gibi marka bazlı iki aşamalı giriş seçenekleri." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Ek depolama alanı (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Daha fazla bilgi al" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Ayarları güncelle" + }, "deleteRecoverDesc": { "message": "Hesabınızı kurtarmak ve silmek için e-posta adresinizi yazın." }, @@ -9820,6 +9856,14 @@ "message": "Çok pahalı", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "1 yıl boyunca ücretsiz" }, @@ -11339,14 +11383,6 @@ "openedExtensionViewAtRiskPasswords": { "message": "Bitwarden tarayıcı uzantısı başarıyla açıldı. Artık risk altındaki parolalarınızı gözden geçirebilirsiniz." }, - "openExtensionManuallyPart1": { - "message": "Bitwarden tarayıcı uzantısını açarken sorun yaşadık. Araç çubuğundan Bitwarden simgesini", - "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": "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": "Aboneliğiniz yakında yenilenecektir. Kesintisiz hizmet için, $RENEWAL_DATE$ tarihinden önce $RESELLER$ ile iletişime geçerek yenileme işleminizi onaylayın.", "placeholders": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden uzantısı yüklendi!" }, - "openTheBitwardenExtension": { - "message": "Bitwarden uzantısını aç" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden uzantısı yüklendi!" }, "openExtensionToAutofill": { "message": "Otomatik doldurmaya başlamak için uzantıyı açıp giriş yapın." @@ -11687,6 +11720,14 @@ "message": "Öğrenim Merkezi", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "Uzantı açılmadıysa araç çubuğundaki simgeye ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " tıklayarak Bitwarden'ı açmanız gerekebilir.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Yardım Merkezi", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Şifreleme ayarlarınızı güncelleyin" }, - "updateSettings": { - "message": "Ayarları güncelle" - }, "algorithm": { "message": "Algoritma" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Devam etmek istediğinizden emin misiniz?" + }, + "userVerificationFailed": { + "message": "Kullanıcı doğrulaması başarısız oldu." } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 85fbbc317e7..be571b0c146 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1 ГБ зашифрованого сховища для файлів." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Додаткові можливості двоетапної авторизації, як-от YubiKey та Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Додаткове сховище (ГБ)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Докладніше" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Update settings" + }, "deleteRecoverDesc": { "message": "Введіть свою адресу е-пошти внизу, щоб відновити і видалити обліковий запис." }, @@ -9820,6 +9856,14 @@ "message": "Висока ціна", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Безплатно на 1 рік" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Розширення Bitwarden встановлено!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Відкрийте розширення, щоб увійти й користуватися автозаповненням." @@ -11687,6 +11720,14 @@ "message": "Навчальний центр", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Довідковий центр", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Update your encryption settings" }, - "updateSettings": { - "message": "Update settings" - }, "algorithm": { "message": "Algorithm" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index b361fb88822..35e7db50023 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "1GB bộ nhớ lưu trữ được mã hóa cho các tệp đính kèm." }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "Các tùy chọn xác minh hai bước như YubiKey và Duo." }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "Dung lượng lưu trữ bổ sung (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "Tìm hiểu thêm" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "Cập nhật cài đặt" + }, "deleteRecoverDesc": { "message": "Nhập địa chỉ email của bạn vào bên dưới để khôi phục và xóa tài khoản của bạn." }, @@ -9820,6 +9856,14 @@ "message": "Quá đắt", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Miễn phí 1 năm" }, @@ -11339,14 +11383,6 @@ "openedExtensionViewAtRiskPasswords": { "message": "Đã mở tiện ích mở rộng trình duyệt Bitwarden thành công. Bây giờ bạn có thể xem lại mật khẩu có rủi ro của mình." }, - "openExtensionManuallyPart1": { - "message": "Chúng tôi gặp sự cố khi mở tiện ích mở rộng trình duyệt Bitwarden. Mở biểu tượng 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": "từ thanh công cụ.", - "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": "Đăng ký của bạn sẽ sớm được gia hạn. Để đảm bảo dịch vụ không bị gián đoạn, hãy liên hệ $RESELLER$ để xác nhận gia hạn trước $RENEWAL_DATE$.", "placeholders": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Đã cài đặt tiện ích mở rộng Bitwarden!" }, - "openTheBitwardenExtension": { - "message": "Mở tiện ích mở rộng Bitwarden" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Tiện ích mở rộng Bitwarden đã được cài đặt! Mở tiện ích mở rộng để đăng nhập và bắt đầu tự động điền." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Mở tiện ích mở rộng để đăng nhập và bắt đầu tự động điền." @@ -11687,6 +11720,14 @@ "message": "Trung tâm học tập", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on 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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Trung tâm Trợ giúp", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "Cập nhật cài đặt mã hóa của bạn" }, - "updateSettings": { - "message": "Cập nhật cài đặt" - }, "algorithm": { "message": "Thuật toán" }, @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 6ccfef71f97..7d898c79952 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -236,7 +236,7 @@ "message": "标记为关键的应用程序" }, "criticalApplicationsMarkedSuccess": { - "message": "$COUNT$ 个应用程序标记为关键", + "message": "$COUNT$ 个标记为关键的应用程序", "placeholders": { "count": { "content": "$1", @@ -1477,7 +1477,7 @@ "message": "移除通行密钥" }, "removePasskeyInfo": { - "message": "如果所有通行密钥被移除,不使用主密码您将无法登录新的设备。" + "message": "如果所有通行密钥被移除,没有主密码您将无法登录新的设备。" }, "passkeyLimitReachedInfo": { "message": "已达到通行密钥上限。请移除一个通行密钥后再添加其他通行密钥。" @@ -2490,7 +2490,7 @@ "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": { @@ -2500,7 +2500,7 @@ "message": "要强制实施 Duo 方式的两步登录,请使用下面的选项。" }, "twoStepLoginOrganizationSsoDesc": { - "message": "如果您已设置或计划设置 SSO,两步登录可能已经通过您的身份提供程序强制实施了。" + "message": "如果您已设置 SSO 或计划设置 SSO,两步登录可能已经通过您的身份提供程序强制实施了。" }, "twoStepLoginRecoveryWarning": { "message": "启用两步登录可能会将您永久锁定在 Bitwarden 账户之外。当您无法使用常规的两步登录提供程序(例如您丢失了设备)时,可以使用恢复代码访问您的账户。如果您失去对您账户的访问,Bitwarden 支持也无法帮助您。我们建议您写下或打印恢复代码,并将其妥善保管。" @@ -2547,7 +2547,7 @@ "message": "需要高级版" }, "premiumRequiredDesc": { - "message": "使用此功能需要高级会员资格。" + "message": "需要高级会员才能使用此功能。" }, "youHavePremiumAccess": { "message": "您拥有高级版访问权限" @@ -3060,7 +3060,16 @@ "message": "将您的账户升级为高级会员,将解锁一些强大的附加功能。" }, "premiumSignUpStorage": { - "message": "1 GB 文件附件加密存储。" + "message": "1 GB 文件附件加密存储空间。" + }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ 文件附件加密存储空间。", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } }, "premiumSignUpTwoStepOptions": { "message": "专有的两步登录选项,如 YubiKey 和 Duo。" @@ -3103,7 +3112,7 @@ } }, "bitwardenFamiliesPlan": { - "message": "Bitwarden 家庭方案。" + "message": "Bitwarden 家庭版方案。" }, "addons": { "message": "附加项目" @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "附加存储 (GB)" }, @@ -3350,7 +3368,7 @@ "message": "GB 存储空间将增加" }, "gbStorageRemove": { - "message": "GB 存储空间将删除" + "message": "GB 存储空间将移除" }, "storageAddNote": { "message": "添加存储空间将会调整计费总金额,并立即通过您的付款方式进行扣款。第一笔费用将按当前计费周期的剩余时间按比例分配。" @@ -3541,7 +3559,7 @@ "message": "创建不限数量的集合" }, "gbEncryptedFileStorage": { - "message": "$SIZE$ 加密文件存储", + "message": "$SIZE$ 加密文件存储空间", "placeholders": { "size": { "content": "$1", @@ -3694,7 +3712,7 @@ "message": "确定要移除此用户吗?" }, "removeOrgUserConfirmation": { - "message": "移除成员后,他们将不再具有对组织数据的访问权限,并且此操作无法撤销。要将此成员添加回组织,必须再次邀请他们并加入。" + "message": "移除成员后,他们将不再具有对组织数据的访问权限,并且此操作无法撤销。要将此成员添加回组织,必须再次邀请他们并重新入职。" }, "revokeUserConfirmation": { "message": "撤销成员后,他们将不再具有对组织数据的访问权限。要快速恢复此成员的访问权限,请转到「已撤销」标签页。" @@ -3796,13 +3814,13 @@ "message": "管理员" }, "adminDesc": { - "message": "管理组织的访问权限,所有集合、成员、报告,以及安全设置" + "message": "管理组织的访问权限、所有集合、成员、报告,以及安全设置" }, "user": { "message": "用户" }, "userDesc": { - "message": "访问并添加项目到已分配的集合" + "message": "访问项目和将项目添加到已分配的集合" }, "all": { "message": "全部" @@ -4124,7 +4142,7 @@ } }, "removeUserIdAccess": { - "message": "移除了 $ID$ 的访问权限", + "message": "移除 $ID$ 的访问权限", "placeholders": { "id": { "content": "$1", @@ -4350,10 +4368,10 @@ "message": "检查您的电子邮箱" }, "followTheLinkInTheEmailSentTo": { - "message": "点击发送到电子邮件中的链接" + "message": "点击发送到" }, "andContinueCreatingYourAccount": { - "message": "然后继续创建您的账户。" + "message": "的电子邮件中的链接,然后继续创建您的账户。" }, "noEmail": { "message": "没有收到电子邮件吗?" @@ -4608,6 +4626,24 @@ "learnMore": { "message": "进一步了解" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "更新设置" + }, "deleteRecoverDesc": { "message": "请在下面输入您的电子邮箱地址以恢复和删除您的账户。" }, @@ -5810,11 +5846,11 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "所有项目都将归组织所有并保存至组织,从而实现组织范围的控制、可见性和报告功能。启用后,每个成员都将拥有一个默认集合来存储项目。进一步了解", + "message": "所有项目都将归组织所有并保存至组织中,从而实现组织范围的控制、可见性和报告。启用后,每个成员都可以使用默认集合来存储项目。进一步了解如何管理", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { - "message": "凭据的生命周期", + "message": "凭据生命周期", "description": "This will be used as a hyperlink" }, "organizationDataOwnershipWarningTitle": { @@ -6115,7 +6151,7 @@ "message": "加强企业安全。" }, "strengthenBusinessSecurityDescription": { - "message": "通过 SSO 集成、事件日志和访问轮换,保持机器和人类对机密访问权限的严格控制。" + "message": "通过 SSO 集成、事件日志和访问权限轮换,保持机器和人类对机密访问权限的严格控制。" }, "tryItNow": { "message": "立即体验" @@ -6385,7 +6421,7 @@ "message": "确定要移除以下用户吗?该过程可能需要几秒钟才能完成,并且不能中断或取消。" }, "removeOrgUsersConfirmation": { - "message": "移除成员后,他们将不再具有对组织数据的访问权限,并且此操作无法撤销。要将成员添加回组织,必须再次邀请他们并加入。该过程可能需要几秒钟才能完成,并且不能被中断或取消。" + "message": "移除成员后,他们将不再具有对组织数据的访问权限,并且此操作无法撤销。要将此成员添加回组织,必须再次邀请他们并重新入职。该过程可能需要几秒钟才能完成,并且不能被中断或取消。" }, "revokeUsersWarning": { "message": "撤销成员后,他们将不再具有对组织数据的访问权限。要快速恢复成员的访问权限,请转到「已撤销」标签页。该过程可能需要几秒钟才能完成,并且不能被中断或取消。" @@ -6882,7 +6918,7 @@ "message": "单点登录配置已保存" }, "sponsoredFamilies": { - "message": "免费 Bitwarden 家庭" + "message": "免费的 Bitwarden 家庭版" }, "sponsoredBitwardenFamilies": { "message": "赞助的家庭" @@ -6891,22 +6927,22 @@ "message": "没有赞助的家庭" }, "nosponsoredFamiliesDetails": { - "message": "已赞助的非成员家庭方案将显示在这里" + "message": "已赞助的非成员家庭版方案将显示在这里" }, "sponsorshipFreeBitwardenFamilies": { - "message": "您的组织成员有资格获得免费的 Bitwarden 家庭计划。您可以为不是您的 Bitwarden 组织成员的员工赞助免费 Bitwarden 家庭。赞助非成员需要您的组织内有可用的席位。" + "message": "您的组织成员有资格获得免费的 Bitwarden 家庭版。您可以为不是您的 Bitwarden 组织成员的员工赞助免费的 Bitwarden 家庭版。赞助非成员需要您的组织内有可用的席位。" }, "sponsoredFamiliesRemoveActiveSponsorship": { "message": "当您移除某个活动赞助,在被赞助组织的续费日期之后,您的组织中将释放一个可用的席位。" }, "sponsoredFamiliesEligible": { - "message": "您和您的家人有资格获得免费的 Bitwarden 家庭版计划。使用您的个人电子邮箱兑换,即使您不在工作中,也能确保您的数据安全。" + "message": "您和您的家人有资格获得免费的 Bitwarden 家庭版。使用您的个人电子邮箱兑换,即使您不在工作中,也能确保您的数据安全。" }, "sponsoredFamiliesEligibleCard": { - "message": "立即兑换免费的 Bitwarden 家庭方案,即使您不在工作中,也能确保您的数据安全。" + "message": "立即兑换免费的 Bitwarden 家庭版方案,即使您不在工作中,也能确保您的数据安全。" }, "sponsoredFamiliesIncludeMessage": { - "message": "Bitwarden 家庭方案包含" + "message": "Bitwarden 家庭版方案包含" }, "sponsoredFamiliesPremiumAccess": { "message": "最多 6 个高级版访问权限的用户" @@ -6921,10 +6957,10 @@ "message": "没有成员家庭" }, "noMemberFamiliesDescription": { - "message": "已兑换家庭方案的成员将在这里显示" + "message": "已兑换家庭版方案的成员将在这里显示" }, "membersWithSponsoredFamilies": { - "message": "您的组织成员有资格获得免费的 Bitwarden 家庭计划。在这里,您可以看到已赞助了家庭组织的成员。" + "message": "您的组织成员有资格获得免费的 Bitwarden 家庭版。在这里,您可以看到已赞助了家庭版组织的成员。" }, "organizationHasMemberMessage": { "message": "由于他们是您组织的成员,因此不能将赞助发送给 $EMAIL$。", @@ -6939,7 +6975,7 @@ "message": "链接已失效。请让赞助方重新发送邀请。" }, "reclaimedFreePlan": { - "message": "重新申领了免费方案" + "message": "已恢复为免费版方案" }, "redeem": { "message": "兑换" @@ -6948,19 +6984,19 @@ "message": "选择您希望被赞助的组织" }, "familiesSponsoringOrgSelect": { - "message": "您想兑换哪一个免费家庭邀请?" + "message": "您想兑换哪一个免费家庭版邀请?" }, "sponsoredFamiliesEmail": { - "message": "输入您的个人电子邮箱以兑换 Bitwarden 家庭" + "message": "输入您的个人电子邮箱以兑换 Bitwarden 家庭版" }, "sponsoredFamiliesLeaveCopy": { - "message": "如果您移除邀请或您被从赞助组织中移除,您的家庭赞助将在下一个续费日到期。" + "message": "如果您移除邀请或您被从赞助组织中移除,您的家庭版赞助将在下一个续费日到期。" }, "acceptBitwardenFamiliesHelp": { - "message": "接受现有组织的邀请或创建一个新的家庭组织。" + "message": "接受现有组织的邀请或创建一个新的家庭版组织。" }, "setupSponsoredFamiliesLoginDesc": { - "message": "您已被邀请加入免费的 Bitwarden 家庭方案组织。要继续,您需要登录到接收邀请的账户。" + "message": "您已被邀请加入免费的 Bitwarden 家庭版方案组织。要继续,您需要登录到接收邀请的账户。" }, "sponsoredFamiliesAcceptFailed": { "message": "无法接受邀请。请通过您的企业账户重新发送邀请邮件,然后重试。" @@ -6975,10 +7011,10 @@ } }, "sponsoredFamiliesOffer": { - "message": "接受免费的 Bitwarden 家庭组织" + "message": "接受免费的 Bitwarden 家庭版" }, "sponsoredFamiliesOfferRedeemed": { - "message": "免费 Bitwarden 家庭邀请已成功兑换" + "message": "免费的 Bitwarden 家庭版邀请已成功兑换" }, "redeemed": { "message": "已兑换" @@ -7005,7 +7041,7 @@ } }, "freeFamiliesPlan": { - "message": "免费家庭方案" + "message": "免费家庭版方案" }, "redeemNow": { "message": "立即兑换" @@ -7149,7 +7185,7 @@ "message": "赞助邀请已过期,您可以删除您创建的组织,以避免 7 天试用期结束时收取费用。您也可以关闭这个提示,以保留此组织并承担计费责任。" }, "newFamiliesOrganization": { - "message": "新建家庭组织" + "message": "新建家庭版组织" }, "acceptOffer": { "message": "接受邀请" @@ -7242,7 +7278,7 @@ "message": "自托管" }, "selfHostingEnterpriseOrganizationSectionCopy": { - "message": "要在您自己的服务器上设置您的组织,您需要上传您的许可证文件。要为您的自托管组织提供免费家庭方案和高级计费功能,您需要设置计费同步。" + "message": "要在您自己的服务器上设置您的组织,您需要上传您的许可证文件。要为您的自托管组织提供免费家庭版方案和高级计费功能,您需要设置计费同步。" }, "billingSyncApiKeyRotated": { "message": "令牌已轮换" @@ -7254,7 +7290,7 @@ "message": "计费同步令牌" }, "automaticBillingSyncDesc": { - "message": "自动同步将解锁家庭赞助功能,并允许您同步许可证而无需上传文件。在 Bitwarden 云服务器中进行更新后,选择「同步许可证」以应用更改。" + "message": "自动同步将解锁家庭版赞助功能,并允许您同步许可证而无需上传文件。在 Bitwarden 云服务器中进行更新后,选择「同步许可证」以应用更改。" }, "active": { "message": "已生效" @@ -9527,13 +9563,13 @@ "message": "允许所有者和管理员从管理控制台管理所有集合和项目" }, "restrictCollectionCreationDescription": { - "message": "限制为所有者和管理员可以创建集合" + "message": "限制为仅所有者和管理员可以创建集合" }, "restrictCollectionDeletionDescription": { - "message": "限制为所有者和管理员可以删除集合" + "message": "限制为仅所有者和管理员可以删除集合" }, "restrictItemDeletionDescriptionStart": { - "message": "限制为具有以下权限的成员可以删除项目:", + "message": "限制为仅具有以下权限的成员可以删除项目:", "description": "This will be used as part of a larger sentence, broken up to allow styling of the middle portion. Full sentence: 'Restrict item deletion to members with the [Manage collection] permission'" }, "restrictItemDeletionDescriptionEnd": { @@ -9820,6 +9856,14 @@ "message": "太贵了", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "正在切换到免费方案", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "正在切换到免费组织", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "免费 1 年" }, @@ -10423,7 +10467,7 @@ "message": "输入您的团队组织信息" }, "enterFamiliesOrgInfo": { - "message": "输入您的家庭组织信息" + "message": "输入您的家庭版组织信息" }, "enterEnterpriseOrgInfo": { "message": "输入您的企业组织信息" @@ -10823,7 +10867,7 @@ "message": "SSO 身份验证" }, "familiesPlanInvLimitReachedManageBilling": { - "message": "家庭组织最多拥有 $SEATCOUNT$ 位成员。要邀请更多成员,请升级到付费方案。", + "message": "家庭版组织最多拥有 $SEATCOUNT$ 位成员。要邀请更多成员,请升级到付费方案。", "placeholders": { "seatcount": { "content": "$1", @@ -10832,7 +10876,7 @@ } }, "familiesPlanInvLimitReachedNoManageBilling": { - "message": "家庭组织最多拥有 $SEATCOUNT$ 位成员。请联系您的组织所有者升级。", + "message": "家庭版组织最多拥有 $SEATCOUNT$ 位成员。要升级,请联系您的组织所有者。", "placeholders": { "seatcount": { "content": "$1", @@ -11033,7 +11077,7 @@ "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": "要在您自己的服务器上托管 Bitwarden,您需要上传许可证文件。要支持自托管组织的免费家庭版方案和高级计费功能,您需要在自托管组织中设置自动同步。" + "message": "要在您自己的服务器上托管 Bitwarden,您需要上传许可证文件。要支持自托管组织的免费版家庭版方案和高级计费功能,您需要在自托管组织中设置自动同步。" }, "selfHostingTitleProper": { "message": "自托管" @@ -11123,7 +11167,7 @@ "message": "删除成功" }, "freeFamiliesSponsorship": { - "message": "禁用免费 Bitwarden 家庭赞助" + "message": "禁用免费的 Bitwarden 家庭版赞助" }, "freeFamiliesSponsorshipPolicyDesc": { "message": "不允许成员通过此组织兑换家庭版方案。" @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden 扩展已安装!" }, - "openTheBitwardenExtension": { - "message": "打开 Bitwarden 扩展" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "Bitwarden 扩展已安装!请打开扩展登录并开始自动填充。" + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden 扩展已安装!" }, "openExtensionToAutofill": { "message": "打开扩展以登录并开始自动填充。" @@ -11687,6 +11720,14 @@ "message": "学习中心", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "如果扩展未打开,您可能需要通过工具栏上的图标 ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "帮助中心", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -11724,7 +11765,7 @@ "message": "关于此设置" }, "permitCipherDetailsDescription": { - "message": "Bitwarden 将使用已保存的登录 URI 来识别应使用哪个图标或更改密码的 URL 来改善您的体验。当您使用此服务时,不会收集或保存任何信息。" + "message": "Bitwarden 将使用已保存的登录 URI 来确定应使用的图标或更改密码的 URL,以提升您的使用体验。使用此服务时不会收集或保存任何信息。" }, "billingAddress": { "message": "计费地址" @@ -12068,7 +12109,7 @@ "message": "立即升级" }, "formWillCreateNewFamiliesOrgMessage": { - "message": "完成此表单将创建一个新的家庭组织。您可以从管理控制台升级您的免费组织。" + "message": "完成此表单将创建一个新的家庭版组织。您可以从管理控制台升级您的免费版组织。" }, "upgradeErrorMessage": { "message": "我们在处理您的升级时遇到错误。请重试。" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "更新您的加密设置" }, - "updateSettings": { - "message": "更新设置" - }, "algorithm": { "message": "算法" }, @@ -12119,7 +12157,7 @@ "message": "开始免费家庭版试用" }, "blockClaimedDomainAccountCreation": { - "message": "阻止为已声明的域名创建账户" + "message": "阻止使用已声明的域名创建账户" }, "blockClaimedDomainAccountCreationDesc": { "message": "禁止用户使用已声明域名的电子邮件地址在组织外部创建账户。" @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "确定要继续吗?" + }, + "userVerificationFailed": { + "message": "用户验证失败。" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index add3fa6a84d..95c345f74d7 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1762,7 +1762,7 @@ "message": "通知已傳送至您的裝置。" }, "notificationSentDevicePart1": { - "message": "在你的裝置或其他裝置上解鎖 Bitwarden" + "message": "在你的裝置或其他裝置上解鎖 Bitwarden " }, "accessAttemptBy": { "message": "來自 $EMAIL$ 的存取嘗試", @@ -2310,7 +2310,7 @@ "message": "匯入資料" }, "onboardingImportDataDetailsPartOne": { - "message": "如果你沒有任何資料要匯入,你可以建立一個", + "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": { @@ -3062,6 +3062,15 @@ "premiumSignUpStorage": { "message": "用於檔案附件的 1 GB 的加密檔案儲存空間。" }, + "premiumSignUpStorageV2": { + "message": "$SIZE$ encrypted storage for file attachments.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, "premiumSignUpTwoStepOptions": { "message": "專有的兩步驟登入選項,例如 YubiKey 和 Duo。" }, @@ -3124,6 +3133,15 @@ } } }, + "premiumSubscriptionEnded": { + "message": "Your Premium subscription ended" + }, + "premiumSubscriptionEndedDesc": { + "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + }, + "restartPremium": { + "message": "Restart Premium" + }, "additionalStorageGb": { "message": "額外儲存空間 (GB)" }, @@ -4608,6 +4626,24 @@ "learnMore": { "message": "深入了解" }, + "migrationsFailed": { + "message": "An error occurred updating the encryption settings." + }, + "updateEncryptionSettingsTitle": { + "message": "Update your encryption settings" + }, + "updateEncryptionSettingsDesc": { + "message": "The new recommended encryption settings will improve your account security. Enter your master password to update now." + }, + "confirmIdentityToContinue": { + "message": "Confirm your identity to continue" + }, + "enterYourMasterPassword": { + "message": "Enter your master password" + }, + "updateSettings": { + "message": "更新設定" + }, "deleteRecoverDesc": { "message": "請在下方輸入您的電子郵件地址以復原及刪除您的帳戶。" }, @@ -5810,7 +5846,7 @@ "description": "This is the policy description shown in the policy list." }, "organizationDataOwnershipDescContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "message": "所有項目將由組織擁有並儲存,啟用組織範圍的控管、可見性及報告。啟用後,每位成員將有預設集合可用於儲存項目。瞭解更多關於管理", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { @@ -9820,6 +9856,14 @@ "message": "太貴了", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "切換至免費方案", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "切換至免費組織", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "免費 1 年" }, @@ -11339,14 +11383,6 @@ "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": { @@ -11667,11 +11703,8 @@ "bitwardenExtensionInstalled": { "message": "已安裝 Bitwarden 擴充套件!" }, - "openTheBitwardenExtension": { - "message": "開啟 Bitwarden 擴充套件" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "已安裝 Bitwarden 擴充套件!開啟擴充套件以登入並開始自動填入。" + "bitwardenExtensionIsInstalled": { + "message": "已安裝 Bitwarden 擴充套件!" }, "openExtensionToAutofill": { "message": "開啟擴充套件以登入並開始自動填入。" @@ -11687,6 +11720,14 @@ "message": "學習中心", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "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 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " 在工具列上。", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "說明中心", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" @@ -12088,9 +12129,6 @@ "updateYourEncryptionSettings": { "message": "更新您的加密設定" }, - "updateSettings": { - "message": "更新設定" - }, "algorithm": { "message": "演算法" }, @@ -12119,13 +12157,13 @@ "message": "開始免費家庭試用" }, "blockClaimedDomainAccountCreation": { - "message": "Block account creation for claimed domains" + "message": "封鎖已宣告網域的帳號建立" }, "blockClaimedDomainAccountCreationDesc": { - "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + "message": "防止使用者使用屬於已宣告網域的電子郵件地址,在組織外建立帳號。" }, "blockClaimedDomainAccountCreationPrerequisite": { - "message": "A domain must be claimed before activating this policy." + "message": "在啟用此原則前,必須先宣告網域。" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" @@ -12166,5 +12204,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "您確定要繼續嗎?" + }, + "userVerificationFailed": { + "message": "使用者驗證失敗。" } } diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts index 51d35570cd2..050ec8df944 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts @@ -1063,6 +1063,7 @@ export class RiskInsightsOrchestratorService { this.logService.debug("[RiskInsightsOrchestratorService] Fetching organization ciphers"); const ciphers = await this.cipherService.getAllFromApiForOrganization( orgDetails.organizationId, + true, ); this._ciphersSubject.next(ciphers); this._hasCiphersSubject$.next(ciphers.length > 0); diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts index 08ee8bb3b1f..602becadd2f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts @@ -2,7 +2,10 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { authGuard } from "@bitwarden/angular/auth/guards"; -import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + canAccessAccessIntelligence, + canAccessSettingsTab, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { isEnterpriseOrgGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/is-enterprise-org.guard"; import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard"; import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-console/organizations/layouts/organization-layout.component"; @@ -79,7 +82,7 @@ const routes: Routes = [ }, { path: "access-intelligence", - canActivate: [organizationPermissionsGuard((org) => org.canAccessReports)], + canActivate: [organizationPermissionsGuard(canAccessAccessIntelligence)], loadChildren: () => import("../../dirt/access-intelligence/access-intelligence.module").then( (m) => m.AccessIntelligenceModule, diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/activate-autofill.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/activate-autofill.component.ts index c32eb3d935b..08fe807f669 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/activate-autofill.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/activate-autofill.component.ts @@ -24,6 +24,7 @@ export class ActivateAutofillPolicy extends BasePolicyEditDefinition { // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "activate-autofill-policy-edit", templateUrl: "activate-autofill.component.html", imports: [SharedModule], }) diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/automatic-app-login.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/automatic-app-login.component.ts index eb82dce4383..23603490d50 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/automatic-app-login.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/automatic-app-login.component.ts @@ -20,6 +20,7 @@ export class AutomaticAppLoginPolicy extends BasePolicyEditDefinition { // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "automatic-app-login-policy-edit", templateUrl: "automatic-app-login.component.html", imports: [SharedModule], }) diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.ts index 5e2925aa0bb..75c61e3e7e3 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.ts @@ -25,6 +25,7 @@ export class BlockClaimedDomainAccountCreationPolicy extends BasePolicyEditDefin } @Component({ + selector: "block-claimed-domain-account-creation-policy-edit", changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: "block-claimed-domain-account-creation.component.html", imports: [SharedModule], diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/disable-personal-vault-export.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/disable-personal-vault-export.component.ts index 17e8eb055b5..5a9b36912c9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/disable-personal-vault-export.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/disable-personal-vault-export.component.ts @@ -8,7 +8,7 @@ import { import { SharedModule } from "@bitwarden/web-vault/app/shared"; export class DisablePersonalVaultExportPolicy extends BasePolicyEditDefinition { - name = "disablePersonalVaultExport"; + name = "disableExport"; description = "disablePersonalVaultExportDescription"; type = PolicyType.DisablePersonalVaultExport; component = DisablePersonalVaultExportPolicyComponent; @@ -17,6 +17,7 @@ export class DisablePersonalVaultExportPolicy extends BasePolicyEditDefinition { // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + selector: "disable-personal-vault-export-policy-edit", templateUrl: "disable-personal-vault-export.component.html", imports: [SharedModule], }) diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-clients.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-clients.component.html index 2ab82bd837b..ce89b3c068d 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-clients.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-clients.component.html @@ -2,18 +2,20 @@ @let provider = provider$ | async; - + @if (provider?.type === ProviderUserType.ProviderAdmin) { + + } `, imports: [BadgeModule, JslibModule], diff --git a/libs/angular/src/billing/components/premium-badge/premium-badge.stories.ts b/libs/angular/src/billing/components/premium-badge/premium-badge.stories.ts index bf50d16d3c4..f6e45dbd5e1 100644 --- a/libs/angular/src/billing/components/premium-badge/premium-badge.stories.ts +++ b/libs/angular/src/billing/components/premium-badge/premium-badge.stories.ts @@ -29,7 +29,7 @@ export default { provide: I18nService, useFactory: () => { return new I18nMockService({ - premium: "Premium", + upgrade: "Upgrade", }); }, }, diff --git a/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.html b/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.html index 62ac981664a..52cd36e9356 100644 --- a/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.html +++ b/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.html @@ -20,33 +20,35 @@
    -
    +

    {{ "upgradeToPremium" | i18n }}

    -
    +

    {{ cardDetails.tagline }}

    -
    -
    - {{ - cardDetails.price.amount | currency: "$" - }} - - / {{ cardDetails.price.cadence }} - + @if (cardDetails.price) { +
    +
    + {{ + cardDetails.price.amount | currency: "$" + }} + + / {{ cardDetails.price.cadence | i18n }} + +
    -
    + } -
    +