From 6179397ba914f62d3a256754763f5f0cd5d81ab5 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 7 Aug 2024 10:59:01 -0700 Subject: [PATCH 01/59] [PM-9503] Add paperclip icon for items with attachments (#10329) --- .../vault-list-items-container.component.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html index f6e815dd461..6ac793e4d4d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html @@ -32,6 +32,11 @@ [size]="'small'" [appA11yTitle]="orgIconTooltip(cipher)" > + {{ cipher.subTitle }} From 041cd87e7e7f414c9155f1d8c95804aa841f454d Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 7 Aug 2024 12:02:33 -0700 Subject: [PATCH 02/59] [PM-9675] Browser Refresh Login - Generator dialog (#10352) * [PM-9675] Introduce CipherFormGenerator component * [PM-9675] Introduce VaultGeneratorDialog component for Browser * [PM-9675] Introduce BrowserCipherFormGeneration Service * [PM-9675] Fix aria label on popup header * [PM-9675] Cleanup html * [PM-9675] Cleanup vault generator dialog spec file --- apps/browser/src/_locales/en/messages.json | 12 + .../popup/layout/popup-header.component.html | 2 +- .../add-edit/add-edit-v2.component.ts | 3 + .../vault-generator-dialog.component.html | 25 +++ .../vault-generator-dialog.component.spec.ts | 82 +++++++ .../vault-generator-dialog.component.ts | 120 ++++++++++ .../browser-cipher-form-generation.service.ts | 45 ++++ .../cipher-form-generator.component.html | 62 ++++++ .../cipher-form-generator.component.spec.ts | 210 ++++++++++++++++++ .../cipher-form-generator.component.ts | 159 +++++++++++++ libs/vault/src/cipher-form/index.ts | 1 + 11 files changed, 720 insertions(+), 1 deletion(-) create mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html create mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts create mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts create mode 100644 apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts create mode 100644 libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html create mode 100644 libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.spec.ts create mode 100644 libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index db1f960b9b3..49d7ae0f3a0 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1803,6 +1803,18 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, diff --git a/apps/browser/src/platform/popup/layout/popup-header.component.html b/apps/browser/src/platform/popup/layout/popup-header.component.html index 82a2b715a0e..fefc7154314 100644 --- a/apps/browser/src/platform/popup/layout/popup-header.component.html +++ b/apps/browser/src/platform/popup/layout/popup-header.component.html @@ -14,7 +14,7 @@ type="button" *ngIf="showBackButton" [title]="'back' | i18n" - [ariaLabel]="'back' | i18n" + [attr.aria-label]="'back' | i18n" [bitAction]="backAction" >

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 cae324fac1f..9d42d6b6040 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 @@ -14,6 +14,7 @@ import { AsyncActionsModule, ButtonModule, SearchModule } from "@bitwarden/compo import { CipherFormConfig, CipherFormConfigService, + CipherFormGenerationService, CipherFormMode, CipherFormModule, DefaultCipherFormConfigService, @@ -27,6 +28,7 @@ import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component"; import { PopupCloseWarningService } from "../../../../../popup/services/popup-close-warning.service"; import { BrowserFido2UserInterfaceSession } from "../../../../fido2/browser-fido2-user-interface.service"; +import { BrowserCipherFormGenerationService } from "../../../services/browser-cipher-form-generation.service"; import { BrowserTotpCaptureService } from "../../../services/browser-totp-capture.service"; import { fido2PopoutSessionData$, @@ -106,6 +108,7 @@ export type AddEditQueryParams = Partial>; providers: [ { provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }, { provide: TotpCaptureService, useClass: BrowserTotpCaptureService }, + { provide: CipherFormGenerationService, useClass: BrowserCipherFormGenerationService }, ], imports: [ CommonModule, diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html new file mode 100644 index 00000000000..7652b8ab0bf --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts new file mode 100644 index 00000000000..d25dfadf5bc --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts @@ -0,0 +1,82 @@ +import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherFormGeneratorComponent } from "@bitwarden/vault"; + +import { + GeneratorDialogParams, + GeneratorDialogResult, + VaultGeneratorDialogComponent, +} from "./vault-generator-dialog.component"; + +@Component({ + selector: "vault-cipher-form-generator", + template: "", + standalone: true, +}) +class MockCipherFormGenerator { + @Input() type: "password" | "username"; + @Output() valueGenerated = new EventEmitter(); +} + +describe("VaultGeneratorDialogComponent", () => { + let component: VaultGeneratorDialogComponent; + let fixture: ComponentFixture; + let mockDialogRef: MockProxy>; + let dialogData: GeneratorDialogParams; + + beforeEach(async () => { + mockDialogRef = mock>(); + dialogData = { type: "password" }; + + await TestBed.configureTestingModule({ + imports: [VaultGeneratorDialogComponent, NoopAnimationsModule], + providers: [ + { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: DIALOG_DATA, useValue: dialogData }, + { provide: DialogRef, useValue: mockDialogRef }, + ], + }) + .overrideComponent(VaultGeneratorDialogComponent, { + remove: { imports: [CipherFormGeneratorComponent] }, + add: { imports: [MockCipherFormGenerator] }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(VaultGeneratorDialogComponent); + component = fixture.componentInstance; + }); + + it("should create", () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it("should use the appropriate text based on generator type", () => { + expect(component["title"]).toBe("passwordGenerator"); + expect(component["selectButtonText"]).toBe("useThisPassword"); + + dialogData.type = "username"; + + fixture = TestBed.createComponent(VaultGeneratorDialogComponent); + component = fixture.componentInstance; + + expect(component["title"]).toBe("usernameGenerator"); + expect(component["selectButtonText"]).toBe("useThisUsername"); + }); + + it("should close the dialog with the generated value when the user selects it", () => { + component["generatedValue"] = "generated-value"; + + fixture.nativeElement.querySelector("button[data-testid='select-button']").click(); + + expect(mockDialogRef.close).toHaveBeenCalledWith({ + action: "selected", + generatedValue: "generated-value", + }); + }); +}); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts new file mode 100644 index 00000000000..657d126081f --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts @@ -0,0 +1,120 @@ +import { animate, group, style, transition, trigger } from "@angular/animations"; +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Overlay } from "@angular/cdk/overlay"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ButtonModule, DialogService } from "@bitwarden/components"; +import { CipherFormGeneratorComponent } from "@bitwarden/vault"; + +import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component"; + +export interface GeneratorDialogParams { + type: "password" | "username"; +} + +export interface GeneratorDialogResult { + action: GeneratorDialogAction; + generatedValue?: string; +} + +export enum GeneratorDialogAction { + Selected = "selected", + Canceled = "canceled", +} + +const slideIn = trigger("slideIn", [ + transition(":enter", [ + style({ opacity: 0, transform: "translateY(100vh)" }), + group([ + animate("0.15s linear", style({ opacity: 1 })), + animate("0.3s ease-out", style({ transform: "none" })), + ]), + ]), +]); + +@Component({ + selector: "app-vault-generator-dialog", + templateUrl: "./vault-generator-dialog.component.html", + standalone: true, + imports: [ + PopupPageComponent, + PopupHeaderComponent, + PopupFooterComponent, + CommonModule, + CipherFormGeneratorComponent, + ButtonModule, + ], + animations: [slideIn], +}) +export class VaultGeneratorDialogComponent { + protected title = this.i18nService.t(this.isPassword ? "passwordGenerator" : "usernameGenerator"); + protected selectButtonText = this.i18nService.t( + this.isPassword ? "useThisPassword" : "useThisUsername", + ); + + /** + * Whether the dialog is generating a password/passphrase. If false, it is generating a username. + * @protected + */ + protected get isPassword() { + return this.params.type === "password"; + } + + /** + * The currently generated value. + * @protected + */ + protected generatedValue: string = ""; + + constructor( + @Inject(DIALOG_DATA) protected params: GeneratorDialogParams, + private dialogRef: DialogRef, + private i18nService: I18nService, + ) {} + + /** + * Close the dialog without selecting a value. + */ + protected close = () => { + this.dialogRef.close({ action: GeneratorDialogAction.Canceled }); + }; + + /** + * Close the dialog and select the currently generated value. + */ + protected selectValue = () => { + this.dialogRef.close({ + action: GeneratorDialogAction.Selected, + generatedValue: this.generatedValue, + }); + }; + + onValueGenerated(value: string) { + this.generatedValue = value; + } + + /** + * Opens the vault generator dialog in a full screen dialog. + */ + static open( + dialogService: DialogService, + overlay: Overlay, + config: DialogConfig, + ) { + const position = overlay.position().global(); + + return dialogService.open( + VaultGeneratorDialogComponent, + { + ...config, + positionStrategy: position, + height: "100vh", + width: "100vw", + }, + ); + } +} diff --git a/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts b/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts new file mode 100644 index 00000000000..b9e8641431f --- /dev/null +++ b/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts @@ -0,0 +1,45 @@ +import { Overlay } from "@angular/cdk/overlay"; +import { inject, Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { DialogService } from "@bitwarden/components"; +import { CipherFormGenerationService } from "@bitwarden/vault"; + +import { VaultGeneratorDialogComponent } from "../components/vault-v2/vault-generator-dialog/vault-generator-dialog.component"; + +@Injectable() +export class BrowserCipherFormGenerationService implements CipherFormGenerationService { + private dialogService = inject(DialogService); + private overlay = inject(Overlay); + + async generatePassword(): Promise { + const dialogRef = VaultGeneratorDialogComponent.open(this.dialogService, this.overlay, { + data: { type: "password" }, + }); + + const result = await firstValueFrom(dialogRef.closed); + + if (result == null || result.action === "canceled") { + return null; + } + + return result.generatedValue; + } + + async generateUsername(): Promise { + const dialogRef = VaultGeneratorDialogComponent.open(this.dialogService, this.overlay, { + data: { type: "username" }, + }); + + const result = await firstValueFrom(dialogRef.closed); + + if (result == null || result.action === "canceled") { + return null; + } + + return result.generatedValue; + } + async generateInitialPassword(): Promise { + return ""; + } +} diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html new file mode 100644 index 00000000000..0a375d5ae51 --- /dev/null +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html @@ -0,0 +1,62 @@ + + + + + {{ "password" | i18n }} + + + {{ "passphrase" | i18n }} + + + + + + + + + + + + + + + + + + + + + + + +

{{ "options" | i18n }}

+
+ + Placeholder: Replace with Generator Options Component(s) when available + +
diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.spec.ts b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.spec.ts new file mode 100644 index 00000000000..5b65c6da24d --- /dev/null +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.spec.ts @@ -0,0 +1,210 @@ +import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + PasswordGenerationServiceAbstraction, + PasswordGeneratorOptions, + UsernameGenerationServiceAbstraction, + UsernameGeneratorOptions, +} from "@bitwarden/generator-legacy"; +import { CipherFormGeneratorComponent } from "@bitwarden/vault"; + +describe("CipherFormGeneratorComponent", () => { + let component: CipherFormGeneratorComponent; + let fixture: ComponentFixture; + + let mockLegacyPasswordGenerationService: MockProxy; + let mockLegacyUsernameGenerationService: MockProxy; + let mockPlatformUtilsService: MockProxy; + + let passwordOptions$: BehaviorSubject; + let usernameOptions$: BehaviorSubject; + + beforeEach(async () => { + passwordOptions$ = new BehaviorSubject([ + { + type: "password", + }, + ] as [PasswordGeneratorOptions]); + usernameOptions$ = new BehaviorSubject([ + { + type: "word", + }, + ] as [UsernameGeneratorOptions]); + + mockPlatformUtilsService = mock(); + + mockLegacyPasswordGenerationService = mock(); + mockLegacyPasswordGenerationService.getOptions$.mockReturnValue(passwordOptions$); + + mockLegacyUsernameGenerationService = mock(); + mockLegacyUsernameGenerationService.getOptions$.mockReturnValue(usernameOptions$); + + await TestBed.configureTestingModule({ + imports: [CipherFormGeneratorComponent], + providers: [ + { provide: I18nService, useValue: { t: (key: string) => key } }, + { + provide: PasswordGenerationServiceAbstraction, + useValue: mockLegacyPasswordGenerationService, + }, + { + provide: UsernameGenerationServiceAbstraction, + useValue: mockLegacyUsernameGenerationService, + }, + { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(CipherFormGeneratorComponent); + component = fixture.componentInstance; + }); + + it("should create", () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it("should use the appropriate text based on generator type", () => { + component.type = "password"; + component.ngOnChanges(); + expect(component["regenerateButtonTitle"]).toBe("regeneratePassword"); + + component.type = "username"; + component.ngOnChanges(); + expect(component["regenerateButtonTitle"]).toBe("regenerateUsername"); + }); + + it("should emit regenerate$ when user clicks the regenerate button", fakeAsync(() => { + const regenerateSpy = jest.spyOn(component["regenerate$"], "next"); + + fixture.nativeElement.querySelector("button[data-testid='regenerate-button']").click(); + + expect(regenerateSpy).toHaveBeenCalled(); + })); + + it("should emit valueGenerated whenever a new value is generated", fakeAsync(() => { + const valueGeneratedSpy = jest.spyOn(component.valueGenerated, "emit"); + + mockLegacyPasswordGenerationService.generatePassword.mockResolvedValue("generated-password"); + component.type = "password"; + + component.ngOnChanges(); + tick(); + + expect(valueGeneratedSpy).toHaveBeenCalledWith("generated-password"); + })); + + describe("password generation", () => { + beforeEach(() => { + component.type = "password"; + }); + + it("should update the generated value when the password options change", fakeAsync(() => { + mockLegacyPasswordGenerationService.generatePassword + .mockResolvedValueOnce("first-password") + .mockResolvedValueOnce("second-password"); + + component.ngOnChanges(); + tick(); + + expect(component["generatedValue"]).toBe("first-password"); + + passwordOptions$.next([{ type: "password" }]); + tick(); + + expect(component["generatedValue"]).toBe("second-password"); + expect(mockLegacyPasswordGenerationService.generatePassword).toHaveBeenCalledTimes(2); + })); + + it("should show password type toggle when the generator type is password", () => { + fixture.detectChanges(); + + expect(fixture.nativeElement.querySelector("bit-toggle-group")).toBeTruthy(); + }); + + it("should save password options when the password type is updated", async () => { + mockLegacyPasswordGenerationService.generatePassword.mockResolvedValue("generated-password"); + + await component["updatePasswordType"]("passphrase"); + + expect(mockLegacyPasswordGenerationService.saveOptions).toHaveBeenCalledWith({ + type: "passphrase", + }); + }); + + it("should update the password history when a new password is generated", fakeAsync(() => { + mockLegacyPasswordGenerationService.generatePassword.mockResolvedValue("new-password"); + + component.ngOnChanges(); + tick(); + + expect(mockLegacyPasswordGenerationService.generatePassword).toHaveBeenCalledTimes(1); + expect(mockLegacyPasswordGenerationService.addHistory).toHaveBeenCalledWith("new-password"); + expect(component["generatedValue"]).toBe("new-password"); + })); + + it("should regenerate the password when regenerate$ emits", fakeAsync(() => { + mockLegacyPasswordGenerationService.generatePassword + .mockResolvedValueOnce("first-password") + .mockResolvedValueOnce("second-password"); + + component.ngOnChanges(); + tick(); + + expect(component["generatedValue"]).toBe("first-password"); + + component["regenerate$"].next(); + tick(); + + expect(component["generatedValue"]).toBe("second-password"); + })); + }); + + describe("username generation", () => { + beforeEach(() => { + component.type = "username"; + }); + + it("should update the generated value when the username options change", fakeAsync(() => { + mockLegacyUsernameGenerationService.generateUsername + .mockResolvedValueOnce("first-username") + .mockResolvedValueOnce("second-username"); + + component.ngOnChanges(); + tick(); + + expect(component["generatedValue"]).toBe("first-username"); + + usernameOptions$.next([{ type: "word" }]); + tick(); + + expect(component["generatedValue"]).toBe("second-username"); + })); + + it("should regenerate the username when regenerate$ emits", fakeAsync(() => { + mockLegacyUsernameGenerationService.generateUsername + .mockResolvedValueOnce("first-username") + .mockResolvedValueOnce("second-username"); + + component.ngOnChanges(); + tick(); + + expect(component["generatedValue"]).toBe("first-username"); + + component["regenerate$"].next(); + tick(); + + expect(component["generatedValue"]).toBe("second-username"); + })); + + it("should not show password type toggle when the generator type is username", () => { + fixture.detectChanges(); + + expect(fixture.nativeElement.querySelector("bit-toggle-group")).toBeNull(); + }); + }); +}); diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts new file mode 100644 index 00000000000..d7c8e5e93cb --- /dev/null +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts @@ -0,0 +1,159 @@ +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, EventEmitter, Input, Output } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { firstValueFrom, map, startWith, Subject, Subscription, switchMap, tap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { + CardComponent, + ColorPasswordModule, + IconButtonModule, + ItemModule, + SectionComponent, + SectionHeaderComponent, + ToggleGroupModule, + TypographyModule, +} from "@bitwarden/components"; +import { GeneratorType } from "@bitwarden/generator-core"; +import { + PasswordGenerationServiceAbstraction, + UsernameGenerationServiceAbstraction, +} from "@bitwarden/generator-legacy"; + +/** + * Renders a password or username generator UI and emits the most recently generated value. + * Used by the cipher form to be shown in a dialog/modal when generating cipher passwords/usernames. + */ +@Component({ + selector: "vault-cipher-form-generator", + templateUrl: "./cipher-form-generator.component.html", + standalone: true, + imports: [ + CommonModule, + CardComponent, + SectionComponent, + ToggleGroupModule, + JslibModule, + ItemModule, + ColorPasswordModule, + IconButtonModule, + SectionHeaderComponent, + TypographyModule, + ], +}) +export class CipherFormGeneratorComponent { + /** + * The type of generator form to show. + */ + @Input({ required: true }) + type: "password" | "username"; + + /** + * Emits an event when a new value is generated. + */ + @Output() + valueGenerated = new EventEmitter(); + + protected get isPassword() { + return this.type === "password"; + } + + protected regenerateButtonTitle: string; + protected regenerate$ = new Subject(); + /** + * The currently generated value displayed to the user. + * @protected + */ + protected generatedValue: string = ""; + + /** + * The current password generation options. + * @private + */ + private passwordOptions$ = this.legacyPasswordGenerationService.getOptions$(); + + /** + * The current username generation options. + * @private + */ + private usernameOptions$ = this.legacyUsernameGenerationService.getOptions$(); + + /** + * The current password type specified by the password generation options. + * @protected + */ + protected passwordType$ = this.passwordOptions$.pipe(map(([options]) => options.type)); + + /** + * Tracks the regenerate$ subscription + * @private + */ + private subscription: Subscription | null; + + constructor( + private i18nService: I18nService, + private legacyPasswordGenerationService: PasswordGenerationServiceAbstraction, + private legacyUsernameGenerationService: UsernameGenerationServiceAbstraction, + private destroyRef: DestroyRef, + ) {} + + ngOnChanges() { + this.regenerateButtonTitle = this.i18nService.t( + this.isPassword ? "regeneratePassword" : "regenerateUsername", + ); + + // If we have a previous subscription, clear it + if (this.subscription) { + this.subscription.unsubscribe(); + this.subscription = null; + } + + if (this.isPassword) { + this.setupPasswordGeneration(); + } else { + this.setupUsernameGeneration(); + } + } + + private setupPasswordGeneration() { + this.subscription = this.regenerate$ + .pipe( + startWith(null), + switchMap(() => this.passwordOptions$), + switchMap(([options]) => this.legacyPasswordGenerationService.generatePassword(options)), + tap(async (password) => { + await this.legacyPasswordGenerationService.addHistory(password); + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe((password) => { + this.generatedValue = password; + this.valueGenerated.emit(password); + }); + } + + private setupUsernameGeneration() { + this.subscription = this.regenerate$ + .pipe( + startWith(null), + switchMap(() => this.usernameOptions$), + switchMap((options) => this.legacyUsernameGenerationService.generateUsername(options)), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe((username) => { + this.generatedValue = username; + this.valueGenerated.emit(username); + }); + } + + /** + * Switch the password generation type and save the options (generating a new password automatically). + * @param value The new password generation type. + */ + protected updatePasswordType = async (value: GeneratorType) => { + const [currentOptions] = await firstValueFrom(this.passwordOptions$); + currentOptions.type = value; + await this.legacyPasswordGenerationService.saveOptions(currentOptions); + }; +} diff --git a/libs/vault/src/cipher-form/index.ts b/libs/vault/src/cipher-form/index.ts index 1d275029df1..8cb779a8ec3 100644 --- a/libs/vault/src/cipher-form/index.ts +++ b/libs/vault/src/cipher-form/index.ts @@ -8,3 +8,4 @@ export { export { TotpCaptureService } from "./abstractions/totp-capture.service"; export { CipherFormGenerationService } from "./abstractions/cipher-form-generation.service"; export { DefaultCipherFormConfigService } from "./services/default-cipher-form-config.service"; +export { CipherFormGeneratorComponent } from "./components/cipher-generator/cipher-form-generator.component"; From 0b6701d3f801162c8ca0b1740ca250404191318b Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Wed, 7 Aug 2024 15:24:19 -0400 Subject: [PATCH 03/59] fix notification settings spacing and labels (#10433) --- .../settings/notifications.component.html | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.html b/apps/browser/src/autofill/popup/settings/notifications.component.html index 9032e742da1..26036acdd0a 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.html +++ b/apps/browser/src/autofill/popup/settings/notifications.component.html @@ -11,39 +11,40 @@

{{ "vaultSaveOptionsTitle" | i18n }}

-
+ - -
-
+ {{ "enableUsePasskeys" | i18n }} + + - -
-
+ {{ + "enableAddLoginNotification" | i18n + }} + + - -
+ }} +
From 600c8de129aa7cfae5216a59ce78a98d1c9b20db Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 7 Aug 2024 12:57:05 -0700 Subject: [PATCH 04/59] [PM-10100] Remove auto password generation (#10355) * [PM-10100] Remove initial password generation on new Login ciphers * [PM-10100] Update password help text * [PM-10100] Fix linter --- apps/browser/src/_locales/en/messages.json | 11 ++++++++++ .../browser-cipher-form-generation.service.ts | 3 --- apps/web/src/locales/en/messages.json | 20 +++++++++++++++++++ .../cipher-form-generation.service.ts | 6 ------ .../cipher-form-generator.component.ts | 4 ++-- .../login-details-section.component.html | 10 ++++++++++ .../login-details-section.component.spec.ts | 8 -------- .../login-details-section.component.ts | 8 +++++++- .../default-cipher-form-generation.service.ts | 4 ---- 9 files changed, 50 insertions(+), 24 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 49d7ae0f3a0..69600b5da7c 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1815,6 +1815,17 @@ "useThisUsername": { "message": "Use this username" }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, diff --git a/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts b/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts index b9e8641431f..70993482046 100644 --- a/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts +++ b/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts @@ -39,7 +39,4 @@ export class BrowserCipherFormGenerationService implements CipherFormGenerationS return result.generatedValue; } - async generateInitialPassword(): Promise { - return ""; - } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index be48d1b301d..5f35c1c3e59 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6050,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, diff --git a/libs/vault/src/cipher-form/abstractions/cipher-form-generation.service.ts b/libs/vault/src/cipher-form/abstractions/cipher-form-generation.service.ts index 6ddd4473445..beb295103fb 100644 --- a/libs/vault/src/cipher-form/abstractions/cipher-form-generation.service.ts +++ b/libs/vault/src/cipher-form/abstractions/cipher-form-generation.service.ts @@ -11,10 +11,4 @@ export abstract class CipherFormGenerationService { * Generates a random username. Called when the user clicks the "Generate Username" button in the UI. */ abstract generateUsername(): Promise; - - /** - * Generates an initial password for a new cipher. This should not involve any user interaction as it will - * be used to pre-fill the password field in the UI for new Login ciphers. - */ - abstract generateInitialPassword(): Promise; } diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts index d7c8e5e93cb..2d24194d290 100644 --- a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component, DestroyRef, EventEmitter, Input, Output } from "@angular/core"; +import { Component, DestroyRef, EventEmitter, Input, OnChanges, Output } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { firstValueFrom, map, startWith, Subject, Subscription, switchMap, tap } from "rxjs"; @@ -42,7 +42,7 @@ import { TypographyModule, ], }) -export class CipherFormGeneratorComponent { +export class CipherFormGeneratorComponent implements OnChanges { /** * The type of generator form to show. */ diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html index db4d8db37e9..0ba2338afa5 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html @@ -23,6 +23,16 @@ {{ "password" | i18n }} + + + {{ "securePasswordGenerated" | i18n }} + + + {{ "useGeneratorHelpTextPartOne" | i18n }} + + {{ "useGeneratorHelpTextPartTwo" | i18n }} + + - + {{ "expiration" | i18n }} - + {{ "securityCode" | i18n }}
  • {{ "lastEdited" | i18n }}: {{ cipher.revisionDate | date: "medium" }}

    -

    +

    {{ "dateCreated" | i18n }}: {{ cipher.creationDate | date: "medium" }}

    @@ -20,7 +26,7 @@ {{ cipher.passwordRevisionDisplayDate | date: "medium" }}

    Date: Wed, 7 Aug 2024 17:09:53 -0400 Subject: [PATCH 06/59] PM-10486 hide empty cardholder field (#10435) --- .../cipher-view/card-details/card-details-view.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.html b/libs/vault/src/cipher-view/card-details/card-details-view.component.html index b83200cadae..c446ba4f319 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.html +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.html @@ -3,7 +3,7 @@

    {{ setSectionTitle }}

    - + {{ "cardholderName" | i18n }} Date: Wed, 7 Aug 2024 16:25:19 -0500 Subject: [PATCH 07/59] [PM-6471] Implement Inline Menu Autofill for Passkeys (#10127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PM-4661: Add passkey.username as item.username (#9756) * Add incoming passkey.username as item.username * Driveby fix, was sending wrong username * added username to new-cipher too * Guarded the if-block * Update apps/browser/src/vault/popup/components/vault/add-edit.component.ts Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * Fixed broken test * fixed username on existing ciphers --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * PM-4878: Add passkey information to items when signing in (#9835) * Added username to subtitle * Added subName to cipher * Moved subName to component * Update apps/browser/src/vault/popup/components/fido2/fido2-cipher-row.component.ts Co-authored-by: SmithThe4th * Fixed double code and added comment * Added changeDetection: ChangeDetectionStrategy.OnPush as per review --------- Co-authored-by: SmithThe4th * [AC-2791] Members page - finish component library refactors (#9727) * Replace PlatformUtilsService with ToastService * Remove unneeded templates * Implement table filtering function * Move member-only methods from base class to subclass * Move utility functions inside new MemberTableDataSource * Rename PeopleComponent to MembersComponent * [deps] Platform: Update angular-cli monorepo to v16.2.14 (#9380) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [PM-8789] Move desktop_native into subcrate (#9682) * Move desktop_native into subcrate * Add publish = false to crates * [PM-6394] remove policy evaluator cache (#9807) * [PM-9364] Copy for Aggregate auto-scaling invoices for Teams and Enterprise customers (#9875) * Change the seat adjustment message * Move changes from en_GB file to en file * revert changes in en_GB file * Add feature flag to the change * use user verification as a part of key rotation (#9722) * Add the ability for custom validation logic to be injected into `UserVerificationDialogComponent` (#8770) * Introduce `verificationType` * Update template to use `verificationType` * Implement a path for `verificationType = 'custom'` * Delete `clientSideOnlyVerification` * Update `EnrollMasterPasswordResetComponent` to include a server-side hash check * Better describe the custom scenerio through comments * Add an example of the custom verficiation scenerio * Move execution of verification function into try/catch * Migrate existing uses of `clientSideOnlyVerification` * Use generic type option instead of casting * Change "given" to "determined" in a comment * Restructure the `org-redirect` guard to be Angular 17+ compliant (#9552) * Document the `org-redirect` guard in code * Make assertions about the way the `org-redirect` guard should behave * Restructure the `org-redirect` guard to be Angular 17+ compliant * Convert data parameter to function parameter * Convert a data parameter to a function parameter that was missed * Pass redirect function to default organization route * don't initialize kdf with validators, do it on first set (#9754) * add testids for attachments (#9892) * Bug fix - error toast in 2fa (#9623) * Bug fix - error toast in 2fa * Bug fix - Yubikey code obscured * 2FA error fix * [PM-5189] Fixing an issue where we can potentially show the inline menu incorrectly after a user switches account * [PM-5189] Fixing an issue where we can potentially show the inline menu incorrectly after a user switches account * [PM-5189] Fixing an issue where we can potentially show the inline menu incorrectly after a user switches account * [PM-2858] Fixing icon color * [PM-2858] Adding subtitle for identity inline menu list items * [PM-2858] Fixing jest tests * [PM-2858] Working through implementation of conditional identity fill logic on inline menu * [PM-2858] Working through implementation of conditional identity fill logic on inline menu * [PM-2858] Working through implementation of conditional identity fill logic on inline menu * [PM-2858] Working through implementation of conditional identity fill logic on inline menu * [PM-2858] Working through implementation of conditional identity fill logic on inline menu * [PM-2858] Working through implementation of conditional identity fill logic on inline menu * [PM-2858] Working through implementation of conditional identity fill logic on inline menu * [PM-2858] Working through implementation of conditional identity fill logic on inline menu * [PM-2858] Working through identity field qualification for the inline menu * [PM-2858] Working through identity field qualification for the inline menu * [PM-2858] Working through identity field qualification for the inline menu * [PM-2858] Working through identity field qualification for the inline menu * [PM-2858] Working through identity field qualification for the inline menu * [PM-2858] Working through identity field qualification for the inline menu * [PM-2858] Scaffolding add new identity logic * [PM-2858] Implementing add new identity * [PM-2858] Implementing add new identity * [PM-2858] Scaffolding add new identity logic * [PM-2858] Scaffolding add new identity logic * [PM-2858] Scaffolding add new identity logic * [PM-2857] Fixing an issue with how we parse the last digits for credit card aria description * [PM-2857] Setting up logic to ensrue we use a set email address as a fallback for a username * [PM-2857] Fixing an issue with how we parse the last digits for credit card aria description * [PM-2858] Reverting forced email address in inline menu identity autofill * Restructure the `is-paid-org` guard to be Angular 17+ compliant (#9598) * Document that `is-paid-org` guard in code * Remove unused `MessagingService` dependency * Make assertions about the way the is-paid-org guard should behave * Restructure the `is-paid-org` guard to be Angular 17+ compliant * Random commit to get the build job moving * Undo previous commit * Bumped client version(s) (#9895) * [PM-9344] Clarify accepted user state (#9861) * Prefer `Needs confirmation` to `Accepted` display status This emphasizes that action is still required to complete setup. * Remove unused message * Bumped client version(s) (#9906) * Revert "Bumped client version(s) (#9906)" (#9907) This reverts commit 78c28297938eda53e7731fdf9f63d7baa7068d0d. * fix duo subscriptions and org vs individual duo setup (#9859) * [PM-5024] Migrate tax-info component (#9872) * Changes for the tax info migration * Return for invalid formgroup * Restructure the `org-permissions` guard to be Angular 17+ compliant (#9631) * Document the `org-permissions` guard in code * Restructure the `org-permissions` guard to be Angular 17+ compliant * Update the `org-permissions` guard to use `ToastService` * Simplify callback function sigantures * Remove unused test object * Fix updated route from merge * Restructure the `provider-permissions` guard to be Angular 17+ compliant (#9609) * Document the `provider-permissions` guard in code * Restructure the `provider-permissions` guard to be Angular 17+ compliant * [deps] Platform: Update @types/argon2-browser to v1.18.4 (#8180) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Bumped client version(s) (#9914) * [PM-7162] Cipher Form - Item Details (#9758) * [PM-7162] Fix weird angular error regarding disabled component bit-select * [PM-7162] Introduce CipherFormConfigService and related types * [PM-7162] Introduce CipherFormService * [PM-7162] Introduce the Item Details section component and the CipherFormContainer interface * [PM-7162] Introduce the CipherForm component * [PM-7162] Add strongly typed QueryParams to the add-edit-v2.component * [PM-7162] Export CipherForm from Vault Lib * [PM-7162] Use the CipherForm in Browser AddEditV2 * [PM-7162] Introduce CipherForm storybook * [PM-7162] Remove VaultPopupListFilterService dependency from NewItemDropDownV2 component * [PM-7162] Add support for content projection of attachment button * [PM-7162] Fix typo * [PM-7162] Cipher form service cleanup * [PM-7162] Move readonly collection notice to bit-hint * [PM-7162] Refactor CipherFormConfig type to enforce required properties with Typescript * [PM-7162] Fix storybook after config changes * [PM-7162] Use new add-edit component for clone route * [deps]: Update @yao-pkg/pkg to ^5.12.0 (#9820) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Autosync the updated translations (#9922) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#9923) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#9924) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * [AC-2830] Unable to create a free organization (#9917) * Resolve the issue free org creation * Check that the taxForm is touched * [PM-7162] Fix broken getter when original cipher is null (#9927) * [PM-8525] Edit Card (#9901) * initial add of card details section * add card number * update card brand when the card number changes * add year and month fields * add security code field * hide number and security code by default * add `id` for all form fields * update select options to match existing options * make year input numerical * only display card details for card ciphers * use style to set input height * handle numerical values for year * update heading when a brand is available * remove unused ref * use cardview types for the form * fix numerical input type * disable card details when in partial-edit mode * remove hardcoded height * update types for formBuilder * [PM-9440] Fix: handle undefined value in migration 66 (#9908) * fix: handle undefined value in migration 66 * fix: the if-statement was typo * Rename "encryptionAlgorithm" to "hashAlgorithmForEncryption" for clarity (#9891) * [PM-7972] Account switching integration with "remember email" functionality (#9750) * add account switching logic to login email service * enforce boolean and fix desktop account switcher order * [PM-9442] Add tests for undefined state values and proper emulation of ElectronStorageService in tests (#9910) * fix: handle undefined value in migration 66 * fix: the if-statement was typo * feat: duplicate error behavior in fake storage service * feat: fix all migrations that were setting undefined values * feat: add test for disabled fingrint in migration 66 * fix: default single user state saving undefined value to state * revert: awaiting floating promise gonna fix this in a separate PR * Revert "feat: fix all migrations that were setting undefined values" This reverts commit 034713256cee9a8e164295c88157fe33d8372c81. * feat: automatically convert save to remove * Revert "fix: default single user state saving undefined value to state" This reverts commit 6c36da6ba52f6886d0de2b502b3aaff7f122c3a7. * [AC-2805] Consolidated Billing UI Updates (#9893) * Add empty state for invoices * Make cards on create client dialog tabbable * Add space in $ / month per member * Mute text, remove (Monthly) and right align menu on clients table * Made used seats account for all users and fixed column sort for used/remaining * Resize pricing cards * Rename assignedSeats to occupiedSeats * [PM-9460][deps] Tools: Update electron to v31 (#9921) * [deps] Tools: Update electron to v31 * Bump version in electron-builder --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith * [AC-1452] Restrict access to 'Organization Info' and 'Two-Step Login' settings pages with a permission check (#9483) * Guard Organization Info route - Owners only * Guard TwoFactor route - Owners only and Organization must be able to use 2FA * Update guards to use function syntax --------- Co-authored-by: Addison Beck * [PM-9437] Use CollectionAccessDetailsResponse type now that is always the type returned from the API (#9951) * Add required env variables to desktop native build script (#9869) * [AC-2676] Remove paging logic from GroupsComponent (#9705) * remove infinite scroll, use virtual scroll instead * use TableDataSource for search * allow sorting by name * replacing PlatformUtilsService.showToast with ToastService * misc FIXMEs * [PM-9441] Catch and log exceptions during migration (#9905) * feat: catch and log exceptions during migration * Revert "feat: catch and log exceptions during migration" This reverts commit d68733b7e58120298974b350e496bb3e0c9af0d2. * feat: use log service to log migration errors * Autosync the updated translations (#9972) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#9973) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Updated codeowners for new design system team (#9913) * Updated codeowners for new design system team. * Moved Angular and Bootstrap dependencies * Moved additional dependencies. * Updated ownership Co-authored-by: Will Martin --------- Co-authored-by: Will Martin * [SM-1016] Fix new access token dialog (#9918) * swap to bit-dialog title & subtitle * remove dialogRef.disableClose & use toastService * Add shared two-factor-options component (#9767) * Communicate the upcoming client vault privacy changes to MSPs (#9994) * Add a banner notification to the provider portal * Feature flag the banner * Move banner copy to messages.json * Allow for dismissing the banner * Auth/PM-7321 - Registration with Email Verification - Registration Finish Component Implementation (#9653) * PM-7321 - Temp add input password * PM-7321 - update input password based on latest PR changes to test. * PM-7321 - Progress on testing input password component + RegistrationFinishComponent checks * PM-7321 - more progress on registration finish. * PM-7321 - Wire up RegistrationFinishRequest model + AccountApiService abstraction + implementation changes for new method. * PM-7321 - WIP Registration Finish - wiring up request building and API call on submit. * PM-7321 - WIP registratin finish * PM-7321 - WIP on creating registration-finish service + web override to add org invite handling * PM-7321 - (1) Move web-registration-finish svc to web (2) Wire up exports (3) wire up RegistrationFinishComponent to call registration finish service * PM-7321 - Get CLI building * PM-7321 - Move all finish registration service and content to registration-finish feature folder. * PM-7321 - Fix RegistrationFinishService config * PM-7321 - RegistrationFinishComponent- handlePasswordFormSubmit - error handling WIP * PM-7321 - InputPasswordComp - Update to accept masterPasswordPolicyOptions as input instead of retrieving it as parent components in different scenarios will need to retrieve the policies differently (e.g., orgInvite token in registration vs direct call via org id post SSO on set password) * PM-7321 - Registration Finish - Add web specific logic for retrieving master password policies and passing them into the input password component. * PM-7321 - Registration Start - Send email via query param to registration finish page so it can create masterKey * PM-7321 - InputPassword comp - (1) Add loading input (2) Add email validation to submit logic. * PM-7321 - Registration Finish - Add submitting state and pass into input password so that the rest of the registration process keeps the child form disabled. * PM-7321 - Registration Finish - use validation service for error handling. * PM-7321 - All register routes must be dynamic and change if the feature flag changes. * PM-7321 - Test registration finish services. * PM-7321 - RegisterRouteService - Add comment documenting why the service exists. * PM-7321 - Add missing input password translations to browser & desktop * PM-7321 - WebRegistrationFinishSvc - apply PR feedback * [deps] Autofill: Update rimraf to v5.0.8 (#10008) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [PM-9318] Fix username on protonpass import (#9889) * Fix username field used for ProtonPass import ProtonPass has changed their export format and userName is not itemEmail * Import additional field itemUsername --------- Co-authored-by: Daniel James Smith * [PM-8943] Update QRious script initialization in Authenticator two-factor provider (#9926) * create onload() for qrious as well as error messaging if QR code cannot be displayed * button and message updates and formpromise removal * load QR script async * rename and reorder methods * Delete Unused Bits of StateService (#9858) * Delete Unused Bits of StateService * Fix Tests * remove getBgService for auth request service (#10020) * [PM-2858] Fixing an issue found when the first or last names of an identity are not filled * [PM-2858] Fixing an issue found where keyboard navigation can potentially close the inline menu * [PM-2858] Fixing jest tests within inline menu list * [PM-2858] Fixing jest tests within inline menu list * [PM-2858] Setting up login items to be presented when an account creation form is shown to the user * [PM-2858] Refactoring implementation used for creating the inline menu cipher data * [PM-2858] Refactoring implementation used for creating the inline menu cipher data * [PM-2858] Refactoring implementation used for creating the inline menu cipher data * [PM-2858] Refactoring implementation * [PM-2858] Refactoring implementation * [PM-2858] Refactoring implementation * [PM-2858] Refactoring implementation * [PM-2858] Changing how we populate login ciphers within create account * [PM-2858] Adding documentation * [PM-2858] Working through jest tests for the OverlayBackground * [PM-2858] Working through jest tests for the OverlayBackground * [PM-2858] Working through jest tests for the AutofillInlineMenuList class * [PM-2858] Adding documentation to inline menu list methods * [PM-2857] Fixing a jest test * [PM-2858] Fixing jest tests within inline menu list * [PM-2858] Addressing jest tests within AutofillOverlayContentService * [PM-2858] Addressing jest tests within AutofillOverlayContentService * [PM-2858] Addressing jest tests within InlineMenuFieldQualificationService * [PM-9267] Implement feature flag for inline menu re-architecture (#9845) * [PM-9267] Implement Feature Flag for Inline Menu Re-Architecture * [PM-9267] Incorporating legacy OverlayBackground implementation * [PM-9267] Incorporating legacy overlay content scripts * [PM-9267] Incorporating legacy overlay content scripts * [PM-9267] Incorporating legacy overlay content scripts * [PM-9267] Incorporating legacy overlay content scripts * [PM-9267] Finalizing feature flag implementation * [PM-9267] Finalizing feature flag implementation * [PM-9267] Finalizing feature flag implementation * [PM-9267] Finalizing feature flag implementation * [PM-9267] Finalizing feature flag implementation * [PM-9267] Finalizing feature flag implementation * [PM-9267] Finalizing feature flag implementation * [PM-9267] Finalizing feature flag implementation * [PM-9267] Adjusting naming convention for page files * [PM-9267] Adjusting naming convention for page files * [PM-5189] Fixing an issue where we can potentially show the inline menu incorrectly after a user switches account * PM-4950 - Fix hint and verify delete components that had the data in the wrong place (#9877) * PM-4661: Add passkey.username as item.username (#9756) * Add incoming passkey.username as item.username * Driveby fix, was sending wrong username * added username to new-cipher too * Guarded the if-block * Update apps/browser/src/vault/popup/components/vault/add-edit.component.ts Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * Fixed broken test * fixed username on existing ciphers --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * PM-4878: Add passkey information to items when signing in (#9835) * Added username to subtitle * Added subName to cipher * Moved subName to component * Update apps/browser/src/vault/popup/components/fido2/fido2-cipher-row.component.ts Co-authored-by: SmithThe4th * Fixed double code and added comment * Added changeDetection: ChangeDetectionStrategy.OnPush as per review --------- Co-authored-by: SmithThe4th * [AC-2791] Members page - finish component library refactors (#9727) * Replace PlatformUtilsService with ToastService * Remove unneeded templates * Implement table filtering function * Move member-only methods from base class to subclass * Move utility functions inside new MemberTableDataSource * Rename PeopleComponent to MembersComponent * [deps] Platform: Update angular-cli monorepo to v16.2.14 (#9380) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [PM-8789] Move desktop_native into subcrate (#9682) * Move desktop_native into subcrate * Add publish = false to crates * [PM-6394] remove policy evaluator cache (#9807) * [PM-9364] Copy for Aggregate auto-scaling invoices for Teams and Enterprise customers (#9875) * Change the seat adjustment message * Move changes from en_GB file to en file * revert changes in en_GB file * Add feature flag to the change * use user verification as a part of key rotation (#9722) * Add the ability for custom validation logic to be injected into `UserVerificationDialogComponent` (#8770) * Introduce `verificationType` * Update template to use `verificationType` * Implement a path for `verificationType = 'custom'` * Delete `clientSideOnlyVerification` * Update `EnrollMasterPasswordResetComponent` to include a server-side hash check * Better describe the custom scenerio through comments * Add an example of the custom verficiation scenerio * Move execution of verification function into try/catch * Migrate existing uses of `clientSideOnlyVerification` * Use generic type option instead of casting * Change "given" to "determined" in a comment * Restructure the `org-redirect` guard to be Angular 17+ compliant (#9552) * Document the `org-redirect` guard in code * Make assertions about the way the `org-redirect` guard should behave * Restructure the `org-redirect` guard to be Angular 17+ compliant * Convert data parameter to function parameter * Convert a data parameter to a function parameter that was missed * Pass redirect function to default organization route * don't initialize kdf with validators, do it on first set (#9754) * add testids for attachments (#9892) * Bug fix - error toast in 2fa (#9623) * Bug fix - error toast in 2fa * Bug fix - Yubikey code obscured * 2FA error fix * Restructure the `is-paid-org` guard to be Angular 17+ compliant (#9598) * Document that `is-paid-org` guard in code * Remove unused `MessagingService` dependency * Make assertions about the way the is-paid-org guard should behave * Restructure the `is-paid-org` guard to be Angular 17+ compliant * Random commit to get the build job moving * Undo previous commit * Bumped client version(s) (#9895) * [PM-9344] Clarify accepted user state (#9861) * Prefer `Needs confirmation` to `Accepted` display status This emphasizes that action is still required to complete setup. * Remove unused message * Bumped client version(s) (#9906) * Revert "Bumped client version(s) (#9906)" (#9907) This reverts commit 78c28297938eda53e7731fdf9f63d7baa7068d0d. * fix duo subscriptions and org vs individual duo setup (#9859) * [PM-5024] Migrate tax-info component (#9872) * Changes for the tax info migration * Return for invalid formgroup * Restructure the `org-permissions` guard to be Angular 17+ compliant (#9631) * Document the `org-permissions` guard in code * Restructure the `org-permissions` guard to be Angular 17+ compliant * Update the `org-permissions` guard to use `ToastService` * Simplify callback function sigantures * Remove unused test object * Fix updated route from merge * Restructure the `provider-permissions` guard to be Angular 17+ compliant (#9609) * Document the `provider-permissions` guard in code * Restructure the `provider-permissions` guard to be Angular 17+ compliant * [deps] Platform: Update @types/argon2-browser to v1.18.4 (#8180) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Bumped client version(s) (#9914) * [PM-7162] Cipher Form - Item Details (#9758) * [PM-7162] Fix weird angular error regarding disabled component bit-select * [PM-7162] Introduce CipherFormConfigService and related types * [PM-7162] Introduce CipherFormService * [PM-7162] Introduce the Item Details section component and the CipherFormContainer interface * [PM-7162] Introduce the CipherForm component * [PM-7162] Add strongly typed QueryParams to the add-edit-v2.component * [PM-7162] Export CipherForm from Vault Lib * [PM-7162] Use the CipherForm in Browser AddEditV2 * [PM-7162] Introduce CipherForm storybook * [PM-7162] Remove VaultPopupListFilterService dependency from NewItemDropDownV2 component * [PM-7162] Add support for content projection of attachment button * [PM-7162] Fix typo * [PM-7162] Cipher form service cleanup * [PM-7162] Move readonly collection notice to bit-hint * [PM-7162] Refactor CipherFormConfig type to enforce required properties with Typescript * [PM-7162] Fix storybook after config changes * [PM-7162] Use new add-edit component for clone route * [deps]: Update @yao-pkg/pkg to ^5.12.0 (#9820) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Autosync the updated translations (#9922) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#9923) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#9924) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * [AC-2830] Unable to create a free organization (#9917) * Resolve the issue free org creation * Check that the taxForm is touched * [PM-7162] Fix broken getter when original cipher is null (#9927) * [PM-8525] Edit Card (#9901) * initial add of card details section * add card number * update card brand when the card number changes * add year and month fields * add security code field * hide number and security code by default * add `id` for all form fields * update select options to match existing options * make year input numerical * only display card details for card ciphers * use style to set input height * handle numerical values for year * update heading when a brand is available * remove unused ref * use cardview types for the form * fix numerical input type * disable card details when in partial-edit mode * remove hardcoded height * update types for formBuilder * [PM-9440] Fix: handle undefined value in migration 66 (#9908) * fix: handle undefined value in migration 66 * fix: the if-statement was typo * Rename "encryptionAlgorithm" to "hashAlgorithmForEncryption" for clarity (#9891) * [PM-7972] Account switching integration with "remember email" functionality (#9750) * add account switching logic to login email service * enforce boolean and fix desktop account switcher order * [PM-9442] Add tests for undefined state values and proper emulation of ElectronStorageService in tests (#9910) * fix: handle undefined value in migration 66 * fix: the if-statement was typo * feat: duplicate error behavior in fake storage service * feat: fix all migrations that were setting undefined values * feat: add test for disabled fingrint in migration 66 * fix: default single user state saving undefined value to state * revert: awaiting floating promise gonna fix this in a separate PR * Revert "feat: fix all migrations that were setting undefined values" This reverts commit 034713256cee9a8e164295c88157fe33d8372c81. * feat: automatically convert save to remove * Revert "fix: default single user state saving undefined value to state" This reverts commit 6c36da6ba52f6886d0de2b502b3aaff7f122c3a7. * [AC-2805] Consolidated Billing UI Updates (#9893) * Add empty state for invoices * Make cards on create client dialog tabbable * Add space in $ / month per member * Mute text, remove (Monthly) and right align menu on clients table * Made used seats account for all users and fixed column sort for used/remaining * Resize pricing cards * Rename assignedSeats to occupiedSeats * [PM-9460][deps] Tools: Update electron to v31 (#9921) * [deps] Tools: Update electron to v31 * Bump version in electron-builder --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith * [AC-1452] Restrict access to 'Organization Info' and 'Two-Step Login' settings pages with a permission check (#9483) * Guard Organization Info route - Owners only * Guard TwoFactor route - Owners only and Organization must be able to use 2FA * Update guards to use function syntax --------- Co-authored-by: Addison Beck * [PM-9437] Use CollectionAccessDetailsResponse type now that is always the type returned from the API (#9951) * Add required env variables to desktop native build script (#9869) * [AC-2676] Remove paging logic from GroupsComponent (#9705) * remove infinite scroll, use virtual scroll instead * use TableDataSource for search * allow sorting by name * replacing PlatformUtilsService.showToast with ToastService * misc FIXMEs * [PM-9441] Catch and log exceptions during migration (#9905) * feat: catch and log exceptions during migration * Revert "feat: catch and log exceptions during migration" This reverts commit d68733b7e58120298974b350e496bb3e0c9af0d2. * feat: use log service to log migration errors * Autosync the updated translations (#9972) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#9973) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Updated codeowners for new design system team (#9913) * Updated codeowners for new design system team. * Moved Angular and Bootstrap dependencies * Moved additional dependencies. * Updated ownership Co-authored-by: Will Martin --------- Co-authored-by: Will Martin * [SM-1016] Fix new access token dialog (#9918) * swap to bit-dialog title & subtitle * remove dialogRef.disableClose & use toastService * Add shared two-factor-options component (#9767) * Communicate the upcoming client vault privacy changes to MSPs (#9994) * Add a banner notification to the provider portal * Feature flag the banner * Move banner copy to messages.json * Allow for dismissing the banner * Auth/PM-7321 - Registration with Email Verification - Registration Finish Component Implementation (#9653) * PM-7321 - Temp add input password * PM-7321 - update input password based on latest PR changes to test. * PM-7321 - Progress on testing input password component + RegistrationFinishComponent checks * PM-7321 - more progress on registration finish. * PM-7321 - Wire up RegistrationFinishRequest model + AccountApiService abstraction + implementation changes for new method. * PM-7321 - WIP Registration Finish - wiring up request building and API call on submit. * PM-7321 - WIP registratin finish * PM-7321 - WIP on creating registration-finish service + web override to add org invite handling * PM-7321 - (1) Move web-registration-finish svc to web (2) Wire up exports (3) wire up RegistrationFinishComponent to call registration finish service * PM-7321 - Get CLI building * PM-7321 - Move all finish registration service and content to registration-finish feature folder. * PM-7321 - Fix RegistrationFinishService config * PM-7321 - RegistrationFinishComponent- handlePasswordFormSubmit - error handling WIP * PM-7321 - InputPasswordComp - Update to accept masterPasswordPolicyOptions as input instead of retrieving it as parent components in different scenarios will need to retrieve the policies differently (e.g., orgInvite token in registration vs direct call via org id post SSO on set password) * PM-7321 - Registration Finish - Add web specific logic for retrieving master password policies and passing them into the input password component. * PM-7321 - Registration Start - Send email via query param to registration finish page so it can create masterKey * PM-7321 - InputPassword comp - (1) Add loading input (2) Add email validation to submit logic. * PM-7321 - Registration Finish - Add submitting state and pass into input password so that the rest of the registration process keeps the child form disabled. * PM-7321 - Registration Finish - use validation service for error handling. * PM-7321 - All register routes must be dynamic and change if the feature flag changes. * PM-7321 - Test registration finish services. * PM-7321 - RegisterRouteService - Add comment documenting why the service exists. * PM-7321 - Add missing input password translations to browser & desktop * PM-7321 - WebRegistrationFinishSvc - apply PR feedback * [deps] Autofill: Update rimraf to v5.0.8 (#10008) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [PM-9318] Fix username on protonpass import (#9889) * Fix username field used for ProtonPass import ProtonPass has changed their export format and userName is not itemEmail * Import additional field itemUsername --------- Co-authored-by: Daniel James Smith * [PM-8943] Update QRious script initialization in Authenticator two-factor provider (#9926) * create onload() for qrious as well as error messaging if QR code cannot be displayed * button and message updates and formpromise removal * load QR script async * rename and reorder methods * Delete Unused Bits of StateService (#9858) * Delete Unused Bits of StateService * Fix Tests * remove getBgService for auth request service (#10020) --------- Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Anders Åberg Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: SmithThe4th Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García Co-authored-by: ✨ Audrey ✨ Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Co-authored-by: Jake Fink Co-authored-by: Addison Beck Co-authored-by: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Co-authored-by: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> Co-authored-by: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Co-authored-by: Matt Gibson Co-authored-by: Opeyemi Co-authored-by: Shane Melton Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Andreas Coroiu Co-authored-by: Bernd Schoolmann Co-authored-by: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Co-authored-by: Daniel James Smith Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> Co-authored-by: Addison Beck Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com> Co-authored-by: Will Martin Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Co-authored-by: Ike <137194738+ike-kottlowski@users.noreply.github.com> * [PM-1223] Passkeys Mediated Conditional UI * [PM-1223] Passkeys Mediated Conditional UI * [PM-1223] Finished migrating existing POC solution * [PM-1223] Setting up passkeys to appear before login ciphers * [PM-6471] Implement on-page autofill menu for passkeys * [PM-6471] Working through visual presentation of passkeys within inline menu * [PM-6471] Implementing visual and behavior differences between inline menu passkeys and regular login elements * [PM-6471] Adding a11y content within inline menu list elements * [PM-6471] Fixing issue with SVG path fill on new passkey icon * [PM-6471] Working through scroll event triggers * [PM-6471] Refactoring onScroll implementation * [PM-6471] Adding a methodology for allow users to cancel a conditional UI workflow, but still be able to re-trigger the passkey fill * [PM-2858] Fixing an issue found where password fields addedin new account forms do not properly pull their value into the add cipher flow * [PM-6471] Implementing a methodology for exlusively displaying credentials that are authorized within the fido2 request * [PM-6471] Adding the webAuthn autocomplete value to the field qualification service * [PM-6471] Fixing issues within OverlayBackground jest tests * [PM-6471] Fixing issues within AutofillInlineMenuList jest tests * [PM-6471] Adding jest tests for the OverlayBackground * [PM-6471] Adding jest tests for the OverlayBackground * [PM-6471] Adding jest tests for the OverlayBackground * [PM-6471] Re-adding an optimization to the inline menu list * [PM-6471] Refactoring implementation, optimizing scroll behavior within the inline menu, and adding a method for ensureing passkeys get set as the most recently used cipher when fill occurs * [PM-6471] Refactoring implementation, optimizing scroll behavior within the inline menu, and adding a method for ensureing passkeys get set as the most recently used cipher when fill occurs * [PM-6471] Refactoring how we identify a cipher as a passkey cipher * [PM-6471] Reworking implementation to loop mediated conditional request until a valid value is returned rather than re-calling navigator API * [PM-6471] Adding jest tests for the inline menu list logic * [PM-6471] Adding jest tests for the inline menu list logic * [PM-6471] Adding jest tests for conditional mediated webauthn request * [PM-6471] Removing unnecessary comment * [PM-6471] Adding jest tests for incorporated Fido2ClientService changes * [PM-6471] Adding jest tests to the Fido2AuthenticatorService changes * [PM-6471] Adding jest tests for the Fido2ActiveRequestManager class * [PM-6471] Fixing issue with master password reprompt not triggering for cipher when user verification is discouraged * [PM-2858] Adjusting scrollbar stylings * [PM-2858] Adjusting how we handle instantiating the feature flag guarded overlay background and how we handle instantiating identities and card ciphers in the inline menu * [PM-2858] Adjusting how we handle instantiating the feature flag guarded overlay background and how we handle instantiating identities and card ciphers in the inline menu * [PM-2858] Adjusting how we handle instantiating the feature flag guarded overlay background and how we handle instantiating identities and card ciphers in the inline menu * [PM-2858] Incorporating some changes that ensure the inline menu list fades in as expected * [PM-2858] Incorporating some changes that ensure the inline menu list fades in as expected * [PM-2858] Incorporating some changes that ensure the inline menu list fades in as expected * [PM-2858] Adjusting how we inject translations for a couple of aria label elements * [PM-6471] Merging changes from identities branch * [PM-6471] Fixing an issue relating to a current tab reference * [PM-6471] Fixing an issue relating to a current tab reference * [PM-6471] Optimizing conditional logic for OverlayBackground.showCipherAsPasskey * [PM-6471] Refactoring implementation * [PM-6471] Refactoring implementation * [PM-6471] Adding coverage for cases where a mediated conditional request is aborted * [PM-6471] Fixing typechecking error --------- Co-authored-by: Anders Åberg Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: SmithThe4th Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García Co-authored-by: ✨ Audrey ✨ Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Co-authored-by: Jake Fink Co-authored-by: Addison Beck Co-authored-by: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Co-authored-by: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> Co-authored-by: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Co-authored-by: Matt Gibson Co-authored-by: Opeyemi Co-authored-by: Shane Melton Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Andreas Coroiu Co-authored-by: Bernd Schoolmann Co-authored-by: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Co-authored-by: Daniel James Smith Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> Co-authored-by: Addison Beck Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com> Co-authored-by: Will Martin Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Co-authored-by: Ike <137194738+ike-kottlowski@users.noreply.github.com> --- apps/browser/src/_locales/en/messages.json | 12 + .../abstractions/overlay.background.ts | 19 +- .../background/overlay.background.spec.ts | 238 +++++++-- .../autofill/background/overlay.background.ts | 158 +++++- .../fido2/content/fido2-page-script.ts | 44 +- ...do2-page-script.webauthn-supported.spec.ts | 11 + .../abstractions/autofill-inline-menu-list.ts | 1 + .../autofill-inline-menu-list.spec.ts.snap | 504 +++++++++++++++++- .../list/autofill-inline-menu-list.spec.ts | 180 ++++++- .../pages/list/autofill-inline-menu-list.ts | 302 ++++++++++- .../overlay/inline-menu/pages/list/list.scss | 52 ++ ...inline-menu-field-qualification.service.ts | 2 + .../src/autofill/spec/autofill-mocks.ts | 5 +- apps/browser/src/autofill/utils/svg-icons.ts | 15 +- .../browser/src/background/main.background.ts | 4 + .../browser/src/vault/fido2/webauthn-utils.ts | 1 + libs/common/src/autofill/constants/index.ts | 2 + ...ido2-active-request-manager.abstraction.ts | 21 + ...fido2-authenticator.service.abstraction.ts | 13 + .../fido2/fido2-client.service.abstraction.ts | 9 + .../fido2-active-request-manager.spec.ts | 89 ++++ .../fido2/fido2-active-request-manager.ts | 109 ++++ .../fido2/fido2-authenticator.service.spec.ts | 16 + .../fido2/fido2-authenticator.service.ts | 31 +- .../fido2/fido2-client.service.spec.ts | 86 ++- .../services/fido2/fido2-client.service.ts | 84 ++- 26 files changed, 1889 insertions(+), 119 deletions(-) create mode 100644 libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts create mode 100644 libs/common/src/platform/services/fido2/fido2-active-request-manager.spec.ts create mode 100644 libs/common/src/platform/services/fido2/fido2-active-request-manager.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 69600b5da7c..9a69d5f1085 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3926,6 +3926,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys. Used in the inline menu list." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords. Used in the inline menu list." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 8122f5c4ed9..950f3b8e275 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -1,5 +1,6 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import AutofillPageDetails from "../../models/autofill-page-details"; import { PageDetail } from "../../services/abstractions/autofill.service"; @@ -132,6 +133,7 @@ export type OverlayPortMessage = { direction?: string; inlineMenuCipherId?: string; addNewCipherType?: CipherType; + usePasskey?: boolean; }; export type InlineMenuCipherData = { @@ -142,7 +144,13 @@ export type InlineMenuCipherData = { favorite: boolean; icon: WebsiteIconData; accountCreationFieldType?: string; - login?: { username: string }; + login?: { + username: string; + passkey: { + rpName: string; + userName: string; + } | null; + }; card?: string; identity?: { fullName: string; @@ -150,6 +158,15 @@ export type InlineMenuCipherData = { }; }; +export type BuildCipherDataParams = { + inlineMenuCipherId: string; + cipher: CipherView; + showFavicons?: boolean; + showInlineMenuAccountCreation?: boolean; + hasPasskey?: boolean; + identityData?: { fullName: string; username?: string }; +}; + export type BackgroundMessageParam = { message: OverlayBackgroundExtensionMessage; }; diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index fe118868628..e29cc8331a2 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -17,6 +17,7 @@ import { EnvironmentService, Region, } from "@bitwarden/common/platform/abstractions/environment.service"; +import { Fido2ClientService } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; @@ -32,6 +33,7 @@ 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 { Fido2CredentialView } from "@bitwarden/common/vault/models/view/fido2-credential.view"; import { BrowserApi } from "../../platform/browser/browser-api"; import { BrowserPlatformUtilsService } from "../../platform/services/platform-utils/browser-platform-utils.service"; @@ -85,6 +87,8 @@ describe("OverlayBackground", () => { let autofillSettingsService: MockProxy; let i18nService: MockProxy; let platformUtilsService: MockProxy; + let availableAutofillCredentialsMock$: BehaviorSubject; + let fido2ClientService: MockProxy; let selectedThemeMock$: BehaviorSubject; let themeStateService: MockProxy; let overlayBackground: OverlayBackground; @@ -151,6 +155,10 @@ describe("OverlayBackground", () => { autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$; i18nService = mock(); platformUtilsService = mock(); + availableAutofillCredentialsMock$ = new BehaviorSubject([]); + fido2ClientService = mock({ + availableAutofillCredentials$: (_tabId) => availableAutofillCredentialsMock$, + }); selectedThemeMock$ = new BehaviorSubject(ThemeType.Light); themeStateService = mock(); themeStateService.selectedTheme$ = selectedThemeMock$; @@ -164,6 +172,7 @@ describe("OverlayBackground", () => { autofillSettingsService, i18nService, platformUtilsService, + fido2ClientService, themeStateService, ); portKeyForTabSpy = overlayBackground["portKeyForTab"]; @@ -699,28 +708,28 @@ describe("OverlayBackground", () => { describe("updating the overlay ciphers", () => { const url = "https://jest-testing-website.com"; const tab = createChromeTabMock({ url }); - const cipher1 = mock({ + const loginCipher1 = mock({ id: "id-1", localData: { lastUsedDate: 222 }, name: "name-1", type: CipherType.Login, login: { username: "username-1", uri: url }, }); - const cipher2 = mock({ + const cardCipher = mock({ id: "id-2", localData: { lastUsedDate: 222 }, name: "name-2", type: CipherType.Card, card: { subTitle: "subtitle-2" }, }); - const cipher3 = mock({ + const loginCipher2 = mock({ id: "id-3", localData: { lastUsedDate: 222 }, name: "name-3", type: CipherType.Login, login: { username: "username-3", uri: url }, }); - const cipher4 = mock({ + const identityCipher = mock({ id: "id-4", localData: { lastUsedDate: 222 }, name: "name-4", @@ -732,6 +741,23 @@ describe("OverlayBackground", () => { email: "email@example.com", }, }); + const passkeyCipher = mock({ + id: "id-5", + localData: { lastUsedDate: 222 }, + name: "name-5", + type: CipherType.Login, + login: { + username: "username-5", + uri: url, + fido2Credentials: [ + mock({ + credentialId: "credential-id", + rpName: "credential-name", + userName: "credential-username", + }), + ], + }, + }); beforeEach(async () => { activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); @@ -764,7 +790,7 @@ describe("OverlayBackground", () => { it("closes the inline menu on the focused field's tab if current tab is different", async () => { getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]); + cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, cardCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); const previousTab = mock({ id: 15 }); overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ tabId: 15 }); @@ -781,7 +807,7 @@ describe("OverlayBackground", () => { it("queries all cipher types, sorts them by last used, and formats them for usage in the overlay", async () => { getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]); + cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, cardCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); await overlayBackground.updateOverlayCiphers(); @@ -794,8 +820,8 @@ describe("OverlayBackground", () => { expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled(); expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual( new Map([ - ["inline-menu-cipher-0", cipher2], - ["inline-menu-cipher-1", cipher1], + ["inline-menu-cipher-0", cardCipher], + ["inline-menu-cipher-1", loginCipher1], ]), ); }); @@ -803,7 +829,7 @@ describe("OverlayBackground", () => { it("queries only login ciphers when not updating all cipher types", async () => { overlayBackground["cardAndIdentityCiphers"] = new Set([]); getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher3, cipher1]); + cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher2, loginCipher1]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); await overlayBackground.updateOverlayCiphers(false); @@ -813,15 +839,15 @@ describe("OverlayBackground", () => { expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled(); expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual( new Map([ - ["inline-menu-cipher-0", cipher1], - ["inline-menu-cipher-1", cipher3], + ["inline-menu-cipher-0", loginCipher1], + ["inline-menu-cipher-1", loginCipher2], ]), ); }); it("queries all cipher types when the card and identity ciphers set is not built when only updating login ciphers", async () => { getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]); + cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, cardCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); await overlayBackground.updateOverlayCiphers(false); @@ -834,15 +860,15 @@ describe("OverlayBackground", () => { expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled(); expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual( new Map([ - ["inline-menu-cipher-0", cipher2], - ["inline-menu-cipher-1", cipher1], + ["inline-menu-cipher-0", cardCipher], + ["inline-menu-cipher-1", loginCipher1], ]), ); }); it("posts an `updateOverlayListCiphers` message to the overlay list port, and send a `updateAutofillInlineMenuListCiphers` message to the tab indicating that the list of ciphers is populated", async () => { overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ tabId: tab.id }); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1]); + cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); @@ -851,10 +877,11 @@ describe("OverlayBackground", () => { expect(listPortSpy.postMessage).toHaveBeenCalledWith({ command: "updateAutofillInlineMenuListCiphers", showInlineMenuAccountCreation: false, + showPasskeysLabels: false, ciphers: [ { accountCreationFieldType: undefined, - favorite: cipher1.favorite, + favorite: loginCipher1.favorite, icon: { fallbackImage: "images/bwi-globe.png", icon: "bwi-globe", @@ -864,9 +891,10 @@ describe("OverlayBackground", () => { id: "inline-menu-cipher-0", login: { username: "username-1", + passkey: null, }, name: "name-1", - reprompt: cipher1.reprompt, + reprompt: loginCipher1.reprompt, type: CipherType.Login, }, ], @@ -878,7 +906,7 @@ describe("OverlayBackground", () => { tabId: tab.id, filledByCipherType: CipherType.Card, }); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher2]); + cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, cardCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); @@ -887,10 +915,11 @@ describe("OverlayBackground", () => { expect(listPortSpy.postMessage).toHaveBeenCalledWith({ command: "updateAutofillInlineMenuListCiphers", showInlineMenuAccountCreation: false, + showPasskeysLabels: false, ciphers: [ { accountCreationFieldType: undefined, - favorite: cipher2.favorite, + favorite: cardCipher.favorite, icon: { fallbackImage: "", icon: "bwi-credit-card", @@ -898,9 +927,9 @@ describe("OverlayBackground", () => { imageEnabled: true, }, id: "inline-menu-cipher-0", - card: cipher2.card.subTitle, - name: cipher2.name, - reprompt: cipher2.reprompt, + card: cardCipher.card.subTitle, + name: cardCipher.name, + reprompt: cardCipher.reprompt, type: CipherType.Card, }, ], @@ -914,7 +943,7 @@ describe("OverlayBackground", () => { accountCreationFieldType: "text", showInlineMenuAccountCreation: true, }); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher4, cipher2]); + cipherService.getAllDecryptedForUrl.mockResolvedValue([identityCipher, cardCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); @@ -923,10 +952,11 @@ describe("OverlayBackground", () => { expect(listPortSpy.postMessage).toHaveBeenCalledWith({ command: "updateAutofillInlineMenuListCiphers", showInlineMenuAccountCreation: true, + showPasskeysLabels: false, ciphers: [ { accountCreationFieldType: "text", - favorite: cipher4.favorite, + favorite: identityCipher.favorite, icon: { fallbackImage: "", icon: "bwi-id-card", @@ -934,12 +964,12 @@ describe("OverlayBackground", () => { imageEnabled: true, }, id: "inline-menu-cipher-1", - name: cipher4.name, - reprompt: cipher4.reprompt, + name: identityCipher.name, + reprompt: identityCipher.reprompt, type: CipherType.Identity, identity: { - fullName: `${cipher4.identity.firstName} ${cipher4.identity.lastName}`, - username: cipher4.identity.username, + fullName: `${identityCipher.identity.firstName} ${identityCipher.identity.lastName}`, + username: identityCipher.identity.username, }, }, ], @@ -952,7 +982,7 @@ describe("OverlayBackground", () => { accountCreationFieldType: "text", showInlineMenuAccountCreation: true, }); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher1, cipher4]); + cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, identityCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); @@ -961,10 +991,11 @@ describe("OverlayBackground", () => { expect(listPortSpy.postMessage).toHaveBeenCalledWith({ command: "updateAutofillInlineMenuListCiphers", showInlineMenuAccountCreation: true, + showPasskeysLabels: false, ciphers: [ { accountCreationFieldType: "text", - favorite: cipher4.favorite, + favorite: identityCipher.favorite, icon: { fallbackImage: "", icon: "bwi-id-card", @@ -972,17 +1003,17 @@ describe("OverlayBackground", () => { imageEnabled: true, }, id: "inline-menu-cipher-0", - name: cipher4.name, - reprompt: cipher4.reprompt, + name: identityCipher.name, + reprompt: identityCipher.reprompt, type: CipherType.Identity, identity: { - fullName: `${cipher4.identity.firstName} ${cipher4.identity.lastName}`, - username: cipher4.identity.username, + fullName: `${identityCipher.identity.firstName} ${identityCipher.identity.lastName}`, + username: identityCipher.identity.username, }, }, { accountCreationFieldType: "text", - favorite: cipher1.favorite, + favorite: loginCipher1.favorite, icon: { fallbackImage: "images/bwi-globe.png", icon: "bwi-globe", @@ -991,10 +1022,11 @@ describe("OverlayBackground", () => { }, id: "inline-menu-cipher-1", login: { - username: cipher1.login.username, + username: loginCipher1.login.username, + passkey: null, }, - name: cipher1.name, - reprompt: cipher1.reprompt, + name: loginCipher1.name, + reprompt: loginCipher1.reprompt, type: CipherType.Login, }, ], @@ -1018,7 +1050,7 @@ describe("OverlayBackground", () => { }, }); cipherService.getAllDecryptedForUrl.mockResolvedValue([ - cipher4, + identityCipher, identityCipherWithoutUsername, ]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); @@ -1029,10 +1061,11 @@ describe("OverlayBackground", () => { expect(listPortSpy.postMessage).toHaveBeenCalledWith({ command: "updateAutofillInlineMenuListCiphers", showInlineMenuAccountCreation: true, + showPasskeysLabels: false, ciphers: [ { accountCreationFieldType: "email", - favorite: cipher4.favorite, + favorite: identityCipher.favorite, icon: { fallbackImage: "", icon: "bwi-id-card", @@ -1040,12 +1073,12 @@ describe("OverlayBackground", () => { imageEnabled: true, }, id: "inline-menu-cipher-1", - name: cipher4.name, - reprompt: cipher4.reprompt, + name: identityCipher.name, + reprompt: identityCipher.reprompt, type: CipherType.Identity, identity: { - fullName: `${cipher4.identity.firstName} ${cipher4.identity.lastName}`, - username: cipher4.identity.email, + fullName: `${identityCipher.identity.firstName} ${identityCipher.identity.lastName}`, + username: identityCipher.identity.email, }, }, ], @@ -1058,7 +1091,7 @@ describe("OverlayBackground", () => { accountCreationFieldType: "password", showInlineMenuAccountCreation: true, }); - cipherService.getAllDecryptedForUrl.mockResolvedValue([cipher4]); + cipherService.getAllDecryptedForUrl.mockResolvedValue([identityCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); @@ -1067,10 +1100,89 @@ describe("OverlayBackground", () => { expect(listPortSpy.postMessage).toHaveBeenCalledWith({ command: "updateAutofillInlineMenuListCiphers", showInlineMenuAccountCreation: true, + showPasskeysLabels: false, ciphers: [], }); }); }); + + it("adds available passkey ciphers to the inline menu", async () => { + availableAutofillCredentialsMock$.next(passkeyCipher.login.fido2Credentials); + overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ + tabId: tab.id, + filledByCipherType: CipherType.Login, + }); + cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, passkeyCipher]); + cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); + getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); + + await overlayBackground.updateOverlayCiphers(); + + expect(listPortSpy.postMessage).toHaveBeenCalledWith({ + command: "updateAutofillInlineMenuListCiphers", + ciphers: [ + { + id: "inline-menu-cipher-0", + name: passkeyCipher.name, + type: CipherType.Login, + reprompt: passkeyCipher.reprompt, + favorite: passkeyCipher.favorite, + icon: { + fallbackImage: "images/bwi-globe.png", + icon: "bwi-globe", + image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", + imageEnabled: true, + }, + accountCreationFieldType: undefined, + login: { + username: passkeyCipher.login.username, + passkey: { + rpName: passkeyCipher.login.fido2Credentials[0].rpName, + userName: passkeyCipher.login.fido2Credentials[0].userName, + }, + }, + }, + { + id: "inline-menu-cipher-0", + name: passkeyCipher.name, + type: CipherType.Login, + reprompt: passkeyCipher.reprompt, + favorite: passkeyCipher.favorite, + icon: { + fallbackImage: "images/bwi-globe.png", + icon: "bwi-globe", + image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", + imageEnabled: true, + }, + accountCreationFieldType: undefined, + login: { + username: passkeyCipher.login.username, + passkey: null, + }, + }, + { + id: "inline-menu-cipher-1", + name: loginCipher1.name, + type: CipherType.Login, + reprompt: loginCipher1.reprompt, + favorite: loginCipher1.favorite, + icon: { + fallbackImage: "images/bwi-globe.png", + icon: "bwi-globe", + image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", + imageEnabled: true, + }, + accountCreationFieldType: undefined, + login: { + username: loginCipher1.login.username, + passkey: null, + }, + }, + ], + showInlineMenuAccountCreation: false, + showPasskeysLabels: true, + }); + }); }); describe("extension message handlers", () => { @@ -1562,6 +1674,7 @@ describe("OverlayBackground", () => { command: "updateAutofillInlineMenuListCiphers", ciphers: [], showInlineMenuAccountCreation: true, + showPasskeysLabels: false, }); }); @@ -2660,6 +2773,41 @@ describe("OverlayBackground", () => { expect(copyToClipboardSpy).toHaveBeenCalledWith("totp-code"); }); + + it("triggers passkey authentication through mediated conditional UI", async () => { + const fido2Credential = mock({ credentialId: "credential-id" }); + const cipher1 = mock({ + id: "inline-menu-cipher-1", + login: { + username: "username1", + password: "password1", + fido2Credentials: [fido2Credential], + }, + }); + overlayBackground["inlineMenuCiphers"] = new Map([["inline-menu-cipher-1", cipher1]]); + const pageDetailsForTab = { + frameId: sender.frameId, + tab: sender.tab, + details: pageDetails, + }; + overlayBackground["pageDetailsForTab"][sender.tab.id] = new Map([ + [sender.frameId, pageDetailsForTab], + ]); + autofillService.isPasswordRepromptRequired.mockResolvedValue(false); + + sendPortMessage(listMessageConnectorSpy, { + command: "fillAutofillInlineMenuCipher", + inlineMenuCipherId: "inline-menu-cipher-1", + usePasskey: true, + portKey, + }); + await flushPromises(); + + expect(fido2ClientService.autofillCredential).toHaveBeenCalledWith( + sender.tab.id, + fido2Credential.credentialId, + ); + }); }); describe("addNewVaultItem message handler", () => { diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 8c4dac56d50..3bb80b09b2e 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -1,5 +1,12 @@ -import { firstValueFrom, merge, Subject, throttleTime } from "rxjs"; -import { debounceTime, switchMap } from "rxjs/operators"; +import { + firstValueFrom, + merge, + ReplaySubject, + Subject, + throttleTime, + switchMap, + debounceTime, +} from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -11,6 +18,7 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { Fido2ClientService } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -21,6 +29,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { buildCipherIcon } from "@bitwarden/common/vault/icon/build-cipher-icon"; import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { Fido2CredentialView } from "@bitwarden/common/vault/models/view/fido2-credential.view"; import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; @@ -41,6 +50,7 @@ import { generateRandomChars } from "../utils"; import { LockedVaultPendingNotificationsData } from "./abstractions/notification.background"; import { + BuildCipherDataParams, CloseInlineMenuMessage, CurrentAddNewItemData, FocusedFieldData, @@ -66,6 +76,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private readonly openUnlockPopout = openUnlockPopout; private readonly openViewVaultItemPopout = openViewVaultItemPopout; private readonly openAddEditVaultItemPopout = openAddEditVaultItemPopout; + private readonly storeInlineMenuFido2CredentialsSubject = new ReplaySubject(1); private pageDetailsForTab: PageDetailsForTab = {}; private subFrameOffsetsForTab: SubFrameOffsetsForTab = {}; private portKeyForTab: Record = {}; @@ -73,6 +84,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private inlineMenuButtonPort: chrome.runtime.Port; private inlineMenuListPort: chrome.runtime.Port; private inlineMenuCiphers: Map = new Map(); + private inlineMenuFido2Credentials: Set = new Set(); private inlineMenuPageTranslations: Record; private inlineMenuPosition: InlineMenuPosition = {}; private cardAndIdentityCiphers: Set | null = null; @@ -91,6 +103,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private isFieldCurrentlyFilling: boolean = false; private isInlineMenuButtonVisible: boolean = false; private isInlineMenuListVisible: boolean = false; + private showPasskeysLabelsWithinInlineMenu: boolean = false; private iconsServerUrl: string; private readonly extensionMessageHandlers: OverlayBackgroundExtensionMessageHandlers = { autofillOverlayElementClosed: ({ message, sender }) => @@ -159,6 +172,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private autofillSettingsService: AutofillSettingsServiceAbstraction, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, + private fido2ClientService: Fido2ClientService, private themeStateService: ThemeStateService, ) { this.initOverlayEventObservables(); @@ -178,6 +192,9 @@ export class OverlayBackground implements OverlayBackgroundInterface { * Initializes event observables that handle events which affect the overlay's behavior. */ private initOverlayEventObservables() { + this.storeInlineMenuFido2CredentialsSubject + .pipe(switchMap((tabId) => this.fido2ClientService.availableAutofillCredentials$(tabId))) + .subscribe((credentials) => this.storeInlineMenuFido2Credentials(credentials)); this.repositionInlineMenuSubject .pipe( debounceTime(1000), @@ -252,6 +269,13 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.closeInlineMenuAfterCiphersUpdate().catch((error) => this.logService.error(error)); } + if (!currentTab) { + return; + } + + this.inlineMenuFido2Credentials.clear(); + this.storeInlineMenuFido2CredentialsSubject.next(currentTab.id); + this.inlineMenuCiphers = new Map(); const ciphersViews = await this.getCipherViews(currentTab, updateAllCipherTypes); for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) { @@ -263,6 +287,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { command: "updateAutofillInlineMenuListCiphers", ciphers, showInlineMenuAccountCreation: this.showInlineMenuAccountCreation(), + showPasskeysLabels: this.showPasskeysLabelsWithinInlineMenu, }); } @@ -280,9 +305,9 @@ export class OverlayBackground implements OverlayBackgroundInterface { return this.getAllCipherTypeViews(currentTab); } - const cipherViews = ( - await this.cipherService.getAllDecryptedForUrl(currentTab?.url || "") - ).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); + const cipherViews = (await this.cipherService.getAllDecryptedForUrl(currentTab.url || "")).sort( + (a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b), + ); return this.cardAndIdentityCiphers ? cipherViews.concat(...this.cardAndIdentityCiphers) @@ -301,7 +326,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.cardAndIdentityCiphers.clear(); const cipherViews = ( - await this.cipherService.getAllDecryptedForUrl(currentTab.url, [ + await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", [ CipherType.Card, CipherType.Identity, ]) @@ -331,6 +356,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { const showFavicons = await firstValueFrom(this.domainSettingsService.showFavicons$); const inlineMenuCiphersArray = Array.from(this.inlineMenuCiphers); let inlineMenuCipherData: InlineMenuCipherData[]; + this.showPasskeysLabelsWithinInlineMenu = false; if (this.showInlineMenuAccountCreation()) { inlineMenuCipherData = this.buildInlineMenuAccountCreationCiphers( @@ -363,7 +389,12 @@ export class OverlayBackground implements OverlayBackgroundInterface { if (cipher.type === CipherType.Login) { accountCreationLoginCiphers.push( - this.buildCipherData(inlineMenuCipherId, cipher, showFavicons, true), + this.buildCipherData({ + inlineMenuCipherId, + cipher, + showFavicons, + showInlineMenuAccountCreation: true, + }), ); continue; } @@ -378,7 +409,13 @@ export class OverlayBackground implements OverlayBackgroundInterface { } inlineMenuCipherData.push( - this.buildCipherData(inlineMenuCipherId, cipher, showFavicons, true, identity), + this.buildCipherData({ + inlineMenuCipherId, + cipher, + showFavicons, + showInlineMenuAccountCreation: true, + identityData: identity, + }), ); } @@ -400,6 +437,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { showFavicons: boolean, ) { const inlineMenuCipherData: InlineMenuCipherData[] = []; + const passkeyCipherData: InlineMenuCipherData[] = []; for (let cipherIndex = 0; cipherIndex < inlineMenuCiphersArray.length; cipherIndex++) { const [inlineMenuCipherId, cipher] = inlineMenuCiphersArray[cipherIndex]; @@ -407,12 +445,43 @@ export class OverlayBackground implements OverlayBackgroundInterface { continue; } - inlineMenuCipherData.push(this.buildCipherData(inlineMenuCipherId, cipher, showFavicons)); + if (this.showCipherAsPasskey(cipher)) { + passkeyCipherData.push( + this.buildCipherData({ + inlineMenuCipherId, + cipher, + showFavicons, + hasPasskey: true, + }), + ); + } + + inlineMenuCipherData.push(this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons })); + } + + if (passkeyCipherData.length) { + this.showPasskeysLabelsWithinInlineMenu = + passkeyCipherData.length > 0 && inlineMenuCipherData.length > 0; + return passkeyCipherData.concat(inlineMenuCipherData); } return inlineMenuCipherData; } + /** + * Identifies whether we should show the cipher as a passkey in the inline menu list. + * + * @param cipher - The cipher to check + */ + private showCipherAsPasskey(cipher: CipherView): boolean { + return ( + cipher.type === CipherType.Login && + cipher.login.fido2Credentials?.length > 0 && + (this.inlineMenuFido2Credentials.size === 0 || + this.inlineMenuFido2Credentials.has(cipher.login.fido2Credentials[0].credentialId)) + ); + } + /** * Builds the cipher data for the inline menu list. * @@ -420,15 +489,17 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param cipher - The cipher to build data for * @param showFavicons - Identifies whether favicons should be shown * @param showInlineMenuAccountCreation - Identifies whether the inline menu is for account creation + * @param hasPasskey - Identifies whether the cipher has a FIDO2 credential * @param identityData - Pre-created identity data */ - private buildCipherData( - inlineMenuCipherId: string, - cipher: CipherView, - showFavicons: boolean, - showInlineMenuAccountCreation: boolean = false, - identityData?: { fullName: string; username?: string }, - ): InlineMenuCipherData { + private buildCipherData({ + inlineMenuCipherId, + cipher, + showFavicons, + showInlineMenuAccountCreation, + hasPasskey, + identityData, + }: BuildCipherDataParams): InlineMenuCipherData { const inlineMenuData: InlineMenuCipherData = { id: inlineMenuCipherId, name: cipher.name, @@ -440,7 +511,15 @@ export class OverlayBackground implements OverlayBackgroundInterface { }; if (cipher.type === CipherType.Login) { - inlineMenuData.login = { username: cipher.login.username }; + inlineMenuData.login = { + username: cipher.login.username, + passkey: hasPasskey + ? { + rpName: cipher.login.fido2Credentials[0].rpName, + userName: cipher.login.fido2Credentials[0].userName, + } + : null, + }; return inlineMenuData; } @@ -512,6 +591,17 @@ export class OverlayBackground implements OverlayBackgroundInterface { return this.inlineMenuCiphers.size === 0; } + /** + * Stores the credential ids associated with a FIDO2 conditional mediated ui request. + * + * @param credentials - The FIDO2 credentials to store + */ + private storeInlineMenuFido2Credentials(credentials: Fido2CredentialView[]) { + credentials + .map((credential) => credential.credentialId) + .forEach((credentialId) => this.inlineMenuFido2Credentials.add(credentialId)); + } + /** * Gets the currently focused field and closes the inline menu on that tab. */ @@ -749,10 +839,11 @@ export class OverlayBackground implements OverlayBackgroundInterface { * the selected cipher at the top of the list of ciphers. * * @param inlineMenuCipherId - Cipher ID corresponding to the inlineMenuCiphers map. Does not correspond to the actual cipher's ID. + * @param usePasskey - Identifies whether the cipher has a FIDO2 credential * @param sender - The sender of the port message */ private async fillInlineMenuCipher( - { inlineMenuCipherId }: OverlayPortMessage, + { inlineMenuCipherId, usePasskey }: OverlayPortMessage, { sender }: chrome.runtime.Port, ) { const pageDetails = this.pageDetailsForTab[sender.tab.id]; @@ -762,6 +853,16 @@ export class OverlayBackground implements OverlayBackgroundInterface { const cipher = this.inlineMenuCiphers.get(inlineMenuCipherId); + if (usePasskey && cipher.login?.hasFido2Credentials) { + await this.fido2ClientService.autofillCredential( + sender.tab.id, + cipher.login.fido2Credentials[0].credentialId, + ); + this.updateLastUsedInlineMenuCipher(inlineMenuCipherId, cipher); + + return; + } + if (await this.autofillService.isPasswordRepromptRequired(cipher, sender.tab)) { return; } @@ -777,6 +878,16 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.platformUtilsService.copyToClipboard(totpCode); } + this.updateLastUsedInlineMenuCipher(inlineMenuCipherId, cipher); + } + + /** + * Sets the most recently used cipher at the top of the list of ciphers. + * + * @param inlineMenuCipherId - The ID of the inline menu cipher + * @param cipher - The cipher to set as the most recently used + */ + private updateLastUsedInlineMenuCipher(inlineMenuCipherId: string, cipher: CipherView) { this.inlineMenuCiphers = new Map([[inlineMenuCipherId, cipher], ...this.inlineMenuCiphers]); } @@ -1163,6 +1274,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { command: "updateAutofillInlineMenuListCiphers", ciphers: await this.getInlineMenuCipherData(), showInlineMenuAccountCreation: this.showInlineMenuAccountCreation(), + showPasskeysLabels: this.showPasskeysLabelsWithinInlineMenu, }); } @@ -1214,6 +1326,9 @@ export class OverlayBackground implements OverlayBackgroundInterface { private async openInlineMenu(isFocusingFieldElement = false, isOpeningFullInlineMenu = false) { this.clearDelayedInlineMenuClosure(); const currentTab = await BrowserApi.getTabFromCurrentWindowId(); + if (!currentTab) { + return; + } await BrowserApi.tabSendMessage( currentTab, @@ -1224,8 +1339,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { authStatus: await this.getAuthStatus(), }, { - frameId: - this.focusedFieldData?.tabId === currentTab?.id ? this.focusedFieldData.frameId : 0, + frameId: this.focusedFieldData?.tabId === currentTab.id ? this.focusedFieldData.frameId : 0, }, ); } @@ -1367,6 +1481,9 @@ export class OverlayBackground implements OverlayBackgroundInterface { newIdentity: this.i18nService.translate("newIdentity"), addNewIdentityItem: this.i18nService.translate("addNewIdentityItemAria"), cardNumberEndsWith: this.i18nService.translate("cardNumberEndsWith"), + passkeys: this.i18nService.translate("passkeys"), + passwords: this.i18nService.translate("passwords"), + logInWithPasskey: this.i18nService.translate("logInWithPasskeyAriaLabel"), }; } @@ -2064,6 +2181,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { : AutofillOverlayPort.ButtonMessageConnector, filledByCipherType: this.focusedFieldData?.filledByCipherType, showInlineMenuAccountCreation: this.showInlineMenuAccountCreation(), + showPasskeysLabels: this.showPasskeysLabelsWithinInlineMenu, }); this.updateInlineMenuPosition( { diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts index 5f91e6c0813..7275ced37ba 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -1,5 +1,3 @@ -import { FallbackRequestedError } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; - import { WebauthnUtils } from "../../../vault/fido2/webauthn-utils"; import { MessageType } from "./messaging/message"; @@ -126,13 +124,47 @@ import { Messenger } from "./messaging/messenger"; return await browserCredentials.get(options); } + const abortSignal = options?.signal || new AbortController().signal; const fallbackSupported = browserNativeWebauthnSupport; - try { - if (options?.mediation && options.mediation !== "optional") { - throw new FallbackRequestedError(); - } + if (options?.mediation && options.mediation === "conditional") { + const internalAbortControllers = [new AbortController(), new AbortController()]; + const bitwardenResponse = async (internalAbortController: AbortController) => { + try { + const response = await messenger.request( + { + type: MessageType.CredentialGetRequest, + data: WebauthnUtils.mapCredentialRequestOptions(options, fallbackSupported), + }, + internalAbortController.signal, + ); + if (response.type !== MessageType.CredentialGetResponse) { + throw new Error("Something went wrong."); + } + return WebauthnUtils.mapCredentialAssertResult(response.result); + } catch { + // Ignoring error + } + }; + const browserResponse = (internalAbortController: AbortController) => + browserCredentials.get({ ...options, signal: internalAbortController.signal }); + const abortListener = () => { + internalAbortControllers.forEach((controller) => controller.abort()); + }; + abortSignal.addEventListener("abort", abortListener); + + const response = await Promise.race([ + bitwardenResponse(internalAbortControllers[0]), + browserResponse(internalAbortControllers[1]), + ]); + abortSignal.removeEventListener("abort", abortListener); + internalAbortControllers.forEach((controller) => controller.abort()); + + return response; + } + + try { const response = await messenger.request( { type: MessageType.CredentialGetRequest, diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts index 292d0e01182..21f5a1d701a 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts @@ -128,6 +128,17 @@ describe("Fido2 page script with native WebAuthn support", () => { mockCredentialAssertResult, ); }); + + it("initiates a conditional mediated webauth request", async () => { + mockCredentialRequestOptions.mediation = "conditional"; + mockCredentialRequestOptions.signal = new AbortController().signal; + + await navigator.credentials.get(mockCredentialRequestOptions); + + expect(WebauthnUtils.mapCredentialAssertResult).toHaveBeenCalledWith( + mockCredentialAssertResult, + ); + }); }); describe("destroy", () => { diff --git a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts index 090fb7887c9..ea584165b4d 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts @@ -18,6 +18,7 @@ export type InitAutofillInlineMenuListMessage = AutofillInlineMenuListMessage & ciphers?: InlineMenuCipherData[]; filledByCipherType?: CipherType; showInlineMenuAccountCreation?: boolean; + showPasskeysLabels?: boolean; portKey: string; }; diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap index a8a4d5c4a78..93d757fc51e 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap @@ -478,7 +478,6 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f class="cipher-container" > + + +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • + + +`; + exports[`AutofillInlineMenuList initAutofillInlineMenuList the locked inline menu for an unauthenticated user creates the views for the locked inline menu 1`] = `
    { fillCipherButton.dispatchEvent(new Event("click")); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "fillAutofillInlineMenuCipher", inlineMenuCipherId: "1", portKey }, + { + command: "fillAutofillInlineMenuCipher", + inlineMenuCipherId: "1", + usePasskey: false, + portKey, + }, "*", ); }); @@ -504,6 +510,178 @@ describe("AutofillInlineMenuList", () => { }); }); }); + + describe("creating a list of passkeys", () => { + let passkeyCipher1: InlineMenuCipherData; + let passkeyCipher2: InlineMenuCipherData; + let passkeyCipher3: InlineMenuCipherData; + let loginCipher1: InlineMenuCipherData; + let loginCipher2: InlineMenuCipherData; + let loginCipher3: InlineMenuCipherData; + let loginCipher4: InlineMenuCipherData; + const borderClass = "inline-menu-list-heading--bordered"; + + beforeEach(() => { + passkeyCipher1 = createAutofillOverlayCipherDataMock(1, { + name: "https://example.com", + login: { + username: "username1", + passkey: { + rpName: "https://example.com", + userName: "username1", + }, + }, + }); + passkeyCipher2 = createAutofillOverlayCipherDataMock(2, { + name: "https://example.com", + login: { + username: "", + passkey: { + rpName: "https://example.com", + userName: "username2", + }, + }, + }); + passkeyCipher3 = createAutofillOverlayCipherDataMock(3, { + login: { + username: "username3", + passkey: { + rpName: "https://example.com", + userName: "username3", + }, + }, + }); + loginCipher1 = createAutofillOverlayCipherDataMock(1, { + login: { + username: "username1", + passkey: null, + }, + }); + loginCipher2 = createAutofillOverlayCipherDataMock(2, { + login: { + username: "username2", + passkey: null, + }, + }); + loginCipher3 = createAutofillOverlayCipherDataMock(3, { + login: { + username: "username3", + passkey: null, + }, + }); + loginCipher4 = createAutofillOverlayCipherDataMock(4, { + login: { + username: "username4", + passkey: null, + }, + }); + postWindowMessage( + createInitAutofillInlineMenuListMessageMock({ + ciphers: [ + passkeyCipher1, + passkeyCipher2, + passkeyCipher3, + loginCipher1, + loginCipher2, + loginCipher3, + loginCipher4, + ], + showPasskeysLabels: true, + portKey, + }), + ); + }); + + it("renders the passkeys list item views", () => { + expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot(); + }); + + describe("passkeys headings on scroll", () => { + it("adds a border class to the passkeys and login headings when the user scrolls the cipher list container", () => { + autofillInlineMenuList["ciphersList"].scrollTop = 300; + + autofillInlineMenuList["ciphersList"].dispatchEvent(new Event("scroll")); + + expect( + autofillInlineMenuList["passkeysHeadingElement"].classList.contains(borderClass), + ).toBe(true); + expect(autofillInlineMenuList["passkeysHeadingElement"].style.position).toBe( + "relative", + ); + expect( + autofillInlineMenuList["loginHeadingElement"].classList.contains(borderClass), + ).toBe(true); + }); + + it("removes the border class from the passkeys and login headings when the user scrolls the cipher list container to the top", () => { + jest.useFakeTimers(); + autofillInlineMenuList["ciphersList"].scrollTop = 300; + + autofillInlineMenuList["ciphersList"].dispatchEvent(new Event("scroll")); + jest.advanceTimersByTime(75); + + autofillInlineMenuList["ciphersList"].scrollTop = -1; + autofillInlineMenuList["ciphersList"].dispatchEvent(new Event("scroll")); + + expect( + autofillInlineMenuList["passkeysHeadingElement"].classList.contains(borderClass), + ).toBe(false); + expect(autofillInlineMenuList["passkeysHeadingElement"].style.position).toBe(""); + expect( + autofillInlineMenuList["loginHeadingElement"].classList.contains(borderClass), + ).toBe(false); + }); + + it("loads each page of ciphers until the list of updated ciphers is exhausted", () => { + jest.useFakeTimers(); + autofillInlineMenuList["ciphersList"].scrollTop = 10; + jest.spyOn(autofillInlineMenuList as any, "loadPageOfCiphers"); + + autofillInlineMenuList["ciphersList"].dispatchEvent(new Event("scroll")); + jest.advanceTimersByTime(1000); + autofillInlineMenuList["ciphersList"].dispatchEvent(new Event("scroll")); + jest.runAllTimers(); + + expect(autofillInlineMenuList["loadPageOfCiphers"]).toHaveBeenCalledTimes(1); + }); + }); + + it("skips the logins heading when the user presses ArrowDown to focus the next list item", () => { + const cipherContainerElements = + autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll("li"); + const viewCipherButton = cipherContainerElements[3].querySelector(".view-cipher-button"); + const fillCipherButton = cipherContainerElements[5].querySelector(".fill-cipher-button"); + jest.spyOn(fillCipherButton as HTMLElement, "focus"); + + viewCipherButton.dispatchEvent(new KeyboardEvent("keyup", { code: "ArrowDown" })); + + expect((fillCipherButton as HTMLElement).focus).toBeCalled(); + }); + + it("skips the passkeys heading when the user presses ArrowDown to focus the first list item", () => { + const cipherContainerElements = + autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll("li"); + const viewCipherButton = cipherContainerElements[7].querySelector(".view-cipher-button"); + const fillCipherButton = cipherContainerElements[1].querySelector(".fill-cipher-button"); + jest.spyOn(fillCipherButton as HTMLElement, "focus"); + + viewCipherButton.dispatchEvent(new KeyboardEvent("keyup", { code: "ArrowDown" })); + + expect((fillCipherButton as HTMLElement).focus).toBeCalled(); + }); + + it("skips the logins heading when the user presses ArrowUp to focus the previous list item", () => { + const cipherContainerElements = + autofillInlineMenuList["inlineMenuListContainer"].querySelectorAll("li"); + const viewCipherButton = cipherContainerElements[5].querySelector(".view-cipher-button"); + const fillCipherButton = cipherContainerElements[3].querySelector(".fill-cipher-button"); + jest.spyOn(fillCipherButton as HTMLElement, "focus"); + + viewCipherButton.dispatchEvent(new KeyboardEvent("keyup", { code: "ArrowUp" })); + + expect((fillCipherButton as HTMLElement).focus).toBeCalled(); + }); + }); }); }); diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts index 8bccf9aae47..6ec0bc83991 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts @@ -1,12 +1,18 @@ import "@webcomponents/custom-elements"; import "lit/polyfill-support.js"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { EVENTS } from "@bitwarden/common/autofill/constants"; +import { EVENTS, UPDATE_PASSKEYS_HEADINGS_ON_SCROLL } from "@bitwarden/common/autofill/constants"; import { CipherType } from "@bitwarden/common/vault/enums"; import { InlineMenuCipherData } from "../../../../background/abstractions/overlay.background"; -import { buildSvgDomElement } from "../../../../utils"; -import { globeIcon, lockIcon, plusIcon, viewCipherIcon } from "../../../../utils/svg-icons"; +import { buildSvgDomElement, throttle } from "../../../../utils"; +import { + globeIcon, + lockIcon, + plusIcon, + viewCipherIcon, + passkeyIcon, +} from "../../../../utils/svg-icons"; import { AutofillInlineMenuListWindowMessageHandlers, InitAutofillInlineMenuListMessage, @@ -24,8 +30,16 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { private currentCipherIndex = 0; private filledByCipherType: CipherType; private showInlineMenuAccountCreation: boolean; - private readonly showCiphersPerPage = 6; + private showPasskeysLabels: boolean; private newItemButtonElement: HTMLButtonElement; + private passkeysHeadingElement: HTMLLIElement; + private loginHeadingElement: HTMLLIElement; + private lastPasskeysListItem: HTMLLIElement; + private passkeysHeadingHeight: number; + private lastPasskeysListItemHeight: number; + private ciphersListHeight: number; + private readonly showCiphersPerPage = 6; + private readonly headingBorderClass = "inline-menu-list-heading--bordered"; private readonly inlineMenuListWindowMessageHandlers: AutofillInlineMenuListWindowMessageHandlers = { initAutofillInlineMenuList: ({ message }) => this.initAutofillInlineMenuList(message), @@ -53,6 +67,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { * @param portKey - Background generated key that allows the port to communicate with the background. * @param filledByCipherType - The type of cipher that fills the current field. * @param showInlineMenuAccountCreation - Whether identity ciphers are shown on login fields. + * @param showPasskeysLabels - Whether passkeys labels are shown in the inline menu list. */ private async initAutofillInlineMenuList({ translations, @@ -63,6 +78,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { portKey, filledByCipherType, showInlineMenuAccountCreation, + showPasskeysLabels, }: InitAutofillInlineMenuListMessage) { const linkElement = await this.initAutofillInlineMenuPage( "list", @@ -72,6 +88,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { ); this.filledByCipherType = filledByCipherType; + this.showPasskeysLabels = showPasskeysLabels; const themeClass = `theme_${theme}`; globalThis.document.documentElement.classList.add(themeClass); @@ -155,9 +172,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { this.ciphersList = globalThis.document.createElement("ul"); this.ciphersList.classList.add("inline-menu-list-actions"); this.ciphersList.setAttribute("role", "list"); - this.ciphersList.addEventListener(EVENTS.SCROLL, this.handleCiphersListScrollEvent, { - passive: true, - }); + this.setupCipherListScrollListeners(); this.loadPageOfCiphers(); @@ -288,8 +303,35 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { this.currentCipherIndex++; } - if (this.currentCipherIndex >= this.ciphers.length) { - this.ciphersList.removeEventListener(EVENTS.SCROLL, this.handleCiphersListScrollEvent); + if (!this.showPasskeysLabels && this.allCiphersLoaded()) { + this.ciphersList.removeEventListener(EVENTS.SCROLL, this.updateCiphersListOnScroll); + } + } + + /** + * Validates whether the list of ciphers has been fully loaded. + */ + private allCiphersLoaded() { + return this.currentCipherIndex >= this.ciphers.length; + } + + /** + * Sets up the scroll listeners for the ciphers list. These are used to trigger an update of + * the list of ciphers when the user scrolls to the bottom of the list. Also sets up the + * scroll listeners that reposition the passkeys and login headings when the user scrolls. + */ + private setupCipherListScrollListeners() { + const options = { passive: true }; + this.ciphersList.addEventListener(EVENTS.SCROLL, this.updateCiphersListOnScroll, options); + if (this.showPasskeysLabels) { + this.ciphersList.addEventListener( + EVENTS.SCROLL, + this.useEventHandlersMemo( + throttle(() => this.updatePasskeysHeadingsOnScroll(this.ciphersList.scrollTop), 50), + UPDATE_PASSKEYS_HEADINGS_ON_SCROLL, + ), + options, + ); } } @@ -297,7 +339,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { * Handles updating the list of ciphers when the * user scrolls to the bottom of the list. */ - private handleCiphersListScrollEvent = () => { + private updateCiphersListOnScroll = () => { if (this.cipherListScrollIsDebounced) { return; } @@ -318,22 +360,109 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { */ private handleDebouncedScrollEvent = () => { this.cipherListScrollIsDebounced = false; + const cipherListScrollTop = this.ciphersList.scrollTop; + + this.updatePasskeysHeadingsOnScroll(cipherListScrollTop); + + if (this.allCiphersLoaded()) { + return; + } + + if (!this.ciphersListHeight) { + this.ciphersListHeight = this.ciphersList.offsetHeight; + } const scrollPercentage = - (this.ciphersList.scrollTop / - (this.ciphersList.scrollHeight - this.ciphersList.offsetHeight)) * - 100; + (cipherListScrollTop / (this.ciphersList.scrollHeight - this.ciphersListHeight)) * 100; if (scrollPercentage >= 80) { this.loadPageOfCiphers(); } }; + /** + * Updates the passkeys and login headings when the user scrolls the ciphers list. + * + * @param cipherListScrollTop - The current scroll top position of the ciphers list. + */ + private updatePasskeysHeadingsOnScroll = (cipherListScrollTop: number) => { + if (!this.showPasskeysLabels) { + return; + } + + if (this.passkeysHeadingElement) { + this.togglePasskeysHeadingAnchored(cipherListScrollTop); + this.togglePasskeysHeadingBorder(cipherListScrollTop); + } + + if (this.loginHeadingElement) { + this.toggleLoginHeadingBorder(cipherListScrollTop); + } + }; + + /** + * Anchors the passkeys heading to the top of the last passkey item when the user scrolls. + * + * @param cipherListScrollTop - The current scroll top position of the ciphers list. + */ + private togglePasskeysHeadingAnchored(cipherListScrollTop: number) { + if (!this.passkeysHeadingHeight) { + this.passkeysHeadingHeight = this.passkeysHeadingElement.offsetHeight; + } + + const passkeysHeadingOffset = this.lastPasskeysListItem.offsetTop - this.passkeysHeadingHeight; + if (cipherListScrollTop >= passkeysHeadingOffset) { + this.passkeysHeadingElement.style.position = "relative"; + this.passkeysHeadingElement.style.top = `${passkeysHeadingOffset}px`; + + return; + } + + this.passkeysHeadingElement.setAttribute("style", ""); + } + + /** + * Toggles a border on the passkeys heading on scroll, adding it when the user has + * scrolled at all and removing it once the user scrolls back to the top. + * + * @param cipherListScrollTop - The current scroll top position of the ciphers list. + */ + private togglePasskeysHeadingBorder(cipherListScrollTop: number) { + if (cipherListScrollTop < 1) { + this.passkeysHeadingElement.classList.remove(this.headingBorderClass); + return; + } + + this.passkeysHeadingElement.classList.add(this.headingBorderClass); + } + + /** + * Toggles a border on the login heading on scroll, adding it when the user has + * scrolled past the last passkey item and removing it once the user scrolls back up. + * + * @param cipherListScrollTop - The current scroll top position of the ciphers list. + */ + private toggleLoginHeadingBorder(cipherListScrollTop: number) { + if (!this.lastPasskeysListItemHeight) { + this.lastPasskeysListItemHeight = this.lastPasskeysListItem.offsetHeight; + } + + const lastPasskeyOffset = this.lastPasskeysListItem.offsetTop + this.lastPasskeysListItemHeight; + if (cipherListScrollTop < lastPasskeyOffset) { + this.loginHeadingElement.classList.remove(this.headingBorderClass); + return; + } + + this.loginHeadingElement.classList.add(this.headingBorderClass); + } + /** * Builds the list item for a given cipher. * * @param cipher - The cipher to build the list item for. */ private buildInlineMenuListActionsItem(cipher: InlineMenuCipherData) { + this.buildPasskeysHeadingElements(cipher); + const fillCipherElement = this.buildFillCipherElement(cipher); const viewCipherElement = this.buildViewCipherElement(cipher); @@ -346,9 +475,43 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { inlineMenuListActionsItem.classList.add("inline-menu-list-actions-item"); inlineMenuListActionsItem.appendChild(cipherContainerElement); + if (this.showPasskeysLabels && cipher.login?.passkey) { + this.lastPasskeysListItem = inlineMenuListActionsItem; + } + return inlineMenuListActionsItem; } + /** + * Builds the passkeys and login headings for the list of cipher items. + * + * @param cipher - The cipher that will follow the heading. + */ + private buildPasskeysHeadingElements(cipher: InlineMenuCipherData) { + if (!this.showPasskeysLabels || (this.passkeysHeadingElement && this.loginHeadingElement)) { + return; + } + + const passkeyData = cipher.login?.passkey; + if (!this.passkeysHeadingElement && passkeyData) { + this.passkeysHeadingElement = globalThis.document.createElement("li"); + this.passkeysHeadingElement.classList.add("inline-menu-list-heading"); + this.passkeysHeadingElement.textContent = this.getTranslation("passkeys"); + this.ciphersList.appendChild(this.passkeysHeadingElement); + + return; + } + + if (!this.passkeysHeadingElement || this.loginHeadingElement || passkeyData) { + return; + } + + this.loginHeadingElement = globalThis.document.createElement("li"); + this.loginHeadingElement.classList.add("inline-menu-list-heading"); + this.loginHeadingElement.textContent = this.getTranslation("passwords"); + this.ciphersList.appendChild(this.loginHeadingElement); + } + /** * Builds the fill cipher button for a given cipher. * Wraps the cipher icon and details. @@ -364,8 +527,13 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { fillCipherElement.classList.add("fill-cipher-button", "inline-menu-list-action"); fillCipherElement.setAttribute( "aria-label", - `${this.getTranslation("fillCredentialsFor")} ${cipher.name}`, + `${ + cipher.login?.passkey + ? this.getTranslation("logInWithPasskey") + : this.getTranslation("fillCredentialsFor") + } ${cipher.name}`, ); + this.addFillCipherElementAriaDescription(fillCipherElement, cipher); fillCipherElement.append(cipherIcon, cipherDetailsElement); fillCipherElement.addEventListener(EVENTS.CLICK, this.handleFillCipherClickEvent(cipher)); @@ -385,10 +553,14 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { cipher: InlineMenuCipherData, ) { if (cipher.login) { - fillCipherElement.setAttribute( - "aria-description", - `${this.getTranslation("username")}: ${cipher.login.username}`, - ); + const passkeyUserName = cipher.login.passkey?.userName || ""; + const username = cipher.login.username || passkeyUserName; + if (username) { + fillCipherElement.setAttribute( + "aria-description", + `${this.getTranslation("username")}: ${username}`, + ); + } return; } @@ -419,13 +591,15 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { * @param cipher - The cipher to fill. */ private handleFillCipherClickEvent = (cipher: InlineMenuCipherData) => { + const usePasskey = !!cipher.login?.passkey; return this.useEventHandlersMemo( () => this.postMessageToParent({ command: "fillAutofillInlineMenuCipher", inlineMenuCipherId: cipher.id, + usePasskey, }), - `${cipher.id}-fill-cipher-button-click-handler`, + `${cipher.id}-fill-cipher-button-click-handler-${usePasskey ? "passkey" : ""}`, ); }; @@ -599,14 +773,20 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { * @param cipher - The cipher to build the details for. */ private buildCipherDetailsElement(cipher: InlineMenuCipherData) { - const cipherNameElement = this.buildCipherNameElement(cipher); - const cipherSubtitleElement = this.buildCipherSubtitleElement(cipher); - const cipherDetailsElement = globalThis.document.createElement("span"); cipherDetailsElement.classList.add("cipher-details"); + + const cipherNameElement = this.buildCipherNameElement(cipher); if (cipherNameElement) { cipherDetailsElement.appendChild(cipherNameElement); } + + if (cipher.login?.passkey) { + return this.buildPasskeysCipherDetailsElement(cipher, cipherDetailsElement); + } + + const subTitleText = this.getSubTitleText(cipher); + const cipherSubtitleElement = this.buildCipherSubtitleElement(subTitleText); if (cipherSubtitleElement) { cipherDetailsElement.appendChild(cipherSubtitleElement); } @@ -635,10 +815,9 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { /** * Builds the subtitle element for a given cipher. * - * @param cipher - The cipher to build the username login element for. + * @param subTitleText - The subtitle text to display. */ - private buildCipherSubtitleElement(cipher: InlineMenuCipherData): HTMLSpanElement | null { - const subTitleText = this.getSubTitleText(cipher); + private buildCipherSubtitleElement(subTitleText: string): HTMLSpanElement | null { if (!subTitleText) { return null; } @@ -651,6 +830,52 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { return cipherSubtitleElement; } + /** + * Builds the passkeys details for a given cipher. Includes the passkey name and username. + * + * @param cipher - The cipher to build the passkey details for. + * @param cipherDetailsElement - The cipher details element to append the passkey details to. + */ + private buildPasskeysCipherDetailsElement( + cipher: InlineMenuCipherData, + cipherDetailsElement: HTMLSpanElement, + ): HTMLSpanElement { + let rpNameSubtitle: HTMLSpanElement; + + if (cipher.name !== cipher.login.passkey.rpName) { + rpNameSubtitle = this.buildCipherSubtitleElement(cipher.login.passkey.rpName); + if (rpNameSubtitle) { + rpNameSubtitle.prepend(buildSvgDomElement(passkeyIcon)); + rpNameSubtitle.classList.add("cipher-subtitle--passkey"); + cipherDetailsElement.appendChild(rpNameSubtitle); + } + } + + if (cipher.login.username) { + const usernameSubtitle = this.buildCipherSubtitleElement(cipher.login.username); + if (usernameSubtitle) { + if (!rpNameSubtitle) { + usernameSubtitle.prepend(buildSvgDomElement(passkeyIcon)); + usernameSubtitle.classList.add("cipher-subtitle--passkey"); + } + cipherDetailsElement.appendChild(usernameSubtitle); + } + + return cipherDetailsElement; + } + + const passkeySubtitle = this.buildCipherSubtitleElement(cipher.login.passkey.userName); + if (passkeySubtitle) { + if (!rpNameSubtitle) { + passkeySubtitle.prepend(buildSvgDomElement(passkeyIcon)); + passkeySubtitle.classList.add("cipher-subtitle--passkey"); + } + cipherDetailsElement.appendChild(passkeySubtitle); + } + + return cipherDetailsElement; + } + /** * Gets the subtitle text for a given cipher. * @@ -779,7 +1004,11 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { * @param currentListItem - The current list item. */ private focusNextListItem(currentListItem: HTMLElement) { - const nextListItem = currentListItem.nextSibling as HTMLElement; + let nextListItem = currentListItem.nextSibling as HTMLElement; + if (this.listItemIsHeadingElement(nextListItem)) { + nextListItem = nextListItem.nextSibling as HTMLElement; + } + const nextSibling = nextListItem?.querySelector(".inline-menu-list-action") as HTMLElement; if (nextSibling) { nextSibling.focus(); @@ -791,7 +1020,11 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { return; } - const firstListItem = currentListItem.parentElement?.firstChild as HTMLElement; + let firstListItem = currentListItem.parentElement?.firstChild as HTMLElement; + if (this.listItemIsHeadingElement(firstListItem)) { + firstListItem = firstListItem.nextSibling as HTMLElement; + } + const firstSibling = firstListItem?.querySelector(".inline-menu-list-action") as HTMLElement; firstSibling?.focus(); } @@ -803,7 +1036,11 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { * @param currentListItem - The current list item. */ private focusPreviousListItem(currentListItem: HTMLElement) { - const previousListItem = currentListItem.previousSibling as HTMLElement; + let previousListItem = currentListItem.previousSibling as HTMLElement; + if (this.listItemIsHeadingElement(previousListItem)) { + previousListItem = previousListItem.previousSibling as HTMLElement; + } + const previousSibling = previousListItem?.querySelector( ".inline-menu-list-action", ) as HTMLElement; @@ -856,4 +1093,13 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { private isFilledByIdentityCipher = () => { return this.filledByCipherType === CipherType.Identity; }; + + /** + * Identifies if the passed list item is a heading element. + * + * @param listItem - The list item to check. + */ + private listItemIsHeadingElement = (listItem: HTMLElement) => { + return listItem === this.passkeysHeadingElement || listItem === this.loginHeadingElement; + }; } diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss b/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss index a63a4bd91ca..fe38ce9933f 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss @@ -166,6 +166,35 @@ body { } } +.inline-menu-list-heading { + position: sticky; + top: 0; + z-index: 1; + font-family: $font-family-sans-serif; + font-weight: 600; + font-size: 1rem; + line-height: 1.3; + letter-spacing: 0.025rem; + width: 100%; + padding: 0.6rem 0.8rem; + will-change: transform; + border-bottom: 0.1rem solid; + + @include themify($themes) { + color: themed("textColor"); + background-color: themed("backgroundColor"); + border-bottom-color: themed("backgroundColor"); + } + + &--bordered { + transition: border-bottom-color 0.15s ease; + + @include themify($themes) { + border-bottom-color: themed("borderColor"); + } + } +} + .inline-menu-list-container--with-new-item-button { .inline-menu-list-actions { max-height: 13.8rem; @@ -340,5 +369,28 @@ body { @include themify($themes) { color: themed("mutedTextColor"); } + + &--passkey { + display: flex; + align-content: center; + align-items: center; + justify-content: flex-start; + + svg { + width: 1.5rem; + height: 1.5rem; + margin-right: 0.2rem; + + @include themify($themes) { + fill: themed("mutedTextColor") !important; + } + + path { + @include themify($themes) { + fill: themed("mutedTextColor") !important; + } + } + } + } } } 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 955334e3fa0..bd03be3fccc 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 @@ -20,9 +20,11 @@ export class InlineMenuFieldQualificationService private usernameFieldTypes = new Set(["text", "email", "number", "tel"]); private usernameAutocompleteValue = "username"; private emailAutocompleteValue = "email"; + private webAuthnAutocompleteValue = "webauthn"; private loginUsernameAutocompleteValues = new Set([ this.usernameAutocompleteValue, this.emailAutocompleteValue, + this.webAuthnAutocompleteValue, ]); private fieldIgnoreListString = AutoFillConstants.FieldIgnoreList.join(","); private passwordFieldExcludeListString = AutoFillConstants.PasswordFieldExcludeList.join(","); diff --git a/apps/browser/src/autofill/spec/autofill-mocks.ts b/apps/browser/src/autofill/spec/autofill-mocks.ts index 2e1202b4a63..c29b8900280 100644 --- a/apps/browser/src/autofill/spec/autofill-mocks.ts +++ b/apps/browser/src/autofill/spec/autofill-mocks.ts @@ -187,7 +187,10 @@ export function createAutofillOverlayCipherDataMock( return { id: String(index), name: `website login ${index}`, - login: { username: `username${index}` }, + login: { + username: `username${index}`, + passkey: null, + }, type: CipherType.Login, reprompt: CipherRepromptType.None, favorite: false, diff --git a/apps/browser/src/autofill/utils/svg-icons.ts b/apps/browser/src/autofill/utils/svg-icons.ts index 1df140d37d0..eec5aaae078 100644 --- a/apps/browser/src/autofill/utils/svg-icons.ts +++ b/apps/browser/src/autofill/utils/svg-icons.ts @@ -1,19 +1,20 @@ -const logoIcon = +export const logoIcon = ''; -const logoLockedIcon = +export const logoLockedIcon = ''; -const globeIcon = +export const globeIcon = ''; -const lockIcon = +export const lockIcon = ''; -const plusIcon = +export const plusIcon = ''; -const viewCipherIcon = +export const viewCipherIcon = ''; -export { logoIcon, logoLockedIcon, globeIcon, lockIcon, plusIcon, viewCipherIcon }; +export const passkeyIcon = + ''; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index db3055b4c68..27007e20214 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -116,6 +116,7 @@ import { BulkEncryptServiceImplementation } from "@bitwarden/common/platform/ser import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; import { FallbackBulkEncryptService } from "@bitwarden/common/platform/services/cryptography/fallback-bulk-encrypt.service"; import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation"; +import { Fido2ActiveRequestManager } from "@bitwarden/common/platform/services/fido2/fido2-active-request-manager"; import { Fido2AuthenticatorService } from "@bitwarden/common/platform/services/fido2/fido2-authenticator.service"; import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service"; import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service"; @@ -984,6 +985,7 @@ export default class MainBackground { this.syncService, this.logService, ); + const fido2ActiveRequestManager = new Fido2ActiveRequestManager(); this.fido2ClientService = new Fido2ClientService( this.fido2AuthenticatorService, this.configService, @@ -991,6 +993,7 @@ export default class MainBackground { this.vaultSettingsService, this.domainSettingsService, this.taskSchedulerService, + fido2ActiveRequestManager, this.logService, ); @@ -1536,6 +1539,7 @@ export default class MainBackground { this.autofillSettingsService, this.i18nService, this.platformUtilsService, + this.fido2ClientService, this.themeStateService, ); } diff --git a/apps/browser/src/vault/fido2/webauthn-utils.ts b/apps/browser/src/vault/fido2/webauthn-utils.ts index df8e5a8fb20..b880b3c790f 100644 --- a/apps/browser/src/vault/fido2/webauthn-utils.ts +++ b/apps/browser/src/vault/fido2/webauthn-utils.ts @@ -111,6 +111,7 @@ export class WebauthnUtils { rpId: keyOptions.rpId, userVerification: keyOptions.userVerification, timeout: keyOptions.timeout, + mediation: options.mediation, fallbackSupported, }; } diff --git a/libs/common/src/autofill/constants/index.ts b/libs/common/src/autofill/constants/index.ts index 215998a560c..b838ff64e9d 100644 --- a/libs/common/src/autofill/constants/index.ts +++ b/libs/common/src/autofill/constants/index.ts @@ -56,6 +56,8 @@ export const NOTIFICATION_BAR_LIFESPAN_MS = 150000; // 150 seconds export const AUTOFILL_OVERLAY_HANDLE_REPOSITION = "autofill-overlay-handle-reposition-event"; +export const UPDATE_PASSKEYS_HEADINGS_ON_SCROLL = "update-passkeys-headings-on-scroll"; + export const AutofillOverlayVisibility = { Off: 0, OnButtonClick: 1, diff --git a/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts new file mode 100644 index 00000000000..4e164c4577c --- /dev/null +++ b/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts @@ -0,0 +1,21 @@ +import { Observable, Subject } from "rxjs"; + +import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; + +export interface ActiveRequest { + credentials: Fido2CredentialView[]; + subject: Subject; +} + +export type RequestCollection = Readonly<{ [tabId: number]: ActiveRequest }>; + +export abstract class Fido2ActiveRequestManager { + getActiveRequest$: (tabId: number) => Observable; + getActiveRequest: (tabId: number) => ActiveRequest | undefined; + newActiveRequest: ( + tabId: number, + credentials: Fido2CredentialView[], + abortController: AbortController, + ) => Promise; + removeActiveRequest: (tabId: number) => void; +} diff --git a/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts index f3aa616cb35..535248e7ecd 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts @@ -1,3 +1,5 @@ +import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; + /** * This class represents an abstraction of the WebAuthn Authenticator model as described by W3C: * https://www.w3.org/TR/webauthn-3/#sctn-authenticator-model @@ -32,6 +34,14 @@ export abstract class Fido2AuthenticatorService { tab: chrome.tabs.Tab, abortController?: AbortController, ) => Promise; + + /** + * Discover credentials for a given Relying Party + * + * @param rpId The Relying Party's ID + * @returns A promise that resolves with an array of discoverable credentials + */ + silentCredentialDiscovery: (rpId: string) => Promise; } export enum Fido2AlgorithmIdentifier { @@ -132,6 +142,9 @@ export interface Fido2AuthenticatorGetAssertionParams { extensions: unknown; /** Forwarded to user interface */ fallbackSupported: boolean; + + // Bypass the UI and assume that the user has already interacted with the authenticator + assumeUserPresence?: boolean; } export interface Fido2AuthenticatorGetAssertionResult { diff --git a/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts index 8e2a1538308..2ba67a48be2 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts @@ -1,3 +1,7 @@ +import { Observable } from "rxjs"; + +import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; + export const UserRequestedFallbackAbortReason = "UserRequestedFallback"; export type UserVerification = "discouraged" | "preferred" | "required"; @@ -16,6 +20,10 @@ export type UserVerification = "discouraged" | "preferred" | "required"; export abstract class Fido2ClientService { isFido2FeatureEnabled: (hostname: string, origin: string) => Promise; + availableAutofillCredentials$: (tabId: number) => Observable; + + autofillCredential: (tabId: number, credentialId: string) => Promise; + /** * Allows WebAuthn Relying Party scripts to request the creation of a new public key credential source. * For more information please see: https://www.w3.org/TR/webauthn-3/#sctn-createCredential @@ -142,6 +150,7 @@ export interface AssertCredentialParams { userVerification?: UserVerification; timeout: number; sameOriginWithAncestors: boolean; + mediation?: "silent" | "optional" | "required" | "conditional"; fallbackSupported: boolean; } diff --git a/libs/common/src/platform/services/fido2/fido2-active-request-manager.spec.ts b/libs/common/src/platform/services/fido2/fido2-active-request-manager.spec.ts new file mode 100644 index 00000000000..77f9bd3f9cb --- /dev/null +++ b/libs/common/src/platform/services/fido2/fido2-active-request-manager.spec.ts @@ -0,0 +1,89 @@ +import { mock } from "jest-mock-extended"; +import { firstValueFrom, Observable } from "rxjs"; + +import { flushPromises } from "@bitwarden/browser/src/autofill/spec/testing-utils"; + +import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; + +import { Fido2ActiveRequestManager } from "./fido2-active-request-manager"; + +jest.mock("rxjs", () => { + const rxjs = jest.requireActual("rxjs"); + const { firstValueFrom } = rxjs; + return { + ...rxjs, + firstValueFrom: jest.fn(firstValueFrom), + }; +}); + +describe("Fido2ActiveRequestManager", () => { + const credentialId = "123"; + const tabId = 1; + let requestManager: Fido2ActiveRequestManager; + + beforeEach(() => { + requestManager = new Fido2ActiveRequestManager(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("creates a new active request", async () => { + const fido2CredentialView = mock({ + credentialId, + }); + const credentials = [fido2CredentialView]; + const abortController = new AbortController(); + (firstValueFrom as jest.Mock).mockResolvedValue(credentialId); + + const result = await requestManager.newActiveRequest(tabId, credentials, abortController); + await flushPromises(); + + expect(result).toBe(credentialId); + }); + + it("gets the observable stream of active requests", async () => { + (firstValueFrom as jest.Mock).mockResolvedValue(credentialId); + await requestManager.newActiveRequest(tabId, [], new AbortController()); + + const result = requestManager.getActiveRequest$(tabId); + + expect(result).toBeInstanceOf(Observable); + + result.subscribe((activeRequest) => { + expect(activeRequest).toBeDefined(); + }); + }); + + it("returns the active request associated with a given tab id", async () => { + const fido2CredentialView = mock({ + credentialId, + }); + const credentials = [fido2CredentialView]; + (firstValueFrom as jest.Mock).mockResolvedValue(credentialId); + await requestManager.newActiveRequest(tabId, credentials, new AbortController()); + + const result = requestManager.getActiveRequest(tabId); + + expect(result).toEqual({ + credentials: credentials, + subject: expect.any(Object), + }); + }); + + it("removes the active request associated with a given tab id", async () => { + const fido2CredentialView = mock({ + credentialId, + }); + const credentials = [fido2CredentialView]; + (firstValueFrom as jest.Mock).mockResolvedValue(credentialId); + await requestManager.newActiveRequest(tabId, credentials, new AbortController()); + + requestManager.removeActiveRequest(tabId); + + const result = requestManager.getActiveRequest(tabId); + + expect(result).toBeUndefined(); + }); +}); diff --git a/libs/common/src/platform/services/fido2/fido2-active-request-manager.ts b/libs/common/src/platform/services/fido2/fido2-active-request-manager.ts new file mode 100644 index 00000000000..0f82d8a09ce --- /dev/null +++ b/libs/common/src/platform/services/fido2/fido2-active-request-manager.ts @@ -0,0 +1,109 @@ +import { + BehaviorSubject, + distinctUntilChanged, + firstValueFrom, + map, + Observable, + shareReplay, + startWith, + Subject, +} from "rxjs"; + +import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; +import { + ActiveRequest, + RequestCollection, + Fido2ActiveRequestManager as Fido2ActiveRequestManagerAbstraction, +} from "../../abstractions/fido2/fido2-active-request-manager.abstraction"; + +export class Fido2ActiveRequestManager implements Fido2ActiveRequestManagerAbstraction { + private activeRequests$: BehaviorSubject = new BehaviorSubject({}); + + /** + * Gets the observable stream of all active requests associated with a given tab id. + * + * @param tabId - The tab id to get the active request for. + */ + getActiveRequest$(tabId: number): Observable { + return this.activeRequests$.pipe( + map((requests) => requests[tabId]), + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: true }), + startWith(undefined), + ); + } + + /** + * Gets the active request associated with a given tab id. + * + * @param tabId - The tab id to get the active request for. + */ + getActiveRequest(tabId: number): ActiveRequest | undefined { + return this.activeRequests$.value[tabId]; + } + + /** + * Creates a new active fido2 request. + * + * @param tabId - The tab id to associate the request with. + * @param credentials - The credentials to use for the request. + * @param abortController - The abort controller to use for the request. + */ + async newActiveRequest( + tabId: number, + credentials: Fido2CredentialView[], + abortController: AbortController, + ): Promise { + const newRequest: ActiveRequest = { + credentials, + subject: new Subject(), + }; + this.updateRequests((existingRequests) => ({ + ...existingRequests, + [tabId]: newRequest, + })); + + const abortListener = () => this.abortActiveRequest(tabId); + abortController.signal.addEventListener("abort", abortListener); + const credentialId = firstValueFrom(newRequest.subject); + abortController.signal.removeEventListener("abort", abortListener); + + return credentialId; + } + + /** + * Removes and aborts the active request associated with a given tab id. + * + * @param tabId - The tab id to abort the active request for. + */ + removeActiveRequest(tabId: number) { + this.abortActiveRequest(tabId); + this.updateRequests((existingRequests) => { + const newRequests = { ...existingRequests }; + delete newRequests[tabId]; + return newRequests; + }); + } + + /** + * Aborts the active request associated with a given tab id. + * + * @param tabId - The tab id to abort the active request for. + */ + private abortActiveRequest(tabId: number): void { + this.activeRequests$.value[tabId]?.subject.error( + new DOMException("The operation either timed out or was not allowed.", "AbortError"), + ); + } + + /** + * Updates the active requests. + * + * @param updateFunction - The function to use to update the active requests. + */ + private updateRequests( + updateFunction: (existingRequests: RequestCollection) => RequestCollection, + ) { + this.activeRequests$.next(updateFunction(this.activeRequests$.value)); + } +} diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index 202381c5ead..806b6592737 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -756,6 +756,22 @@ describe("FidoAuthenticatorService", () => { }); }); + describe("silentCredentialDiscovery", () => { + it("returns the fido2Credentials of a cipher found by its rpId", async () => { + const credentialId = Utils.newGuid(); + const cipher = await createCipherView( + { type: CipherType.Login }, + { credentialId, rpId: RpId, discoverable: true }, + ); + const ciphers = [cipher]; + cipherService.getAllDecrypted.mockResolvedValue(ciphers); + + const result = await authenticator.silentCredentialDiscovery(RpId); + + expect(result).toEqual([cipher.login.fido2Credentials[0]]); + }); + }); + async function createParams( params: Partial = {}, ): Promise { diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index 3464154b9cc..e82a2e32c97 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -234,10 +234,15 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotAllowed); } - const response = await userInterfaceSession.pickCredential({ - cipherIds: cipherOptions.map((cipher) => cipher.id), - userVerification: params.requireUserVerification, - }); + let response; + if (this.requiresUserVerificationPrompt(params, cipherOptions)) { + response = await userInterfaceSession.pickCredential({ + cipherIds: cipherOptions.map((cipher) => cipher.id), + userVerification: params.requireUserVerification, + }); + } else { + response = { cipherId: cipherOptions[0].id, userVerified: false }; + } const selectedCipherId = response.cipherId; const userVerified = response.userVerified; const selectedCipher = cipherOptions.find((c) => c.id === selectedCipherId); @@ -310,6 +315,24 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr } } + private requiresUserVerificationPrompt( + params: Fido2AuthenticatorGetAssertionParams, + cipherOptions: CipherView[], + ): boolean { + return ( + params.requireUserVerification || + !params.assumeUserPresence || + cipherOptions.length > 1 || + cipherOptions.length === 0 || + cipherOptions.some((cipher) => cipher.reprompt !== CipherRepromptType.None) + ); + } + + async silentCredentialDiscovery(rpId: string): Promise { + const credentials = await this.findCredentialsByRp(rpId); + return credentials.map((c) => c.login.fido2Credentials[0]); + } + /** Finds existing crendetials and returns the `cipherId` for each one */ private async findExcludedCredentials( credentials: PublicKeyCredentialDescriptor[], diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts index aac447e0337..c0ae2cabfba 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts @@ -1,12 +1,17 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { of } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { AuthService } from "../../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; import { Utils } from "../../../platform/misc/utils"; import { VaultSettingsService } from "../../../vault/abstractions/vault-settings/vault-settings.service"; +import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; import { ConfigService } from "../../abstractions/config/config.service"; +import { + ActiveRequest, + Fido2ActiveRequestManager, +} from "../../abstractions/fido2/fido2-active-request-manager.abstraction"; import { Fido2AuthenticatorError, Fido2AuthenticatorErrorCode, @@ -37,6 +42,8 @@ describe("FidoAuthenticatorService", () => { let vaultSettingsService: MockProxy; let domainSettingsService: MockProxy; let taskSchedulerService: MockProxy; + let activeRequest!: MockProxy; + let requestManager!: MockProxy; let client!: Fido2ClientService; let tab!: chrome.tabs.Tab; let isValidRpId!: jest.SpyInstance; @@ -48,6 +55,13 @@ describe("FidoAuthenticatorService", () => { vaultSettingsService = mock(); domainSettingsService = mock(); taskSchedulerService = mock(); + activeRequest = mock({ + subject: new BehaviorSubject(""), + }); + requestManager = mock({ + getActiveRequest$: (tabId: number) => new BehaviorSubject(activeRequest), + getActiveRequest: (tabId: number) => activeRequest, + }); isValidRpId = jest.spyOn(DomainUtils, "isValidRpId"); @@ -58,11 +72,12 @@ describe("FidoAuthenticatorService", () => { vaultSettingsService, domainSettingsService, taskSchedulerService, + requestManager, ); configService.serverConfig$ = of({ environment: { vault: VaultUrl } } as any); vaultSettingsService.enablePasskeys$ = of(true); domainSettingsService.neverDomains$ = of({}); - authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Unlocked); + authService.activeAccountStatus$ = of(AuthenticationStatus.Unlocked); tab = { id: 123, windowId: 456 } as chrome.tabs.Tab; }); @@ -592,6 +607,50 @@ describe("FidoAuthenticatorService", () => { }); }); + describe("assert mediated conditional ui credential", () => { + const params = createParams({ + userVerification: "required", + mediation: "conditional", + allowedCredentialIds: [], + }); + + beforeEach(() => { + requestManager.newActiveRequest.mockResolvedValue(crypto.randomUUID()); + authenticator.getAssertion.mockResolvedValue(createAuthenticatorAssertResult()); + }); + + it("creates an active mediated conditional request", async () => { + await client.assertCredential(params, tab); + + expect(requestManager.newActiveRequest).toHaveBeenCalled(); + expect(authenticator.getAssertion).toHaveBeenCalledWith( + expect.objectContaining({ + assumeUserPresence: true, + rpId: RpId, + }), + tab, + ); + }); + + it("restarts the mediated conditional request if a user aborts the request", async () => { + authenticator.getAssertion.mockRejectedValueOnce(new Error()); + + await client.assertCredential(params, tab); + + expect(authenticator.getAssertion).toHaveBeenCalledTimes(2); + }); + + it("restarts the mediated conditional request if a the abort controller aborts the request", async () => { + const abortController = new AbortController(); + abortController.abort(); + authenticator.getAssertion.mockRejectedValueOnce(new DOMException("AbortError")); + + await client.assertCredential(params, tab); + + expect(authenticator.getAssertion).toHaveBeenCalledTimes(2); + }); + }); + function createParams(params: Partial = {}): AssertCredentialParams { return { allowedCredentialIds: params.allowedCredentialIds ?? [], @@ -602,6 +661,7 @@ describe("FidoAuthenticatorService", () => { userVerification: params.userVerification, sameOriginWithAncestors: true, fallbackSupported: params.fallbackSupported ?? false, + mediation: params.mediation, }; } @@ -616,6 +676,28 @@ describe("FidoAuthenticatorService", () => { }; } }); + + describe("autofill of credentials through the active request manager", () => { + it("returns an observable that updates with an array of the credentials for active Fido2 requests", async () => { + const activeRequestCredentials = mock(); + activeRequest.credentials = [activeRequestCredentials]; + + const observable = client.availableAutofillCredentials$(tab.id); + observable.subscribe((credentials) => { + expect(credentials).toEqual([activeRequestCredentials]); + }); + }); + + it("triggers the logic of the next behavior subject of an active request", async () => { + const activeRequestCredentials = mock(); + activeRequest.credentials = [activeRequestCredentials]; + jest.spyOn(activeRequest.subject, "next"); + + await client.autofillCredential(tab.id, activeRequestCredentials.credentialId); + + expect(activeRequest.subject.next).toHaveBeenCalled(); + }); + }); }); /** This is a fake function that always returns the same byte sequence */ diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.ts b/libs/common/src/platform/services/fido2/fido2-client.service.ts index b384fce1f12..972d6889122 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.ts @@ -1,15 +1,18 @@ -import { firstValueFrom, Subscription } from "rxjs"; +import { firstValueFrom, map, Observable, Subscription } from "rxjs"; import { parse } from "tldts"; import { AuthService } from "../../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; import { VaultSettingsService } from "../../../vault/abstractions/vault-settings/vault-settings.service"; +import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; import { ConfigService } from "../../abstractions/config/config.service"; +import { Fido2ActiveRequestManager } from "../../abstractions/fido2/fido2-active-request-manager.abstraction"; import { Fido2AuthenticatorError, Fido2AuthenticatorErrorCode, Fido2AuthenticatorGetAssertionParams, + Fido2AuthenticatorGetAssertionResult, Fido2AuthenticatorMakeCredentialsParams, Fido2AuthenticatorService, PublicKeyCredentialDescriptor, @@ -32,6 +35,7 @@ import { TaskSchedulerService } from "../../scheduling/task-scheduler.service"; import { isValidRpId } from "./domain-utils"; import { Fido2Utils } from "./fido2-utils"; +import { guidToRawFormat } from "./guid-utils"; /** * Bitwarden implementation of the Web Authentication API as described by W3C @@ -61,6 +65,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { private vaultSettingsService: VaultSettingsService, private domainSettingsService: DomainSettingsService, private taskSchedulerService: TaskSchedulerService, + private requestManager: Fido2ActiveRequestManager, private logService?: LogService, ) { this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.fido2ClientAbortTimeout, () => @@ -68,6 +73,17 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { ); } + availableAutofillCredentials$(tabId: number): Observable { + return this.requestManager + .getActiveRequest$(tabId) + .pipe(map((request) => request?.credentials ?? [])); + } + + async autofillCredential(tabId: number, credentialId: string) { + const request = this.requestManager.getActiveRequest(tabId); + request.subject.next(credentialId); + } + async isFido2FeatureEnabled(hostname: string, origin: string): Promise { const isUserLoggedIn = (await this.authService.getAuthStatus()) !== AuthenticationStatus.LoggedOut; @@ -287,6 +303,16 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { }; const clientDataJSON = JSON.stringify(collectedClientData); const clientDataJSONBytes = Utils.fromByteStringToArray(clientDataJSON); + + if (params.mediation === "conditional") { + return this.handleMediatedConditionalRequest( + params, + tab, + abortController, + clientDataJSONBytes, + ); + } + const clientDataHash = await crypto.subtle.digest({ name: "SHA-256" }, clientDataJSONBytes); const getAssertionParams = mapToGetAssertionParams({ params, clientDataHash }); @@ -339,6 +365,59 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { timeoutSubscription?.unsubscribe(); + return this.generateAssertCredentialResult(getAssertionResult, clientDataJSONBytes); + } + + private async handleMediatedConditionalRequest( + params: AssertCredentialParams, + tab: chrome.tabs.Tab, + abortController: AbortController, + clientDataJSONBytes: Uint8Array, + ): Promise { + let getAssertionResult; + let assumeUserPresence = false; + while (!getAssertionResult) { + const authStatus = await firstValueFrom(this.authService.activeAccountStatus$); + const availableCredentials = + authStatus === AuthenticationStatus.Unlocked + ? await this.authenticator.silentCredentialDiscovery(params.rpId) + : []; + this.logService?.info( + `[Fido2Client] started mediated request, available credentials: ${availableCredentials.length}`, + ); + const credentialId = await this.requestManager.newActiveRequest( + tab.id, + availableCredentials, + abortController, + ); + params.allowedCredentialIds = [Fido2Utils.bufferToString(guidToRawFormat(credentialId))]; + assumeUserPresence = true; + + const clientDataHash = await crypto.subtle.digest({ name: "SHA-256" }, clientDataJSONBytes); + const getAssertionParams = mapToGetAssertionParams({ + params, + clientDataHash, + assumeUserPresence, + }); + + try { + getAssertionResult = await this.authenticator.getAssertion(getAssertionParams, tab); + } catch (e) { + this.logService?.info(`[Fido2Client] Aborted by user: ${e}`); + } + + if (abortController.signal.aborted) { + this.logService?.info(`[Fido2Client] Aborted with AbortController`); + } + } + + return this.generateAssertCredentialResult(getAssertionResult, clientDataJSONBytes); + } + + private generateAssertCredentialResult( + getAssertionResult: Fido2AuthenticatorGetAssertionResult, + clientDataJSONBytes: Uint8Array, + ): AssertCredentialResult { return { authenticatorData: Fido2Utils.bufferToString(getAssertionResult.authenticatorData), clientDataJSON: Fido2Utils.bufferToString(clientDataJSONBytes), @@ -431,9 +510,11 @@ function mapToMakeCredentialParams({ function mapToGetAssertionParams({ params, clientDataHash, + assumeUserPresence, }: { params: AssertCredentialParams; clientDataHash: ArrayBuffer; + assumeUserPresence?: boolean; }): Fido2AuthenticatorGetAssertionParams { const allowCredentialDescriptorList: PublicKeyCredentialDescriptor[] = params.allowedCredentialIds.map((id) => ({ @@ -453,5 +534,6 @@ function mapToGetAssertionParams({ allowCredentialDescriptorList, extensions: {}, fallbackSupported: params.fallbackSupported, + assumeUserPresence, }; } From a3bf74ae1b32a4616c1385917fd3af6fa86999a1 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:12:55 +0200 Subject: [PATCH 08/59] SM-1146: Display total counts of projects, machine accounts, secrets in Secrets Manager (#9791) * SM-1146: Secrets Manager total counts * SM-1146: Tab link component simplifications * SM-1146: Total counts update on CRUD * SM-1146: Total counts API paths update * SM-1146: Unit test coverage for services * SM-1146: Fix incorrect types returned * SM-1146: Storybook example for tab-link with child counter * SM-1146: Tailwind states with groups * SM-1146: Moving counts view types in one file * SM-1146: Moving counts methods, responses to one shared service * SM-1146: Removing redundant services imports * SM-1146: Removing redundant observables * SM-1337: Total counts hidden for suspended organizations * SM-1336: Total counts updated on import * SM-1336: Import error handling refactor * SM-1336: Import error handling improvements * SM-1336: Import error not working with project errors, Unit Test coverage * Update bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * SM-1336: UT deprecation removal * SM-1336: Better UT * SM-1336: Lint fix * SM-1146: Linter fix --------- Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> --- .../layout/navigation.component.html | 18 +- .../layout/navigation.component.ts | 81 +++- .../models/view/counts.view.ts | 17 + .../overview/overview.component.html | 4 +- .../overview/overview.component.ts | 22 +- .../projects/project/project.component.html | 21 +- .../projects/project/project.component.ts | 29 +- .../service-account-people.component.ts | 3 +- .../service-account.component.html | 21 +- .../service-account.component.ts | 40 +- .../models/error/sm-import-error-line.ts | 2 +- .../settings/porting/sm-import.component.ts | 70 ++-- .../services/sm-porting-api.service.spec.ts | 286 ++++++++++++++ .../services/sm-porting-api.service.ts | 22 +- .../settings/settings.module.ts | 3 +- .../access-policy.service.spec.ts | 359 ++++++++++++++++++ .../access-policies/access-policy.service.ts | 32 +- .../shared/counts/count.service.spec.ts | 98 +++++ .../shared/counts/count.service.ts | 47 +++ .../responses/organization-counts.response.ts | 14 + .../responses/project-counts.response.ts | 14 + .../service-account-counts.response.ts | 14 + .../src/navigation/nav-item.component.html | 8 +- .../tabs/tab-nav-bar/tab-link.component.html | 8 +- libs/components/src/tabs/tabs.stories.ts | 14 + 25 files changed, 1143 insertions(+), 104 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/models/view/counts.view.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.spec.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.spec.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/counts/count.service.spec.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/counts/count.service.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/counts/models/responses/organization-counts.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/counts/models/responses/project-counts.response.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/counts/models/responses/service-account-counts.response.ts diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html index 80750dc0d88..2ee5ea5037f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.html @@ -6,19 +6,31 @@ [text]="'projects' | i18n" route="projects" [relativeTo]="route.parent" - > + > +
    + {{ organizationCounts?.projects }} +
    + + > +
    + {{ organizationCounts?.secrets }} +
    + + > +
    + {{ organizationCounts?.serviceAccounts }} +
    + org.canAccessSecretsManager; - protected isAdmin$ = this.route.params.pipe( - concatMap( - async (params) => (await this.organizationService.get(params.organizationId))?.isAdmin, - ), - ); + protected isAdmin$: Observable; + protected isOrgEnabled$: Observable; + protected organizationCounts: OrganizationCounts; + private destroy$: Subject = new Subject(); constructor( protected route: ActivatedRoute, private organizationService: OrganizationService, + private countService: CountService, + private projectService: ProjectService, + private secretService: SecretService, + private serviceAccountService: ServiceAccountService, + private portingApiService: SecretsManagerPortingApiService, ) {} + + ngOnInit() { + const org$ = this.route.params.pipe( + concatMap((params) => this.organizationService.get(params.organizationId)), + distinctUntilChanged(), + takeUntil(this.destroy$), + ); + + this.isAdmin$ = org$.pipe( + map((org) => org?.isAdmin), + takeUntil(this.destroy$), + ); + + this.isOrgEnabled$ = org$.pipe( + map((org) => org?.enabled), + takeUntil(this.destroy$), + ); + + combineLatest([ + org$, + this.projectService.project$.pipe(startWith(null)), + this.secretService.secret$.pipe(startWith(null)), + this.serviceAccountService.serviceAccount$.pipe(startWith(null)), + this.portingApiService.imports$.pipe(startWith(null)), + ]) + .pipe( + filter(([org]) => org?.enabled), + switchMap(([org]) => this.countService.getOrganizationCounts(org.id)), + takeUntil(this.destroy$), + ) + .subscribe((organizationCounts) => { + this.organizationCounts = { + projects: organizationCounts.projects, + secrets: organizationCounts.secrets, + serviceAccounts: organizationCounts.serviceAccounts, + }; + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/counts.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/counts.view.ts new file mode 100644 index 00000000000..1520c772ec7 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/counts.view.ts @@ -0,0 +1,17 @@ +export type OrganizationCounts = { + projects: number; + secrets: number; + serviceAccounts: number; +}; + +export type ProjectCounts = { + secrets: number; + people: number; + serviceAccounts: number; +}; + +export type ServiceAccountCounts = { + projects: number; + people: number; + accessTokens: number; +}; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html index 29cbf78464b..a82e35afb60 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html @@ -54,7 +54,7 @@ [projects]="view.latestProjects" >
    @@ -72,7 +72,7 @@ [secrets]="view.latestSecrets" >
    - {{ "showingPortionOfTotal" | i18n: view.latestSecrets.length : view.allSecrets.length }} + {{ "showingPortionOfTotal" | i18n: view.latestSecrets.length : view.counts.secrets }} {{ "viewAll" | i18n }}
    diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index 4c057e56988..7073b4c289f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -21,6 +21,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService } from "@bitwarden/components"; +import { OrganizationCounts } from "../models/view/counts.view"; import { ProjectListView } from "../models/view/project-list.view"; import { SecretListView } from "../models/view/secret-list.view"; import { @@ -51,6 +52,7 @@ import { ServiceAccountOperation, } from "../service-accounts/dialog/service-account-dialog.component"; import { ServiceAccountService } from "../service-accounts/service-account.service"; +import { CountService } from "../shared/counts/count.service"; import { SecretsListComponent } from "../shared/secrets-list.component"; import { SMOnboardingTasks, SMOnboardingTasksService } from "./sm-onboarding-tasks.service"; @@ -87,11 +89,13 @@ export class OverviewComponent implements OnInit, OnDestroy { latestProjects: ProjectListView[]; latestSecrets: SecretListView[]; tasks: OrganizationTasks; + counts: OrganizationCounts; }>; constructor( private route: ActivatedRoute, private projectService: ProjectService, + private countService: CountService, private secretService: SecretService, private serviceAccountService: ServiceAccountService, private dialogService: DialogService, @@ -148,10 +152,19 @@ export class OverviewComponent implements OnInit, OnDestroy { share(), ); + const counts$ = combineLatest([ + orgId$, + this.secretService.secret$.pipe(startWith(null)), + this.projectService.project$.pipe(startWith(null)), + ]).pipe( + switchMap(([orgId]) => this.countService.getOrganizationCounts(orgId)), + share(), + ); + this.view$ = orgId$.pipe( switchMap((orgId) => - combineLatest([projects$, secrets$, serviceAccounts$]).pipe( - switchMap(async ([projects, secrets, serviceAccounts]) => ({ + combineLatest([projects$, secrets$, serviceAccounts$, counts$]).pipe( + switchMap(async ([projects, secrets, serviceAccounts, counts]) => ({ latestProjects: this.getRecentItems(projects, this.tableSize), latestSecrets: this.getRecentItems(secrets, this.tableSize), allProjects: projects, @@ -162,6 +175,11 @@ export class OverviewComponent implements OnInit, OnDestroy { createProject: projects.length > 0, createServiceAccount: serviceAccounts.length > 0, }), + counts: { + projects: counts.projects, + secrets: counts.secrets, + serviceAccounts: counts.serviceAccounts, + }, })), ), ), diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html index d95f4392ebc..d44d7898cac 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html @@ -3,10 +3,25 @@ {{ "projects" | i18n }} - {{ "secrets" | i18n }} + + {{ "secrets" | i18n }} +
    + {{ projectCounts?.secrets }} +
    +
    - {{ "people" | i18n }} - {{ "machineAccounts" | i18n }} + + {{ "people" | i18n }} +
    + {{ projectCounts?.people }} +
    +
    + + {{ "machineAccounts" | i18n }} +
    + {{ projectCounts?.serviceAccounts }} +
    +
    diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts index 07ca32600a9..fb7f2d74528 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts @@ -1,5 +1,5 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; +import { ActivatedRoute } from "@angular/router"; import { combineLatest, filter, @@ -13,11 +13,13 @@ import { } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService } from "@bitwarden/components"; +import { ProjectCounts } from "../../models/view/counts.view"; import { ProjectView } from "../../models/view/project.view"; +import { SecretService } from "../../secrets/secret.service"; +import { AccessPolicyService } from "../../shared/access-policies/access-policy.service"; +import { CountService } from "../../shared/counts/count.service"; import { OperationType, ProjectDialogComponent, @@ -31,6 +33,7 @@ import { ProjectService } from "../project.service"; }) export class ProjectComponent implements OnInit, OnDestroy { protected project$: Observable; + protected projectCounts: ProjectCounts; private organizationId: string; private projectId: string; @@ -40,11 +43,11 @@ export class ProjectComponent implements OnInit, OnDestroy { constructor( private route: ActivatedRoute, private projectService: ProjectService, - private router: Router, + private secretService: SecretService, + private accessPolicyService: AccessPolicyService, private dialogService: DialogService, - private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, private organizationService: OrganizationService, + private countService: CountService, ) {} ngOnInit(): void { @@ -62,13 +65,23 @@ export class ProjectComponent implements OnInit, OnDestroy { const organization$ = this.route.params.pipe( concatMap((params) => this.organizationService.get$(params.organizationId)), ); + const projectCounts$ = combineLatest([ + this.route.params, + this.secretService.secret$.pipe(startWith(null)), + this.accessPolicyService.accessPolicy$.pipe(startWith(null)), + ]).pipe(switchMap(([params]) => this.countService.getProjectCounts(params.projectId))); - combineLatest([projectId$, organization$]) + combineLatest([projectId$, organization$, projectCounts$]) .pipe(takeUntil(this.destroy$)) - .subscribe(([projectId, organization]) => { + .subscribe(([projectId, organization, projectCounts]) => { this.organizationId = organization.id; this.projectId = projectId; this.organizationEnabled = organization.enabled; + this.projectCounts = { + secrets: projectCounts.secrets, + people: projectCounts.people, + serviceAccounts: projectCounts.serviceAccounts, + }; }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts index 31394f7fec7..46033688826 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts @@ -8,6 +8,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { DialogService } from "@bitwarden/components"; +import { ServiceAccountPeopleAccessPoliciesView } from "../../models/view/access-policies/service-account-people-access-policies.view"; import { AccessPolicySelectorService } from "../../shared/access-policies/access-policy-selector/access-policy-selector.service"; import { ApItemValueType, @@ -179,7 +180,7 @@ export class ServiceAccountPeopleComponent implements OnInit, OnDestroy { private async updateServiceAccountPeopleAccessPolicies( serviceAccountId: string, selectedPolicies: ApItemValueType[], - ) { + ): Promise { const serviceAccountPeopleView = convertToPeopleAccessPoliciesView(selectedPolicies); return await this.accessPolicyService.putServiceAccountPeopleAccessPolicies( serviceAccountId, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html index 00b5201a657..c9ce8d8c64c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html @@ -10,9 +10,24 @@ - {{ "projects" | i18n }} - {{ "people" | i18n }} - {{ "accessTokens" | i18n }} + + {{ "projects" | i18n }} +
    + {{ serviceAccountCounts?.projects }} +
    +
    + + {{ "people" | i18n }} +
    + {{ serviceAccountCounts?.people }} +
    +
    + + {{ "accessTokens" | i18n }} +
    + {{ serviceAccountCounts?.accessTokens }} +
    +
    {{ "eventLogs" | i18n }}
    + + + + + diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts new file mode 100644 index 00000000000..84f25a146c6 --- /dev/null +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts @@ -0,0 +1,40 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; +import { + CardComponent, + FormFieldModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + IconButtonModule, +} from "@bitwarden/components"; + +import { TotpCaptureService } from "../../cipher-form"; + +@Component({ + selector: "app-autofill-options-view", + templateUrl: "autofill-options-view.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + CardComponent, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + FormFieldModule, + IconButtonModule, + ], +}) +export class AutofillOptionsViewComponent { + @Input() loginUris: LoginUriView[]; + + constructor(private totpCaptureService: TotpCaptureService) {} + + async openWebsite(selectedUri: string) { + await this.totpCaptureService.openAutofillNewTab(selectedUri); + } +} diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.ts b/libs/vault/src/cipher-view/card-details/card-details-view.component.ts index a3c55977a28..028417faf16 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.ts +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.ts @@ -13,8 +13,6 @@ import { IconButtonModule, } from "@bitwarden/components"; -import { OrgIconDirective } from "../../components/org-icon.directive"; - @Component({ selector: "app-card-details-view", templateUrl: "card-details-view.component.html", @@ -26,7 +24,6 @@ import { OrgIconDirective } from "../../components/org-icon.directive"; SectionComponent, SectionHeaderComponent, TypographyModule, - OrgIconDirective, FormFieldModule, IconButtonModule, ], diff --git a/libs/vault/src/cipher-view/cipher-view.component.html b/libs/vault/src/cipher-view/cipher-view.component.html index 6046bb48fd6..2a2a4ded05c 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.html +++ b/libs/vault/src/cipher-view/cipher-view.component.html @@ -8,10 +8,19 @@ > + + + + + + + - - - + diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index d2ac0963911..7900fddbe06 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -19,10 +19,12 @@ import { PopupPageComponent } from "../../../../apps/browser/src/platform/popup/ import { AdditionalOptionsComponent } from "./additional-options/additional-options.component"; import { AttachmentsV2ViewComponent } from "./attachments/attachments-v2-view.component"; +import { AutofillOptionsViewComponent } from "./autofill-options/autofill-options-view.component"; import { CardDetailsComponent } from "./card-details/card-details-view.component"; import { CustomFieldV2Component } from "./custom-fields/custom-fields-v2.component"; import { ItemDetailsV2Component } from "./item-details/item-details-v2.component"; import { ItemHistoryV2Component } from "./item-history/item-history-v2.component"; +import { LoginCredentialsViewComponent } from "./login-credentials/login-credentials-view.component"; import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-identity-sections.component"; @Component({ @@ -43,6 +45,8 @@ import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-ide CustomFieldV2Component, CardDetailsComponent, ViewIdentitySectionsComponent, + LoginCredentialsViewComponent, + AutofillOptionsViewComponent, ], }) export class CipherViewComponent implements OnInit, OnDestroy { @@ -61,6 +65,7 @@ export class CipherViewComponent implements OnInit, OnDestroy { async ngOnInit() { await this.loadCipherData(); } + ngOnDestroy(): void { this.destroyed$.next(); this.destroyed$.complete(); @@ -71,6 +76,15 @@ export class CipherViewComponent implements OnInit, OnDestroy { return cardholderName || code || expMonth || expYear || brand || number; } + get hasLogin() { + const { username, password, totp } = this.cipher.login; + return username || password || totp; + } + + get hasAutofill() { + return this.cipher.login?.uris.length > 0; + } + async loadCipherData() { if (this.cipher.collectionIds.length > 0) { this.collections$ = this.collectionService diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html new file mode 100644 index 00000000000..9ca983a5c98 --- /dev/null +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -0,0 +1,106 @@ + + +

    {{ "loginCredentials" | i18n }}

    +
    + + + + {{ "username" | i18n }} + + + + + + {{ "password" | i18n }} + + + + + + + + + + {{ "verificationCodeTotp" | i18n }} + + {{ "premium" | i18n }} + + + + + + +
    diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts new file mode 100644 index 00000000000..15234556e9f --- /dev/null +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -0,0 +1,63 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { Router } from "@angular/router"; +import { Observable, shareReplay } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; +import { + CardComponent, + FormFieldModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + IconButtonModule, + BadgeModule, + ColorPasswordModule, +} from "@bitwarden/components"; + +@Component({ + selector: "app-login-credentials-view", + templateUrl: "login-credentials-view.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + CardComponent, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + FormFieldModule, + IconButtonModule, + BadgeModule, + ColorPasswordModule, + ], +}) +export class LoginCredentialsViewComponent { + @Input() login: LoginView; + @Input() viewPassword: boolean; + isPremium$: Observable = + this.billingAccountProfileStateService.hasPremiumFromAnySource$.pipe( + shareReplay({ refCount: true, bufferSize: 1 }), + ); + showPasswordCount: boolean = false; + passwordRevealed: boolean = false; + + constructor( + private billingAccountProfileStateService: BillingAccountProfileStateService, + private router: Router, + ) {} + + async getPremium() { + await this.router.navigate(["/premium"]); + } + + pwToggleValue(evt: boolean) { + this.passwordRevealed = evt; + } + + togglePasswordCount() { + this.showPasswordCount = !this.showPasswordCount; + } +} From d2c4c4cad490b220039f18afb38bf28505ce14c5 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Thu, 8 Aug 2024 13:04:28 -0500 Subject: [PATCH 13/59] [PM-10723] Remove autocomplete="new-password" attribute from 2fa based code inputs (#10448) * [PM-10723] Remove autocomplete="new-password" attribute from 2fa based code inputs * [PM-10723] Remove autocomplete="new-password" attribute from 2fa based code inputs --- .../src/app/auth/settings/two-factor-duo.component.html | 8 +------- apps/web/src/app/auth/two-factor.component.html | 9 +-------- .../two-factor-auth-yubikey.component.html | 1 - 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/apps/web/src/app/auth/settings/two-factor-duo.component.html b/apps/web/src/app/auth/settings/two-factor-duo.component.html index 6c733ed798a..f20bd4f5f70 100644 --- a/apps/web/src/app/auth/settings/two-factor-duo.component.html +++ b/apps/web/src/app/auth/settings/two-factor-duo.component.html @@ -25,13 +25,7 @@ {{ "twoFactorDuoClientSecret" | i18n }} - + {{ "twoFactorDuoApiHostname" | i18n }} diff --git a/apps/web/src/app/auth/two-factor.component.html b/apps/web/src/app/auth/two-factor.component.html index b09ab68c312..a941413dbb0 100644 --- a/apps/web/src/app/auth/two-factor.component.html +++ b/apps/web/src/app/auth/two-factor.component.html @@ -37,14 +37,7 @@ {{ "verificationCode" | i18n }} - + diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component.html b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component.html index ea0c98cece9..c6654c00edb 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component.html +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component.html @@ -11,7 +11,6 @@ bitInput appAutofocus appInputVerbatim - autocomplete="new-password" [(ngModel)]="tokenValue" (input)="token.emit(tokenValue)" /> From 28a2014e69a812d69df1f77aa6a1d7071daffee2 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:24:49 -0500 Subject: [PATCH 14/59] [PM-8204] V2 Folder View (#10423) * add no folders icon to icon library * add/edit folder contained within a dialog * add/edit folder dialog contained new item dropdown * browser refresh folders page component * swap in v2 folder component for extension refresh * add copy for all folder related changes --- apps/browser/src/_locales/en/messages.json | 18 ++ apps/browser/src/popup/app-routing.module.ts | 6 +- .../add-edit-folder-dialog.component.html | 41 +++++ .../add-edit-folder-dialog.component.spec.ts | 157 ++++++++++++++++++ .../add-edit-folder-dialog.component.ts | 155 +++++++++++++++++ .../new-item-dropdown-v2.component.html | 13 +- .../new-item-dropdown-v2.component.spec.ts | 108 ++++++++++++ .../new-item-dropdown-v2.component.ts | 12 +- .../popup/settings/folders-v2.component.html | 47 ++++++ .../settings/folders-v2.component.spec.ts | 115 +++++++++++++ .../popup/settings/folders-v2.component.ts | 76 +++++++++ libs/components/src/icon/icons/index.ts | 1 + libs/components/src/icon/icons/no-folders.ts | 19 +++ 13 files changed, 759 insertions(+), 9 deletions(-) create mode 100644 apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.html create mode 100644 apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts create mode 100644 apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts create mode 100644 apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts create mode 100644 apps/browser/src/vault/popup/settings/folders-v2.component.html create mode 100644 apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts create mode 100644 apps/browser/src/vault/popup/settings/folders-v2.component.ts create mode 100644 libs/components/src/icon/icons/no-folders.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 0217e3f6331..6c41706ddd7 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently":{ + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete folder" }, diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 9f13ab57d96..16e12c3d75c 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -81,6 +81,7 @@ import { AttachmentsV2Component } from "../vault/popup/components/vault-v2/attac import { ViewV2Component } from "../vault/popup/components/vault-v2/view-v2/view-v2.component"; import { AppearanceComponent } from "../vault/popup/settings/appearance.component"; import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component"; +import { FoldersV2Component } from "../vault/popup/settings/folders-v2.component"; import { FoldersComponent } from "../vault/popup/settings/folders.component"; import { SyncComponent } from "../vault/popup/settings/sync.component"; import { VaultSettingsV2Component } from "../vault/popup/settings/vault-settings-v2.component"; @@ -303,12 +304,11 @@ const routes: Routes = [ canActivate: [authGuard], data: { state: "vault-settings" }, }), - { + ...extensionRefreshSwap(FoldersComponent, FoldersV2Component, { path: "folders", - component: FoldersComponent, canActivate: [authGuard], data: { state: "folders" }, - }, + }), { path: "add-folder", component: FolderAddEditComponent, diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.html b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.html new file mode 100644 index 00000000000..0e6dbf24427 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.html @@ -0,0 +1,41 @@ +
    + + + {{ (variant === "add" ? "newFolder" : "editFolder") | i18n }} + +
    + + {{ "folderName" | i18n }} + + + {{ "folderHintText" | i18n }} + + +
    +
    + + + + +
    +
    +
    diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts new file mode 100644 index 00000000000..8453b4cc63e --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts @@ -0,0 +1,157 @@ +import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { NoopAnimationsModule } from "@angular/platform-browser/animations"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { Folder } from "@bitwarden/common/vault/models/domain/folder"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { DialogService, ToastService } from "@bitwarden/components"; + +import { + AddEditFolderDialogComponent, + AddEditFolderDialogData, +} from "./add-edit-folder-dialog.component"; + +describe("AddEditFolderDialogComponent", () => { + let component: AddEditFolderDialogComponent; + let fixture: ComponentFixture; + + const dialogData = {} as AddEditFolderDialogData; + const folder = new Folder(); + const encrypt = jest.fn().mockResolvedValue(folder); + const save = jest.fn().mockResolvedValue(null); + const deleteFolder = jest.fn().mockResolvedValue(null); + const openSimpleDialog = jest.fn().mockResolvedValue(true); + const error = jest.fn(); + const close = jest.fn(); + const showToast = jest.fn(); + + const dialogRef = { + close, + }; + + beforeEach(async () => { + encrypt.mockClear(); + save.mockClear(); + deleteFolder.mockClear(); + error.mockClear(); + close.mockClear(); + showToast.mockClear(); + + await TestBed.configureTestingModule({ + imports: [AddEditFolderDialogComponent, NoopAnimationsModule], + providers: [ + { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: FolderService, useValue: { encrypt } }, + { provide: FolderApiServiceAbstraction, useValue: { save, delete: deleteFolder } }, + { provide: LogService, useValue: { error } }, + { provide: ToastService, useValue: { showToast } }, + { provide: DIALOG_DATA, useValue: dialogData }, + { provide: DialogRef, useValue: dialogRef }, + ], + }) + .overrideProvider(DialogService, { useValue: { openSimpleDialog } }) + .compileComponents(); + + fixture = TestBed.createComponent(AddEditFolderDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe("new folder", () => { + it("requires a folder name", async () => { + await component.submit(); + + expect(encrypt).not.toHaveBeenCalled(); + + component.folderForm.controls.name.setValue("New Folder"); + + await component.submit(); + + expect(encrypt).toHaveBeenCalled(); + }); + + it("submits a new folder view", async () => { + component.folderForm.controls.name.setValue("New Folder"); + + await component.submit(); + + const newFolder = new FolderView(); + newFolder.name = "New Folder"; + + expect(encrypt).toHaveBeenCalledWith(newFolder); + expect(save).toHaveBeenCalled(); + }); + + it("shows success toast after saving", async () => { + component.folderForm.controls.name.setValue("New Folder"); + + await component.submit(); + + expect(showToast).toHaveBeenCalledWith({ + message: "editedFolder", + title: null, + variant: "success", + }); + }); + + it("closes the dialog after saving", async () => { + component.folderForm.controls.name.setValue("New Folder"); + + await component.submit(); + + expect(close).toHaveBeenCalled(); + }); + + it("logs error if saving fails", async () => { + const errorObj = new Error("Failed to save folder"); + save.mockRejectedValue(errorObj); + + component.folderForm.controls.name.setValue("New Folder"); + + await component.submit(); + + expect(error).toHaveBeenCalledWith(errorObj); + }); + }); + + describe("editing folder", () => { + const folderView = new FolderView(); + folderView.id = "1"; + folderView.name = "Folder 1"; + + beforeEach(() => { + dialogData.editFolderConfig = { folder: folderView }; + + component.ngOnInit(); + }); + + it("populates form with folder name", () => { + expect(component.folderForm.controls.name.value).toBe("Folder 1"); + }); + + it("submits the updated folder", async () => { + component.folderForm.controls.name.setValue("Edited Folder"); + await component.submit(); + + expect(encrypt).toHaveBeenCalledWith({ + ...dialogData.editFolderConfig.folder, + name: "Edited Folder", + }); + }); + + it("deletes the folder", async () => { + await component.deleteFolder(); + + expect(deleteFolder).toHaveBeenCalledWith(folderView.id); + expect(showToast).toHaveBeenCalledWith({ + variant: "success", + title: null, + message: "deletedFolder", + }); + }); + }); +}); diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts new file mode 100644 index 00000000000..33263533990 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts @@ -0,0 +1,155 @@ +import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { + AfterViewInit, + Component, + DestroyRef, + inject, + Inject, + OnInit, + ViewChild, +} from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { + AsyncActionsModule, + BitSubmitDirective, + ButtonComponent, + ButtonModule, + DialogModule, + DialogService, + FormFieldModule, + IconButtonModule, + ToastService, +} from "@bitwarden/components"; + +export type AddEditFolderDialogData = { + /** When provided, dialog will display edit folder variant */ + editFolderConfig?: { folder: FolderView }; +}; + +@Component({ + standalone: true, + selector: "vault-add-edit-folder-dialog", + templateUrl: "./add-edit-folder-dialog.component.html", + imports: [ + CommonModule, + JslibModule, + DialogModule, + ButtonModule, + FormFieldModule, + ReactiveFormsModule, + IconButtonModule, + AsyncActionsModule, + ], +}) +export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { + @ViewChild(BitSubmitDirective) private bitSubmit: BitSubmitDirective; + @ViewChild("submitBtn") private submitBtn: ButtonComponent; + + folder: FolderView; + + variant: "add" | "edit"; + + folderForm = this.formBuilder.group({ + name: ["", Validators.required], + }); + + private destroyRef = inject(DestroyRef); + + constructor( + private formBuilder: FormBuilder, + private folderService: FolderService, + private folderApiService: FolderApiServiceAbstraction, + private toastService: ToastService, + private i18nService: I18nService, + private logService: LogService, + private dialogService: DialogService, + private dialogRef: DialogRef, + @Inject(DIALOG_DATA) private data?: AddEditFolderDialogData, + ) {} + + ngOnInit(): void { + this.variant = this.data?.editFolderConfig ? "edit" : "add"; + + if (this.variant === "edit") { + this.folderForm.controls.name.setValue(this.data.editFolderConfig.folder.name); + this.folder = this.data.editFolderConfig.folder; + } else { + // Create a new folder view + this.folder = new FolderView(); + } + } + + ngAfterViewInit(): void { + this.bitSubmit.loading$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((loading) => { + if (!this.submitBtn) { + return; + } + + this.submitBtn.loading = loading; + }); + } + + /** Submit the new folder */ + submit = async () => { + if (this.folderForm.invalid) { + return; + } + + this.folder.name = this.folderForm.controls.name.value; + + try { + const folder = await this.folderService.encrypt(this.folder); + await this.folderApiService.save(folder); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("editedFolder"), + }); + + this.close(); + } catch (e) { + this.logService.error(e); + } + }; + + /** Delete the folder with when the user provides a confirmation */ + deleteFolder = async () => { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "deleteFolder" }, + content: { key: "deleteFolderPermanently" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + await this.folderApiService.delete(this.folder.id); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedFolder"), + }); + } catch (e) { + this.logService.error(e); + } + + this.close(); + }; + + /** Close the dialog */ + private close() { + this.dialogRef.close(); + } +} 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 0bd85c21696..78403784f46 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html @@ -3,20 +3,25 @@ {{ "new" | i18n }} - + {{ "typeLogin" | i18n }} - + {{ "typeCard" | i18n }} - + {{ "typeIdentity" | i18n }} - + {{ "note" | i18n }} + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts new file mode 100644 index 00000000000..868cb242aa2 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts @@ -0,0 +1,108 @@ +import { CommonModule } from "@angular/common"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { Router } from "@angular/router"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { ButtonModule, DialogService, MenuModule } from "@bitwarden/components"; + +import { AddEditQueryParams } from "../add-edit/add-edit-v2.component"; +import { AddEditFolderDialogComponent } from "../add-edit-folder-dialog/add-edit-folder-dialog.component"; + +import { NewItemDropdownV2Component, NewItemInitialValues } from "./new-item-dropdown-v2.component"; + +describe("NewItemDropdownV2Component", () => { + let component: NewItemDropdownV2Component; + let fixture: ComponentFixture; + const open = jest.fn(); + const navigate = jest.fn(); + + beforeEach(async () => { + open.mockClear(); + navigate.mockClear(); + + await TestBed.configureTestingModule({ + imports: [NewItemDropdownV2Component, MenuModule, ButtonModule, JslibModule, CommonModule], + providers: [ + { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: Router, useValue: { navigate } }, + ], + }) + .overrideProvider(DialogService, { useValue: { open } }) + .compileComponents(); + + fixture = TestBed.createComponent(NewItemDropdownV2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("opens new folder dialog", () => { + component.openFolderDialog(); + + expect(open).toHaveBeenCalledWith(AddEditFolderDialogComponent); + }); + + describe("new item", () => { + const emptyParams: AddEditQueryParams = { + collectionId: undefined, + organizationId: undefined, + folderId: undefined, + }; + + beforeEach(() => { + jest.spyOn(component, "newItemNavigate"); + }); + + it("navigates to new login", () => { + component.newItemNavigate(CipherType.Login); + + expect(navigate).toHaveBeenCalledWith(["/add-cipher"], { + queryParams: { type: CipherType.Login.toString(), ...emptyParams }, + }); + }); + + it("navigates to new card", () => { + component.newItemNavigate(CipherType.Card); + + expect(navigate).toHaveBeenCalledWith(["/add-cipher"], { + queryParams: { type: CipherType.Card.toString(), ...emptyParams }, + }); + }); + + it("navigates to new identity", () => { + component.newItemNavigate(CipherType.Identity); + + expect(navigate).toHaveBeenCalledWith(["/add-cipher"], { + queryParams: { type: CipherType.Identity.toString(), ...emptyParams }, + }); + }); + + it("navigates to new note", () => { + component.newItemNavigate(CipherType.SecureNote); + + expect(navigate).toHaveBeenCalledWith(["/add-cipher"], { + queryParams: { type: CipherType.SecureNote.toString(), ...emptyParams }, + }); + }); + + it("includes initial values", () => { + component.initialValues = { + folderId: "222-333-444", + organizationId: "444-555-666", + collectionId: "777-888-999", + } as NewItemInitialValues; + + component.newItemNavigate(CipherType.Login); + + expect(navigate).toHaveBeenCalledWith(["/add-cipher"], { + queryParams: { + type: CipherType.Login.toString(), + folderId: "222-333-444", + organizationId: "444-555-666", + collectionId: "777-888-999", + }, + }); + }); + }); +}); diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts index 65456fd74ae..ee9d7c28fec 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts @@ -5,9 +5,10 @@ import { Router, RouterLink } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; -import { ButtonModule, MenuModule, NoItemsModule } from "@bitwarden/components"; +import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components"; import { AddEditQueryParams } from "../add-edit/add-edit-v2.component"; +import { AddEditFolderDialogComponent } from "../add-edit-folder-dialog/add-edit-folder-dialog.component"; export interface NewItemInitialValues { folderId?: string; @@ -30,7 +31,10 @@ export class NewItemDropdownV2Component { @Input() initialValues: NewItemInitialValues; - constructor(private router: Router) {} + constructor( + private router: Router, + private dialogService: DialogService, + ) {} private buildQueryParams(type: CipherType): AddEditQueryParams { return { @@ -44,4 +48,8 @@ export class NewItemDropdownV2Component { newItemNavigate(type: CipherType) { void this.router.navigate(["/add-cipher"], { queryParams: this.buildQueryParams(type) }); } + + openFolderDialog() { + this.dialogService.open(AddEditFolderDialogComponent); + } } diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.html b/apps/browser/src/vault/popup/settings/folders-v2.component.html new file mode 100644 index 00000000000..7ac69876cc0 --- /dev/null +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + {{ folder.name }} + + + + + + + + + {{ "noFoldersAdded" | i18n }} + {{ "createFoldersToOrganize" | i18n }} + + + + + diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts new file mode 100644 index 00000000000..eecad04613e --- /dev/null +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts @@ -0,0 +1,115 @@ +import { Component, Input } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { DialogService } from "@bitwarden/components"; + +import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { AddEditFolderDialogComponent } from "../components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component"; + +import { FoldersV2Component } from "./folders-v2.component"; + +@Component({ + standalone: true, + selector: "popup-header", + template: ``, +}) +class MockPopupHeaderComponent { + @Input() pageTitle: string; + @Input() backAction: () => void; +} + +@Component({ + standalone: true, + selector: "popup-footer", + template: ``, +}) +class MockPopupFooterComponent { + @Input() pageTitle: string; +} + +describe("FoldersV2Component", () => { + let component: FoldersV2Component; + let fixture: ComponentFixture; + const folderViews$ = new BehaviorSubject([]); + const open = jest.fn(); + + beforeEach(async () => { + open.mockClear(); + + await TestBed.configureTestingModule({ + imports: [FoldersV2Component], + providers: [ + { provide: PlatformUtilsService, useValue: mock() }, + { provide: ConfigService, useValue: mock() }, + { provide: LogService, useValue: mock() }, + { provide: FolderService, useValue: { folderViews$ } }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + ], + }) + .overrideComponent(FoldersV2Component, { + remove: { + imports: [PopupHeaderComponent, PopupFooterComponent], + }, + add: { + imports: [MockPopupHeaderComponent, MockPopupFooterComponent], + }, + }) + .overrideProvider(DialogService, { useValue: { open } }) + .compileComponents(); + + fixture = TestBed.createComponent(FoldersV2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + beforeEach(() => { + folderViews$.next([ + { id: "1", name: "Folder 1" }, + { id: "2", name: "Folder 2" }, + { id: "0", name: "No Folder" }, + ] as FolderView[]); + fixture.detectChanges(); + }); + + it("removes the last option in the folder array", (done) => { + component.folders$.subscribe((folders) => { + expect(folders).toEqual([ + { id: "1", name: "Folder 1" }, + { id: "2", name: "Folder 2" }, + ]); + done(); + }); + }); + + it("opens edit dialog for existing folder", () => { + const folder = { id: "1", name: "Folder 1" } as FolderView; + const editButton = fixture.debugElement.query(By.css('[data-testid="edit-folder-button"]')); + + editButton.triggerEventHandler("click"); + + expect(open).toHaveBeenCalledWith(AddEditFolderDialogComponent, { + data: { editFolderConfig: { folder } }, + }); + }); + + it("opens add dialog for new folder when there are no folders", () => { + folderViews$.next([]); + fixture.detectChanges(); + + const addButton = fixture.debugElement.query(By.css('[data-testid="empty-new-folder-button"]')); + + addButton.triggerEventHandler("click"); + + expect(open).toHaveBeenCalledWith(AddEditFolderDialogComponent, { data: {} }); + }); +}); diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.ts new file mode 100644 index 00000000000..503484410b1 --- /dev/null +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -0,0 +1,76 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { map, Observable } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { + AsyncActionsModule, + ButtonModule, + DialogService, + IconButtonModule, + Icons, +} from "@bitwarden/components"; + +import { ItemGroupComponent } from "../../../../../../libs/components/src/item/item-group.component"; +import { ItemModule } from "../../../../../../libs/components/src/item/item.module"; +import { NoItemsModule } from "../../../../../../libs/components/src/no-items/no-items.module"; +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +import { + AddEditFolderDialogComponent, + AddEditFolderDialogData, +} from "../components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component"; +import { NewItemDropdownV2Component } from "../components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component"; + +@Component({ + standalone: true, + templateUrl: "./folders-v2.component.html", + imports: [ + CommonModule, + JslibModule, + NewItemDropdownV2Component, + PopOutComponent, + PopupPageComponent, + PopupHeaderComponent, + ItemModule, + ItemGroupComponent, + NoItemsModule, + IconButtonModule, + ButtonModule, + AsyncActionsModule, + ], +}) +export class FoldersV2Component { + folders$: Observable; + + NoFoldersIcon = Icons.NoFolders; + + constructor( + private folderService: FolderService, + private dialogService: DialogService, + ) { + this.folders$ = this.folderService.folderViews$.pipe( + map((folders) => { + // Remove the last folder, which is the "no folder" option folder + if (folders.length > 0) { + return folders.slice(0, folders.length - 1); + } + + return folders; + }), + ); + } + + /** Open the Add/Edit folder dialog */ + openAddEditFolderDialog(folder?: FolderView) { + // If a folder is provided, the edit variant should be shown + const editFolderConfig = folder ? { folder } : undefined; + + this.dialogService.open(AddEditFolderDialogComponent, { + data: { editFolderConfig }, + }); + } +} diff --git a/libs/components/src/icon/icons/index.ts b/libs/components/src/icon/icons/index.ts index ea583031f61..c0a8f3bd1a6 100644 --- a/libs/components/src/icon/icons/index.ts +++ b/libs/components/src/icon/icons/index.ts @@ -3,3 +3,4 @@ export * from "./search"; export * from "./no-access"; export * from "./vault"; export * from "./no-results"; +export * from "./no-folders"; diff --git a/libs/components/src/icon/icons/no-folders.ts b/libs/components/src/icon/icons/no-folders.ts new file mode 100644 index 00000000000..ee69aac1ae9 --- /dev/null +++ b/libs/components/src/icon/icons/no-folders.ts @@ -0,0 +1,19 @@ +import { svgIcon } from "../icon"; + +export const NoFolders = svgIcon` + + + + + + + + + + + + + + + +`; From 4556e59ee8076d127567b493ff650696f4313c38 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Thu, 8 Aug 2024 15:48:23 -0400 Subject: [PATCH 15/59] [PM-10652] remove excess MP reprompt call for fill and save (#10455) --- .../item-more-options/item-more-options.component.ts | 7 ------- 1 file changed, 7 deletions(-) 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 0291d6e872d..4857703d3b1 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 @@ -76,13 +76,6 @@ export class ItemMoreOptionsComponent { } async doAutofillAndSave() { - if ( - this.cipher.reprompt === CipherRepromptType.Password && - !(await this.passwordRepromptService.showPasswordPrompt()) - ) { - return; - } - await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher); } From d212bb1fd08b24f345bbb45423260f902b8ad750 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:01:47 -0500 Subject: [PATCH 16/59] [PM-7713] Refresh Appearance Settings (#10458) * add v2 of appearance component * swap in new appearance component based on refresh flag * update default theme verbiage --- apps/browser/src/_locales/en/messages.json | 6 + apps/browser/src/popup/app-routing.module.ts | 6 +- .../settings/appearance-v2.component.html | 32 +++++ .../settings/appearance-v2.component.spec.ts | 110 ++++++++++++++++++ .../popup/settings/appearance-v2.component.ts | 110 ++++++++++++++++++ 5 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 apps/browser/src/vault/popup/settings/appearance-v2.component.html create mode 100644 apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts create mode 100644 apps/browser/src/vault/popup/settings/appearance-v2.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 6c41706ddd7..1af1cb66387 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4164,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 16e12c3d75c..b7b4e41778b 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -79,6 +79,7 @@ import { AddEditV2Component } from "../vault/popup/components/vault-v2/add-edit/ import { AssignCollections } from "../vault/popup/components/vault-v2/assign-collections/assign-collections.component"; import { AttachmentsV2Component } from "../vault/popup/components/vault-v2/attachments/attachments-v2.component"; import { ViewV2Component } from "../vault/popup/components/vault-v2/view-v2/view-v2.component"; +import { AppearanceV2Component } from "../vault/popup/settings/appearance-v2.component"; import { AppearanceComponent } from "../vault/popup/settings/appearance.component"; import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component"; import { FoldersV2Component } from "../vault/popup/settings/folders-v2.component"; @@ -339,12 +340,11 @@ const routes: Routes = [ canActivate: [authGuard], data: { state: "premium" }, }, - { + ...extensionRefreshSwap(AppearanceComponent, AppearanceV2Component, { path: "appearance", - component: AppearanceComponent, canActivate: [authGuard], data: { state: "appearance" }, - }, + }), ...extensionRefreshSwap(AddEditComponent, AddEditV2Component, { path: "clone-cipher", canActivate: [authGuard], diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.html b/apps/browser/src/vault/popup/settings/appearance-v2.component.html new file mode 100644 index 00000000000..565699a6f5b --- /dev/null +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.html @@ -0,0 +1,32 @@ + + + + + + + +
    + + + {{ "theme" | i18n }} + + + + + + + + {{ "showNumberOfAutofillSuggestions" | i18n }} + + + + + {{ "enableFavicon" | i18n }} + + +
    +
    diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts new file mode 100644 index 00000000000..69186359e2b --- /dev/null +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts @@ -0,0 +1,110 @@ +import { Component, Input } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.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 { ThemeType } from "@bitwarden/common/platform/enums"; +import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; + +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; + +import { AppearanceV2Component } from "./appearance-v2.component"; + +@Component({ + standalone: true, + selector: "popup-header", + template: ``, +}) +class MockPopupHeaderComponent { + @Input() pageTitle: string; + @Input() backAction: () => void; +} + +@Component({ + standalone: true, + selector: "popup-page", + template: ``, +}) +class MockPopupPageComponent {} + +describe("AppearanceV2Component", () => { + let component: AppearanceV2Component; + let fixture: ComponentFixture; + + const showFavicons$ = new BehaviorSubject(true); + const enableBadgeCounter$ = new BehaviorSubject(true); + const selectedTheme$ = new BehaviorSubject(ThemeType.Nord); + const setSelectedTheme = jest.fn().mockResolvedValue(undefined); + const setShowFavicons = jest.fn().mockResolvedValue(undefined); + const setEnableBadgeCounter = jest.fn().mockResolvedValue(undefined); + + beforeEach(async () => { + setSelectedTheme.mockClear(); + setShowFavicons.mockClear(); + setEnableBadgeCounter.mockClear(); + + await TestBed.configureTestingModule({ + imports: [AppearanceV2Component], + providers: [ + { provide: ConfigService, useValue: mock() }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: MessagingService, useValue: mock() }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: DomainSettingsService, useValue: { showFavicons$, setShowFavicons } }, + { + provide: BadgeSettingsServiceAbstraction, + useValue: { enableBadgeCounter$, setEnableBadgeCounter }, + }, + { provide: ThemeStateService, useValue: { selectedTheme$, setSelectedTheme } }, + ], + }) + .overrideComponent(AppearanceV2Component, { + remove: { + imports: [PopupHeaderComponent, PopupPageComponent], + }, + add: { + imports: [MockPopupHeaderComponent, MockPopupPageComponent], + }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(AppearanceV2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("populates the form with the user's current settings", () => { + expect(component.appearanceForm.value).toEqual({ + enableFavicon: true, + enableBadgeCounter: true, + theme: ThemeType.Nord, + }); + }); + + describe("form changes", () => { + it("updates the users theme", () => { + component.appearanceForm.controls.theme.setValue(ThemeType.Light); + + expect(setSelectedTheme).toHaveBeenCalledWith(ThemeType.Light); + }); + + it("updates the users favicon setting", () => { + component.appearanceForm.controls.enableFavicon.setValue(false); + + expect(setShowFavicons).toHaveBeenCalledWith(false); + }); + + it("updates the users badge counter setting", () => { + component.appearanceForm.controls.enableBadgeCounter.setValue(false); + + expect(setEnableBadgeCounter).toHaveBeenCalledWith(false); + }); + }); +}); diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts new file mode 100644 index 00000000000..7ca073d51b0 --- /dev/null +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts @@ -0,0 +1,110 @@ +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { ThemeType } from "@bitwarden/common/platform/enums"; +import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { CheckboxModule } from "@bitwarden/components"; + +import { CardComponent } from "../../../../../../libs/components/src/card/card.component"; +import { FormFieldModule } from "../../../../../../libs/components/src/form-field/form-field.module"; +import { SelectModule } from "../../../../../../libs/components/src/select/select.module"; +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; + +@Component({ + standalone: true, + templateUrl: "./appearance-v2.component.html", + imports: [ + CommonModule, + JslibModule, + PopupPageComponent, + PopupHeaderComponent, + PopOutComponent, + CardComponent, + FormFieldModule, + SelectModule, + ReactiveFormsModule, + CheckboxModule, + ], +}) +export class AppearanceV2Component implements OnInit { + appearanceForm = this.formBuilder.group({ + enableFavicon: false, + enableBadgeCounter: true, + theme: ThemeType.System, + }); + + /** Available theme options */ + themeOptions: { name: string; value: ThemeType }[]; + + constructor( + private messagingService: MessagingService, + private domainSettingsService: DomainSettingsService, + private badgeSettingsService: BadgeSettingsServiceAbstraction, + private themeStateService: ThemeStateService, + private formBuilder: FormBuilder, + private destroyRef: DestroyRef, + i18nService: I18nService, + ) { + this.themeOptions = [ + { name: i18nService.t("systemDefault"), value: ThemeType.System }, + { name: i18nService.t("light"), value: ThemeType.Light }, + { name: i18nService.t("dark"), value: ThemeType.Dark }, + { name: "Nord", value: ThemeType.Nord }, + { name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark }, + ]; + } + + async ngOnInit() { + const enableFavicon = await firstValueFrom(this.domainSettingsService.showFavicons$); + const enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$); + const theme = await firstValueFrom(this.themeStateService.selectedTheme$); + + // Set initial values for the form + this.appearanceForm.setValue({ + enableFavicon, + enableBadgeCounter, + theme, + }); + + this.appearanceForm.controls.theme.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((newTheme) => { + void this.saveTheme(newTheme); + }); + + this.appearanceForm.controls.enableFavicon.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((enableFavicon) => { + void this.updateFavicon(enableFavicon); + }); + + this.appearanceForm.controls.enableBadgeCounter.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((enableBadgeCounter) => { + void this.updateBadgeCounter(enableBadgeCounter); + }); + } + + async updateFavicon(enableFavicon: boolean) { + await this.domainSettingsService.setShowFavicons(enableFavicon); + } + + async updateBadgeCounter(enableBadgeCounter: boolean) { + await this.badgeSettingsService.setEnableBadgeCounter(enableBadgeCounter); + this.messagingService.send("bgUpdateContextMenu"); + } + + async saveTheme(newTheme: ThemeType) { + await this.themeStateService.setSelectedTheme(newTheme); + } +} From 304bd662ecf88e47a74edd969ae0d93bc1452ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Fri, 9 Aug 2024 00:55:13 +0200 Subject: [PATCH 17/59] [BRE-210] Split GitHub releases from deploy/publish (#10243) * Split web release and publish * Change publish browser to release browser * Split publish and release cli * Fix cli publish * Split publish and release desktop workflows * Add deployment status update * Fix deployment status --- .github/workflows/publish-cli.yml | 221 +++++++++++++++++++ .github/workflows/publish-desktop.yml | 296 ++++++++++++++++++++++++++ .github/workflows/publish-web.yml | 144 +++++++++++++ .github/workflows/release-browser.yml | 26 --- .github/workflows/release-cli.yml | 218 +------------------ .github/workflows/release-desktop.yml | 215 +------------------ .github/workflows/release-web.yml | 98 --------- 7 files changed, 669 insertions(+), 549 deletions(-) create mode 100644 .github/workflows/publish-cli.yml create mode 100644 .github/workflows/publish-desktop.yml create mode 100644 .github/workflows/publish-web.yml diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml new file mode 100644 index 00000000000..3f9eb7b2e42 --- /dev/null +++ b/.github/workflows/publish-cli.yml @@ -0,0 +1,221 @@ +--- +name: Publish CLI +run-name: Publish CLI ${{ inputs.publish_type }} + +on: + workflow_dispatch: + inputs: + publish_type: + description: 'Publish Options' + required: true + default: 'Initial Publish' + type: choice + options: + - Initial Publish + - Republish + - Dry Run + version: + description: 'Version to publish (default: latest cli release)' + required: true + type: string + default: latest + snap_publish: + description: 'Publish to Snap store' + required: true + default: true + type: boolean + choco_publish: + description: 'Publish to Chocolatey store' + required: true + default: true + type: boolean + npm_publish: + description: 'Publish to npm registry' + required: true + default: true + type: boolean + + +defaults: + run: + working-directory: apps/cli + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + release-version: ${{ steps.version-output.outputs.version }} + deployment-id: ${{ steps.deployment.outputs.deployment-id }} + steps: + - name: Version output + id: version-output + run: | + if [[ "${{ github.event.inputs.version }}" == "latest" || "${{ github.event.inputs.version }}" == "" ]]; then + VERSION=$(curl "https://api.github.com/repos/bitwarden/clients/releases" | jq -c '.[] | select(.tag_name | contains("cli")) | .tag_name' | head -1 | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') + echo "Latest Released Version: $VERSION" + echo "::set-output name=version::$VERSION" + else + echo "Release Version: ${{ github.event.inputs.version }}" + echo "::set-output name=version::${{ github.event.inputs.version }}" + fi + + - name: Create GitHub deployment + if: ${{ github.event.inputs.release_type != 'Dry Run' }} + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 + id: deployment + with: + token: '${{ secrets.GITHUB_TOKEN }}' + initial-status: 'in_progress' + environment: 'CLI - Production' + description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ github.ref_name }}' + task: release + + snap: + name: Deploy Snap + runs-on: ubuntu-22.04 + needs: setup + if: inputs.snap_publish + env: + _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Login to Azure + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "snapcraft-store-token" + + - name: Install Snap + uses: samuelmeuli/action-snapcraft@d33c176a9b784876d966f80fb1b461808edc0641 # v2.1.1 + + - name: Download artifacts + run: wget https://github.com/bitwarden/clients/releases/cli-v${{ env._PKG_VERSION }}/download/bw_${{ env._PKG_VERSION }}_amd64.snap + + - name: Publish Snap & logout + if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} + run: | + snapcraft upload bw_${{ env._PKG_VERSION }}_amd64.snap --release stable + snapcraft logout + + choco: + name: Deploy Choco + runs-on: windows-2022 + needs: setup + if: inputs.choco_publish + env: + _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Login to Azure + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "cli-choco-api-key" + + - name: Setup Chocolatey + run: choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/ + env: + CHOCO_API_KEY: ${{ steps.retrieve-secrets.outputs.cli-choco-api-key }} + + - name: Make dist dir + shell: pwsh + run: New-Item -ItemType directory -Path ./dist + + - name: Download artifacts + run: wget https://github.com/bitwarden/clients/releases/cli-v${{ env._PKG_VERSION }}/download/bitwarden-cli.${{ env._PKG_VERSION }}.nupkg + + - name: Push to Chocolatey + if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + shell: pwsh + run: | + cd dist + choco push --source=https://push.chocolatey.org/ + + npm: + name: Publish NPM + runs-on: ubuntu-22.04 + needs: setup + if: inputs.npm_publish + env: + _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Login to Azure + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "npm-api-key" + + - name: Download artifacts + run: wget https://github.com/bitwarden/clients/releases/cli-v${{ env._PKG_VERSION }}/download/bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip + + - name: Setup NPM + run: | + echo 'registry="https://registry.npmjs.org/"' > ./.npmrc + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc + env: + NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.npm-api-key }} + + - name: Install Husky + run: npm install -g husky + + - name: Publish NPM + if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + run: npm publish --access public --regsitry=https://registry.npmjs.org/ --userconfig=./.npmrc + + update-deployment: + name: Update Deployment Status + runs-on: ubuntu-22.04 + needs: + - setup + - npm + - snap + - choco + if: ${{ always() && github.event.inputs.publish_type != 'Dry Run' }} + steps: + - name: Check if any job failed + if: contains(needs.*.result, 'failure') + run: exit 1 + + - name: Update deployment status to Success + if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: '${{ secrets.GITHUB_TOKEN }}' + state: 'success' + deployment-id: ${{ needs.setup.outputs.deployment-id }} + + - name: Update deployment status to Failure + if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: '${{ secrets.GITHUB_TOKEN }}' + state: 'failure' + deployment-id: ${{ needs.setup.outputs.deployment-id }} \ No newline at end of file diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml new file mode 100644 index 00000000000..2c4e467bc2a --- /dev/null +++ b/.github/workflows/publish-desktop.yml @@ -0,0 +1,296 @@ +--- +name: Publish Desktop +run-name: Publish Desktop ${{ inputs.publish_type }} + +on: + workflow_dispatch: + inputs: + publish_type: + description: 'Publish Options' + required: true + default: 'Initial Publish' + type: choice + options: + - Initial Publish + - Republish + - Dry Run + version: + description: 'Version to publish (default: latest cli release)' + required: true + type: string + default: latest + rollout_percentage: + description: 'Staged Rollout Percentage' + required: true + default: '10' + type: string + snap_publish: + description: 'Publish to Snap store' + required: true + default: true + type: boolean + choco_publish: + description: 'Publish to Chocolatey store' + required: true + default: true + type: boolean + +defaults: + run: + shell: bash + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + release-version: ${{ steps.version.outputs.version }} + release-channel: ${{ steps.release-channel.outputs.channel }} + tag-name: ${{ steps.version.outputs.tag_name }} + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + steps: + - name: Check Publish Version + id: version + run: | + if [[ "${{ github.event.inputs.version }}" == "latest" || "${{ github.event.inputs.version }}" == "" ]]; then + TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/clients/releases" | jq -c '.[] | select(.tag_name | contains("desktop")) | .tag_name' | head -1 | cut -d '"' -f 2) + VERSION=$(echo $TAG_NAME | sed "s/desktop-v//") + echo "Latest Released Version: $VERSION" + echo "::set-output name=version::$VERSION" + + echo "Tag name: $TAG_NAME" + echo "::set-output name=tag_name::$TAG_NAME" + else + echo "Release Version: ${{ github.event.inputs.version }}" + echo "::set-output name=version::${{ github.event.inputs.version }}" + + $TAG_NAME="desktop-v${{ github.event.inputs.version }}" + + echo "Tag name: $TAG_NAME" + echo "::set-output name=tag_name::$TAG_NAME" + fi + + - name: Get Version Channel + id: release-channel + run: | + case "${{ steps.version.outputs.version }}" in + *"alpha"*) + echo "channel=alpha" >> $GITHUB_OUTPUT + echo "[!] We do not yet support 'alpha'" + exit 1 + ;; + *"beta"*) + echo "channel=beta" >> $GITHUB_OUTPUT + ;; + *) + echo "channel=latest" >> $GITHUB_OUTPUT + ;; + esac + + - name: Create GitHub deployment + if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 + id: deployment + with: + token: '${{ secrets.GITHUB_TOKEN }}' + initial-status: 'in_progress' + environment: 'Desktop - Production' + description: 'Deployment ${{ steps.version.outputs.version }} to channel ${{ steps.release-channel.outputs.channel }} from branch ${{ github.ref_name }}' + task: release + + electron-blob: + name: Electron blob publish + runs-on: ubuntu-22.04 + env: + _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} + steps: + - name: Login to Azure + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "aws-electron-access-id, + aws-electron-access-key, + aws-electron-bucket-name" + + - name: Download all artifacts + if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-desktop.yml + workflow_conclusion: success + branch: ${{ github.ref_name }} + path: apps/desktop/artifacts + + - name: Download artifacts + working-directory: apps/desktop/artifacts + run: gh release download ${{ env._RELEASE_TAG }} -R bitwarden/desktop + + - name: Set staged rollout percentage + env: + RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} + ROLLOUT_PCT: ${{ inputs.rollout_percentage }} + run: | + echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}.yml + echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-linux.yml + echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml + + - name: Publish artifacts to S3 + if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + env: + AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }} + AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }} + AWS_DEFAULT_REGION: 'us-west-2' + AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }} + working-directory: apps/desktop/artifacts + run: | + aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \ + --acl "public-read" \ + --recursive \ + --quiet + + - name: Update deployment status to Success + if: ${{ github.event.inputs.publish_type != 'Dry Run' && success() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: '${{ secrets.GITHUB_TOKEN }}' + state: 'success' + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + + - name: Update deployment status to Failure + if: ${{ github.event.inputs.publish_type != 'Dry Run' && failure() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: '${{ secrets.GITHUB_TOKEN }}' + state: 'failure' + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + + snap: + name: Deploy Snap + runs-on: ubuntu-22.04 + needs: setup + if: ${{ github.event.inputs.snap_publish == 'true' }} + env: + _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Login to Azure + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "snapcraft-store-token" + + - name: Install Snap + uses: samuelmeuli/action-snapcraft@d33c176a9b784876d966f80fb1b461808edc0641 # v2.1.1 + + - name: Setup + run: mkdir dist + working-directory: apps/desktop + + - name: Download artifacts + working-directory: apps/desktop/dist + run: wget https://github.com/bitwarden/clients/releases/${{ env._RELEASE_TAG }}/download/bitwarden_${{ env._PKG_VERSION }}_amd64.snap + + - name: Deploy to Snap Store + if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} + run: | + snapcraft upload bitwarden_${{ env._PKG_VERSION }}_amd64.snap --release stable + snapcraft logout + working-directory: apps/desktop/dist + + choco: + name: Deploy Choco + runs-on: windows-2022 + needs: setup + if: ${{ github.event.inputs.choco_publish == 'true' }} + env: + _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Print Environment + run: | + dotnet --version + dotnet nuget --version + + - name: Login to Azure + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "cli-choco-api-key" + + - name: Setup Chocolatey + shell: pwsh + run: choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/ + env: + CHOCO_API_KEY: ${{ steps.retrieve-secrets.outputs.cli-choco-api-key }} + + - name: Make dist dir + shell: pwsh + run: New-Item -ItemType directory -Path ./dist + working-directory: apps/desktop + + - name: Download artifacts + working-directory: apps/desktop/dist + run: wget https://github.com/bitwarden/clients/releases/${{ env._RELEASE_TAG }}/download/bitwarden.${{ env._PKG_VERSION }}.nupkg + + - name: Push to Chocolatey + if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + shell: pwsh + run: choco push --source=https://push.chocolatey.org/ + working-directory: apps/desktop/dist + + update-deployment: + name: Update Deployment Status + runs-on: ubuntu-22.04 + needs: + - setup + - electron-blob + - snap + - choco + if: ${{ always() && github.event.inputs.publish_type != 'Dry Run' }} + steps: + - name: Check if any job failed + if: contains(needs.*.result, 'failure') + run: exit 1 + + - name: Update deployment status to Success + if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: '${{ secrets.GITHUB_TOKEN }}' + state: 'success' + deployment-id: ${{ needs.setup.outputs.deployment-id }} + + - name: Update deployment status to Failure + if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: '${{ secrets.GITHUB_TOKEN }}' + state: 'failure' + deployment-id: ${{ needs.setup.outputs.deployment-id }} diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml new file mode 100644 index 00000000000..733e3945e5f --- /dev/null +++ b/.github/workflows/publish-web.yml @@ -0,0 +1,144 @@ +--- +name: Publish Web +run-name: Publish Web ${{ inputs.publish_type }} + +on: + workflow_dispatch: + inputs: + publish_type: + description: 'Publish Options' + required: true + default: 'Initial Publish' + type: choice + options: + - Initial Publish + - Redeploy + - Dry Run + +env: + _AZ_REGISTRY: bitwardenprod.azurecr.io + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + release_version: ${{ steps.version.outputs.version }} + tag_version: ${{ steps.version.outputs.tag }} + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Branch check + if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc-web" ]]; then + echo "===================================" + echo "[!] Can only release from the 'rc' or 'hotfix-rc-web' branches" + echo "===================================" + exit 1 + fi + + - name: Check Release Version + id: version + uses: bitwarden/gh-actions/release-version-check@main + with: + release-type: ${{ github.event.inputs.publish_type }} + project-type: ts + file: apps/web/package.json + monorepo: true + monorepo-project: web + + self-host: + name: Release self-host docker + runs-on: ubuntu-22.04 + needs: setup + env: + _BRANCH_NAME: ${{ github.ref_name }} + _RELEASE_VERSION: ${{ needs.setup.outputs.release_version }} + _RELEASE_OPTION: ${{ github.event.inputs.publish_type }} + steps: + - name: Print environment + run: | + whoami + docker --version + echo "GitHub ref: $GITHUB_REF" + echo "GitHub event: $GITHUB_EVENT" + echo "Github Release Option: $_RELEASE_OPTION" + + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + ########## ACR ########## + - name: Login to Azure - PROD Subscription + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} + + - name: Login to Azure ACR + run: az acr login -n bitwardenprod + + - name: Create GitHub deployment + if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 + id: deployment + with: + token: '${{ secrets.GITHUB_TOKEN }}' + initial-status: 'in_progress' + environment-url: http://vault.bitwarden.com + environment: 'Web Vault - US Production Cloud' + description: 'Deployment ${{ needs.setup.outputs.release_version }} from branch ${{ github.ref_name }}' + task: release + + - name: Pull branch image + run: | + if [[ "${{ github.event.inputs.publish_type }}" == "Dry Run" ]]; then + docker pull $_AZ_REGISTRY/web:latest + else + docker pull $_AZ_REGISTRY/web:$_BRANCH_NAME + fi + + - name: Tag version + run: | + if [[ "${{ github.event.inputs.publish_type }}" == "Dry Run" ]]; then + docker tag $_AZ_REGISTRY/web:latest $_AZ_REGISTRY/web:dryrun + docker tag $_AZ_REGISTRY/web:latest $_AZ_REGISTRY/web-sh:dryrun + else + docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web:$_RELEASE_VERSION + docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web-sh:$_RELEASE_VERSION + docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web:latest + docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web-sh:latest + fi + + - name: Push version + run: | + if [[ "${{ github.event.inputs.publish_type }}" == "Dry Run" ]]; then + docker push $_AZ_REGISTRY/web:dryrun + docker push $_AZ_REGISTRY/web-sh:dryrun + else + docker push $_AZ_REGISTRY/web:$_RELEASE_VERSION + docker push $_AZ_REGISTRY/web-sh:$_RELEASE_VERSION + docker push $_AZ_REGISTRY/web:latest + docker push $_AZ_REGISTRY/web-sh:latest + fi + + - name: Update deployment status to Success + if: ${{ github.event.inputs.publish_type != 'Dry Run' && success() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: '${{ secrets.GITHUB_TOKEN }}' + environment-url: http://vault.bitwarden.com + state: 'success' + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + + - name: Update deployment status to Failure + if: ${{ github.event.inputs.publish_type != 'Dry Run' && failure() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: '${{ secrets.GITHUB_TOKEN }}' + environment-url: http://vault.bitwarden.com + state: 'failure' + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + + - name: Log out of Docker + run: docker logout diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index 68c33ca358e..3feaff8cede 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -91,16 +91,6 @@ jobs: - setup - locales-test steps: - - name: Create GitHub deployment - uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 - id: deployment - with: - token: '${{ secrets.GITHUB_TOKEN }}' - initial-status: 'in_progress' - environment: 'Browser - Production' - description: 'Deployment ${{ needs.setup.outputs.release-version }} from branch ${{ github.ref_name }}' - task: release - - name: Download latest Release build artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main @@ -152,19 +142,3 @@ jobs: body: "" token: ${{ secrets.GITHUB_TOKEN }} draft: true - - - name: Update deployment status to Success - if: ${{ success() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: '${{ secrets.GITHUB_TOKEN }}' - state: 'success' - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - - name: Update deployment status to Failure - if: ${{ failure() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: '${{ secrets.GITHUB_TOKEN }}' - state: 'failure' - deployment-id: ${{ steps.deployment.outputs.deployment_id }} diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 6d56c3be831..fe402e7a8f1 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -14,22 +14,6 @@ on: - Initial Release - Redeploy - Dry Run - snap_publish: - description: 'Publish to Snap store' - required: true - default: true - type: boolean - choco_publish: - description: 'Publish to Chocolatey store' - required: true - default: true - type: boolean - npm_publish: - description: 'Publish to npm registry' - required: true - default: true - type: boolean - defaults: run: @@ -65,17 +49,11 @@ jobs: monorepo: true monorepo-project: cli - - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 - id: deployment - with: - token: '${{ secrets.GITHUB_TOKEN }}' - initial-status: 'in_progress' - environment: 'CLI - Production' - description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ github.ref_name }}' - task: release - + release: + name: Release + runs-on: ubuntu-22.04 + needs: setup + steps: - name: Download all Release artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main @@ -121,189 +99,3 @@ jobs: body: "" token: ${{ secrets.GITHUB_TOKEN }} draft: true - - - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: '${{ secrets.GITHUB_TOKEN }}' - state: 'success' - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: '${{ secrets.GITHUB_TOKEN }}' - state: 'failure' - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - snap: - name: Deploy Snap - runs-on: ubuntu-22.04 - needs: setup - if: inputs.snap_publish - env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - steps: - - name: Checkout repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "snapcraft-store-token" - - - name: Install Snap - uses: samuelmeuli/action-snapcraft@d33c176a9b784876d966f80fb1b461808edc0641 # v2.1.1 - - - name: Download artifacts - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-cli.yml - path: apps/cli - workflow_conclusion: success - branch: ${{ github.ref_name }} - artifacts: bw_${{ env._PKG_VERSION }}_amd64.snap - - - name: Dry Run - Download artifacts - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-cli.yml - path: apps/cli - workflow_conclusion: success - branch: main - artifacts: bw_${{ env._PKG_VERSION }}_amd64.snap - - - name: Publish Snap & logout - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - env: - SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} - run: | - snapcraft upload bw_${{ env._PKG_VERSION }}_amd64.snap --release stable - snapcraft logout - - choco: - name: Deploy Choco - runs-on: windows-2022 - needs: setup - if: inputs.choco_publish - env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - steps: - - name: Checkout repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "cli-choco-api-key" - - - name: Setup Chocolatey - run: choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/ - env: - CHOCO_API_KEY: ${{ steps.retrieve-secrets.outputs.cli-choco-api-key }} - - - name: Make dist dir - shell: pwsh - run: New-Item -ItemType directory -Path ./dist - - - name: Download artifacts - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-cli.yml - path: apps/cli/dist - workflow_conclusion: success - branch: ${{ github.ref_name }} - artifacts: bitwarden-cli.${{ env._PKG_VERSION }}.nupkg - - - name: Dry Run - Download artifacts - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-cli.yml - path: apps/cli/dist - workflow_conclusion: success - branch: main - artifacts: bitwarden-cli.${{ env._PKG_VERSION }}.nupkg - - - name: Push to Chocolatey - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - shell: pwsh - run: | - cd dist - choco push --source=https://push.chocolatey.org/ - - npm: - name: Publish NPM - runs-on: ubuntu-22.04 - needs: setup - if: inputs.npm_publish - env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - steps: - - name: Checkout repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "npm-api-key" - - - name: Download artifacts - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-cli.yml - path: apps/cli/build - workflow_conclusion: success - branch: ${{ github.ref_name }} - artifacts: bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip - - - name: Dry Run - Download artifacts - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-cli.yml - path: apps/cli/build - workflow_conclusion: success - branch: main - artifacts: bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip - - - name: Setup NPM - run: | - echo 'registry="https://registry.npmjs.org/"' > ./.npmrc - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc - env: - NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.npm-api-key }} - - - name: Install Husky - run: npm install -g husky - - - name: Publish NPM - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - run: npm publish --access public --regsitry=https://registry.npmjs.org/ --userconfig=./.npmrc diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index eb63a53f2ea..c9e1df94026 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -6,34 +6,13 @@ on: workflow_dispatch: inputs: release_type: - description: 'Release Options' + description: 'Release Type' required: true - default: 'Initial Release' + default: 'Release' type: choice options: - - Initial Release - - Redeploy + - Release - Dry Run - rollout_percentage: - description: 'Staged Rollout Percentage' - required: true - default: '10' - type: string - snap_publish: - description: 'Publish to Snap store' - required: true - default: true - type: boolean - choco_publish: - description: 'Publish to Chocolatey store' - required: true - default: true - type: boolean - github_release: - description: 'Publish GitHub release' - required: true - default: true - type: boolean defaults: run: @@ -87,31 +66,6 @@ jobs: ;; esac - - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 - id: deployment - with: - token: '${{ secrets.GITHUB_TOKEN }}' - initial-status: 'in_progress' - environment: 'Desktop - Production' - description: 'Deployment ${{ steps.version.outputs.version }} to channel ${{ steps.release-channel.outputs.channel }} from branch ${{ github.ref_name }}' - task: release - - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "aws-electron-access-id, - aws-electron-access-key, - aws-electron-bucket-name" - - name: Download all artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main @@ -136,29 +90,6 @@ jobs: working-directory: apps/desktop/artifacts run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive - - name: Set staged rollout percentage - env: - RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} - ROLLOUT_PCT: ${{ inputs.rollout_percentage }} - run: | - echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}.yml - echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-linux.yml - echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml - - - name: Publish artifacts to S3 - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - env: - AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }} - AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }} - AWS_DEFAULT_REGION: 'us-west-2' - AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }} - working-directory: apps/desktop/artifacts - run: | - aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \ - --acl "public-read" \ - --recursive \ - --quiet - - name: Get checksum files uses: bitwarden/gh-actions/get-checksum@main with: @@ -203,143 +134,3 @@ jobs: body: "" token: ${{ secrets.GITHUB_TOKEN }} draft: true - - - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: '${{ secrets.GITHUB_TOKEN }}' - state: 'success' - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: '${{ secrets.GITHUB_TOKEN }}' - state: 'failure' - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - snap: - name: Deploy Snap - runs-on: ubuntu-22.04 - needs: setup - if: ${{ github.event.inputs.snap_publish == 'true' }} - env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - steps: - - name: Checkout Repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "snapcraft-store-token" - - - name: Install Snap - uses: samuelmeuli/action-snapcraft@d33c176a9b784876d966f80fb1b461808edc0641 # v2.1.1 - - - name: Setup - run: mkdir dist - working-directory: apps/desktop - - - name: Download Snap artifact - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-desktop.yml - workflow_conclusion: success - branch: ${{ github.ref_name }} - artifacts: bitwarden_${{ env._PKG_VERSION }}_amd64.snap - path: apps/desktop/dist - - - name: Dry Run - Download Snap artifact - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-desktop.yml - workflow_conclusion: success - branch: main - artifacts: bitwarden_${{ env._PKG_VERSION }}_amd64.snap - path: apps/desktop/dist - - - name: Deploy to Snap Store - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - env: - SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} - run: | - snapcraft upload bitwarden_${{ env._PKG_VERSION }}_amd64.snap --release stable - snapcraft logout - working-directory: apps/desktop/dist - - choco: - name: Deploy Choco - runs-on: windows-2022 - needs: setup - if: ${{ github.event.inputs.choco_publish == 'true' }} - env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - steps: - - name: Checkout Repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Print Environment - run: | - dotnet --version - dotnet nuget --version - - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "cli-choco-api-key" - - - name: Setup Chocolatey - shell: pwsh - run: choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/ - env: - CHOCO_API_KEY: ${{ steps.retrieve-secrets.outputs.cli-choco-api-key }} - - - name: Make dist dir - shell: pwsh - run: New-Item -ItemType directory -Path ./dist - working-directory: apps/desktop - - - name: Download choco artifact - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-desktop.yml - workflow_conclusion: success - branch: ${{ github.ref_name }} - artifacts: bitwarden.${{ env._PKG_VERSION }}.nupkg - path: apps/desktop/dist - - - name: Dry Run - Download choco artifact - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-desktop.yml - workflow_conclusion: success - branch: main - artifacts: bitwarden.${{ env._PKG_VERSION }}.nupkg - path: apps/desktop/dist - - - name: Push to Chocolatey - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - shell: pwsh - run: choco push --source=https://push.chocolatey.org/ - working-directory: apps/desktop/dist diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 2da6daaa19c..596341459cd 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -15,9 +15,6 @@ on: - Redeploy - Dry Run -env: - _AZ_REGISTRY: bitwardenprod.azurecr.io - jobs: setup: name: Setup @@ -49,89 +46,12 @@ jobs: monorepo: true monorepo-project: web - self-host: - name: Release self-host docker - runs-on: ubuntu-22.04 - needs: setup - env: - _BRANCH_NAME: ${{ github.ref_name }} - _RELEASE_VERSION: ${{ needs.setup.outputs.release_version }} - _RELEASE_OPTION: ${{ github.event.inputs.release_type }} - steps: - - name: Print environment - run: | - whoami - docker --version - echo "GitHub ref: $GITHUB_REF" - echo "GitHub event: $GITHUB_EVENT" - echo "Github Release Option: $_RELEASE_OPTION" - - - name: Checkout repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - ########## ACR ########## - - name: Login to Azure - PROD Subscription - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - - name: Login to Azure ACR - run: az acr login -n bitwardenprod - - - name: Pull branch image - run: | - if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then - docker pull $_AZ_REGISTRY/web:latest - else - docker pull $_AZ_REGISTRY/web:$_BRANCH_NAME - fi - - - name: Tag version - run: | - if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then - docker tag $_AZ_REGISTRY/web:latest $_AZ_REGISTRY/web:dryrun - docker tag $_AZ_REGISTRY/web:latest $_AZ_REGISTRY/web-sh:dryrun - else - docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web:$_RELEASE_VERSION - docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web-sh:$_RELEASE_VERSION - docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web:latest - docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web-sh:latest - fi - - - name: Push version - run: | - if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then - docker push $_AZ_REGISTRY/web:dryrun - docker push $_AZ_REGISTRY/web-sh:dryrun - else - docker push $_AZ_REGISTRY/web:$_RELEASE_VERSION - docker push $_AZ_REGISTRY/web-sh:$_RELEASE_VERSION - docker push $_AZ_REGISTRY/web:latest - docker push $_AZ_REGISTRY/web-sh:latest - fi - - - name: Log out of Docker - run: docker logout - release: name: Create GitHub Release runs-on: ubuntu-22.04 needs: - setup - - self-host steps: - - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 - id: deployment - with: - token: '${{ secrets.GITHUB_TOKEN }}' - initial-status: 'in_progress' - environment-url: http://vault.bitwarden.com - environment: 'Web Vault - US Production Cloud' - description: 'Deployment ${{ needs.setup.outputs.release_version }} from branch ${{ github.ref_name }}' - task: release - - name: Download latest build artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main @@ -172,21 +92,3 @@ jobs: apps/web/artifacts/web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip" token: ${{ secrets.GITHUB_TOKEN }} draft: true - - - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: '${{ secrets.GITHUB_TOKEN }}' - environment-url: http://vault.bitwarden.com - state: 'success' - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: '${{ secrets.GITHUB_TOKEN }}' - environment-url: http://vault.bitwarden.com - state: 'failure' - deployment-id: ${{ steps.deployment.outputs.deployment_id }} From 2b69ccda40c74beb6edb06d12f62d222cbc45781 Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Thu, 8 Aug 2024 23:45:47 -0500 Subject: [PATCH 18/59] [PM-8655] Update web app new item button (#10354) * Add additional extension refresh menu behind feature flag. * Open new cipher dialog with proper cipher type selected. * Adjust onboarding copy and default to login cipher. * Update "New item" button styles. * Add test to ensure onboarding component always calls onAddCipher.emit with the login cipher type. * Hide onboarding and new item changes behind feature flag * Fix missing mock in test. * Remove extensionRefreshEnabled$ and conditional styles from the "add new" button. * Remove rounding class from menu "new" button. --- .../vault-header/vault-header.component.html | 102 +++++++++++++----- .../vault-header/vault-header.component.ts | 12 ++- .../vault-onboarding.component.html | 7 +- .../vault-onboarding.component.spec.ts | 15 +++ .../vault-onboarding.component.ts | 12 ++- .../individual-vault/vault.component.html | 10 +- .../vault/individual-vault/vault.component.ts | 6 +- apps/web/src/locales/en/messages.json | 7 ++ 8 files changed, 135 insertions(+), 36 deletions(-) diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html index b0ae308ea86..3f46cb803cf 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html @@ -69,30 +69,84 @@
    - - - - - - + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index 7803b1c32f2..ad07d2847e6 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -14,6 +14,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { BreadcrumbsModule, MenuModule } from "@bitwarden/components"; @@ -47,6 +48,8 @@ export class VaultHeaderComponent implements OnInit { protected Unassigned = Unassigned; protected All = All; protected CollectionDialogTabType = CollectionDialogTabType; + protected CipherType = CipherType; + protected extensionRefreshEnabled = false; /** * Boolean to determine the loading state of the header. @@ -67,7 +70,7 @@ export class VaultHeaderComponent implements OnInit { @Input() canCreateCollections: boolean; /** Emits an event when the new item button is clicked in the header */ - @Output() onAddCipher = new EventEmitter(); + @Output() onAddCipher = new EventEmitter(); /** Emits an event when the new collection button is clicked in the 'New' dropdown menu */ @Output() onAddCollection = new EventEmitter(); @@ -92,6 +95,9 @@ export class VaultHeaderComponent implements OnInit { this.flexibleCollectionsV1Enabled = await firstValueFrom( this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1), ); + this.extensionRefreshEnabled = await firstValueFrom( + this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), + ); } /** @@ -199,8 +205,8 @@ export class VaultHeaderComponent implements OnInit { this.onDeleteCollection.emit(); } - protected addCipher() { - this.onAddCipher.emit(); + protected addCipher(cipherType?: CipherType) { + this.onAddCipher.emit(cipherType); } async addFolder(): Promise { diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html index 9f6f589df63..b9647e3237d 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html @@ -22,7 +22,12 @@

    {{ "onboardingImportDataDetailsPartOne" | i18n }} {{ "onboardingImportDataDetailsPartTwoNoOrgs" | i18n }} diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts index 490c07d7538..778132676fa 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts @@ -5,9 +5,11 @@ import { Subject, of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { 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 { StateProvider } from "@bitwarden/common/platform/state"; +import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum"; import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./services/abstraction/vault-onboarding.service"; @@ -24,6 +26,7 @@ describe("VaultOnboardingComponent", () => { let mockStateProvider: Partial; let setInstallExtLinkSpy: any; let individualVaultPolicyCheckSpy: any; + let mockConfigService: MockProxy; beforeEach(() => { mockPolicyService = mock(); @@ -42,6 +45,7 @@ describe("VaultOnboardingComponent", () => { }), ), }; + mockConfigService = mock(); // eslint-disable-next-line @typescript-eslint/no-floating-promises TestBed.configureTestingModule({ @@ -54,6 +58,7 @@ describe("VaultOnboardingComponent", () => { { provide: I18nService, useValue: mockI18nService }, { provide: ApiService, useValue: mockApiService }, { provide: StateProvider, useValue: mockStateProvider }, + { provide: ConfigService, useValue: mockConfigService }, ], }).compileComponents(); fixture = TestBed.createComponent(VaultOnboardingComponent); @@ -178,4 +183,14 @@ describe("VaultOnboardingComponent", () => { expect(saveCompletedTasksSpy).toHaveBeenCalled(); }); }); + + describe("emitToAddCipher", () => { + it("always emits the `CipherType.Login` type when called", () => { + const emitSpy = jest.spyOn(component.onAddCipher, "emit"); + + component.emitToAddCipher(); + + expect(emitSpy).toHaveBeenCalledWith(CipherType.Login); + }); + }); }); diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts index a7331c73151..94ae1a4df47 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts @@ -16,7 +16,10 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { LinkModule } from "@bitwarden/components"; @@ -41,7 +44,7 @@ import { VaultOnboardingService, VaultOnboardingTasks } from "./services/vault-o export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { @Input() ciphers: CipherView[]; @Input() orgs: Organization[]; - @Output() onAddCipher = new EventEmitter(); + @Output() onAddCipher = new EventEmitter(); extensionUrl: string; isIndividualPolicyVault: boolean; @@ -53,12 +56,14 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { protected onboardingTasks$: Observable; protected showOnboarding = false; + protected extensionRefreshEnabled = false; constructor( protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService, private apiService: ApiService, private vaultOnboardingService: VaultOnboardingServiceAbstraction, + private configService: ConfigService, ) {} async ngOnInit() { @@ -67,6 +72,9 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { this.setInstallExtLink(); this.individualVaultPolicyCheck(); this.checkForBrowserExtension(); + this.extensionRefreshEnabled = await this.configService.getFeatureFlag( + FeatureFlag.ExtensionRefresh, + ); } async ngOnChanges(changes: SimpleChanges) { @@ -162,7 +170,7 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { } emitToAddCipher() { - this.onAddCipher.emit(); + this.onAddCipher.emit(CipherType.Login); } setInstallExtLink() { 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 f0be76018f7..183c4f65afd 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.html +++ b/apps/web/src/app/vault/individual-vault/vault.component.html @@ -6,14 +6,18 @@ [organizations]="allOrganizations" [canCreateCollections]="canCreateCollections" [collection]="selectedCollection" - (onAddCipher)="addCipher()" + (onAddCipher)="addCipher($event)" (onAddCollection)="addCollection()" (onAddFolder)="addFolder()" (onEditCollection)="editCollection(selectedCollection.node, $event.tab)" (onDeleteCollection)="deleteCollection(selectedCollection.node)" > - +

    @@ -80,7 +84,7 @@ (click)="addCipher()" *ngIf="filter.type !== 'trash'" > - + {{ "newItem" | i18n }}
    diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 6aca5662e53..1b9d0e1b629 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -50,6 +50,7 @@ import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; @@ -163,7 +164,6 @@ export class VaultComponent implements OnInit, OnDestroy { protected vaultBulkManagementActionEnabled$ = this.configService.getFeatureFlag$( FeatureFlag.VaultBulkManagementAction, ); - private searchText$ = new Subject(); private refresh$ = new BehaviorSubject(null); private destroy$ = new Subject(); @@ -586,9 +586,9 @@ export class VaultComponent implements OnInit, OnDestroy { } } - async addCipher() { + async addCipher(cipherType?: CipherType) { const component = await this.editCipher(null); - component.type = this.activeFilter.cipherType; + component.type = cipherType || this.activeFilter.cipherType; if (this.activeFilter.organizationId !== "MyVault") { component.organizationId = this.activeFilter.organizationId; component.collections = ( diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 5f35c1c3e59..fd2924badb4 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -36,6 +36,9 @@ "notes": { "message": "Notes" }, + "note": { + "message": "Note" + }, "customFields": { "message": "Custom fields" }, @@ -1505,6 +1508,10 @@ "message": "new item", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, + "onboardingImportDataDetailsLoginLink": { + "message": "new login", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" + }, "onboardingImportDataDetailsPartTwoNoOrgs": { "message": " instead.", "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." From 795c21a1c78e83697ecfa5e4db8d6f0441927650 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:00:36 +0000 Subject: [PATCH 19/59] Autosync the updated translations (#10460) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 24 ++++ apps/desktop/src/locales/ar/messages.json | 24 ++++ apps/desktop/src/locales/az/messages.json | 24 ++++ apps/desktop/src/locales/be/messages.json | 24 ++++ apps/desktop/src/locales/bg/messages.json | 24 ++++ apps/desktop/src/locales/bn/messages.json | 24 ++++ apps/desktop/src/locales/bs/messages.json | 24 ++++ apps/desktop/src/locales/ca/messages.json | 80 ++++++++----- apps/desktop/src/locales/cs/messages.json | 24 ++++ apps/desktop/src/locales/cy/messages.json | 24 ++++ apps/desktop/src/locales/da/messages.json | 24 ++++ apps/desktop/src/locales/de/messages.json | 24 ++++ apps/desktop/src/locales/el/messages.json | 76 ++++++++---- apps/desktop/src/locales/en_GB/messages.json | 24 ++++ apps/desktop/src/locales/en_IN/messages.json | 24 ++++ apps/desktop/src/locales/eo/messages.json | 24 ++++ apps/desktop/src/locales/es/messages.json | 24 ++++ apps/desktop/src/locales/et/messages.json | 24 ++++ apps/desktop/src/locales/eu/messages.json | 24 ++++ apps/desktop/src/locales/fa/messages.json | 24 ++++ apps/desktop/src/locales/fi/messages.json | 24 ++++ apps/desktop/src/locales/fil/messages.json | 24 ++++ apps/desktop/src/locales/fr/messages.json | 24 ++++ apps/desktop/src/locales/gl/messages.json | 24 ++++ apps/desktop/src/locales/he/messages.json | 24 ++++ apps/desktop/src/locales/hi/messages.json | 24 ++++ apps/desktop/src/locales/hr/messages.json | 26 +++- apps/desktop/src/locales/hu/messages.json | 24 ++++ apps/desktop/src/locales/id/messages.json | 24 ++++ apps/desktop/src/locales/it/messages.json | 24 ++++ apps/desktop/src/locales/ja/messages.json | 24 ++++ apps/desktop/src/locales/ka/messages.json | 24 ++++ apps/desktop/src/locales/km/messages.json | 24 ++++ apps/desktop/src/locales/kn/messages.json | 24 ++++ apps/desktop/src/locales/ko/messages.json | 24 ++++ apps/desktop/src/locales/lt/messages.json | 24 ++++ apps/desktop/src/locales/lv/messages.json | 24 ++++ apps/desktop/src/locales/me/messages.json | 24 ++++ apps/desktop/src/locales/ml/messages.json | 24 ++++ apps/desktop/src/locales/mr/messages.json | 24 ++++ apps/desktop/src/locales/my/messages.json | 24 ++++ apps/desktop/src/locales/nb/messages.json | 40 +++++-- apps/desktop/src/locales/ne/messages.json | 24 ++++ apps/desktop/src/locales/nl/messages.json | 66 +++++++---- apps/desktop/src/locales/nn/messages.json | 24 ++++ apps/desktop/src/locales/or/messages.json | 24 ++++ apps/desktop/src/locales/pl/messages.json | 24 ++++ apps/desktop/src/locales/pt_BR/messages.json | 118 +++++++++++-------- apps/desktop/src/locales/pt_PT/messages.json | 24 ++++ apps/desktop/src/locales/ro/messages.json | 24 ++++ apps/desktop/src/locales/ru/messages.json | 24 ++++ apps/desktop/src/locales/si/messages.json | 24 ++++ apps/desktop/src/locales/sk/messages.json | 24 ++++ apps/desktop/src/locales/sl/messages.json | 24 ++++ apps/desktop/src/locales/sr/messages.json | 24 ++++ apps/desktop/src/locales/sv/messages.json | 24 ++++ apps/desktop/src/locales/te/messages.json | 24 ++++ apps/desktop/src/locales/th/messages.json | 24 ++++ apps/desktop/src/locales/tr/messages.json | 24 ++++ apps/desktop/src/locales/uk/messages.json | 38 ++++-- apps/desktop/src/locales/vi/messages.json | 24 ++++ apps/desktop/src/locales/zh_CN/messages.json | 26 +++- apps/desktop/src/locales/zh_TW/messages.json | 24 ++++ 63 files changed, 1651 insertions(+), 139 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index f51b07b0a7f..84b45245c42 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Bykomende Windows Hello-instellings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Bevestig vir Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Ontgrendel met Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Vra vir Windows Hello by lansering" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Vra vir Touch ID by lansering" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Vir blaaierbiometrie moet werkskermbiometrie eers in instellings geaktiveer wees." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Weens ’n ondernemingsbeleid mag u geen wagwoorde in u persoonlike kluis bewaar nie. Verander die eienaarskap na ’n organisasie en kies uit ’n van die beskikbare versamelings." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "U hoofwagwoord voldoen nie aan een of meer van die organisasiebeleide nie. Om toegang tot die kluis te kry, moet u nou u hoofwagwoord bywerk. Deur voort te gaan sal u van u huidige sessie afgeteken word, en u sal weer moet aanteken. Aktiewe sessies op ander toestelle kan vir tot een uur aktief bly." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 3b6957fccdb..27cb1e1ba1f 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "إعدادات Windows Hello إضافية" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "تحقق من Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "فتح بواسطة معرف اللمس" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "اسأل عن Windows Hello عند التشغيل" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "اطلب معرف اللمس عند التشغيل" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "القياسات الحيوية للمتصفح تتطلب القياسات الحيوية لسطح المكتب ليتم تمكينها في الإعدادات أولاً." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "بسبب سياسة المؤسسة، يمنع عليك حفظ العناصر في خزانتك الشخصية. غيّر خيار الملكية إلى مؤسسة واختر من المجموعات المتاحة." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "كلمة المرور الرئيسية الخاصة بك لا تفي بواحدة أو أكثر من سياسات مؤسستك. من أجل الوصول إلى الخزنة، يجب عليك تحديث كلمة المرور الرئيسية الآن. سيتم تسجيل خروجك من الجلسة الحالية، مما يتطلب منك تسجيل الدخول مرة أخرى. وقد تظل الجلسات النشطة على أجهزة أخرى نشطة لمدة تصل إلى ساعة واحدة." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "حاول مرة أخرى" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 6e8967e5a35..923b1c54006 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Əlavə Windows Hello ayarları" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Bitwarden üçün doğrula." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Touch ID kilidini aç" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Açılışda Windows Hello-nu soruşun" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Açılışda Touch ID-ni soruşun" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Brauzer biometrikləri, əvvəlcə ayarlarda masaüstü biometriklərinin qurulmasını tələb edir." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Müəssisə Siyasətinə görə, elementləri şəxsi anbarınızda saxlamağınız məhdudlaşdırılıb. Sahiblik seçimini təşkilat olaraq dəyişdirin və mövcud kolleksiyalar arasından seçim edin." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Ana parolunuz təşkilatınızdakı siyasətlərdən birinə və ya bir neçəsinə uyğun gəlmir. Anbara müraciət üçün ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış etmiş və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Təşkilatınız, güvənli cihaz şifrələməsini sıradan çıxartdı. Anbarınıza müraciət etmək üçün lütfən ana parol təyin edin." + }, "tryAgain": { "message": "Yenidən sına" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index e3c4f8f97ce..9931724dd9b 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Дадатковыя налады Windows Hello" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Праверыць на Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Разблакіраваць з Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Пытацца пра Windows Hello пры запуску" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Пытацца пра Touch ID пры запуску" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Для актывацыі біяметрыі ў браўзеры неабходна спачатку ўключыць яе ў наладах праграмы для камп'ютара." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "У адпаведнасці з палітыкай прадпрыемства вам забаронена захоўваць элементы ў асабістым сховішчы. Змяніце параметры ўласнасці на арганізацыю і выберыце з даступных калекцый." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Ваш асноўны пароль не адпавядае адной або некалькім палітыкам арганізацыі. Для атрымання доступу да сховішча, вы павінны абнавіць яго. Працягваючы, вы выйдзіце з бягучага сеанса і вам неабходна будзе ўвайсці паўторна. Актыўныя сеансы на іншых прыладах могуць заставацца актыўнымі на працягу адной гадзіны." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index e6db6903e22..ce84d0259ef 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Допълнителни настройки на Windows Hello" }, + "unlockWithPolkit": { + "message": "Отключване чрез системно удостоверяване" + }, "windowsHelloConsentMessage": { "message": "Потвърждаване за Битуорден." }, + "polkitConsentMessage": { + "message": "Идентифицирайте се, за да отключите Битуорден." + }, "unlockWithTouchId": { "message": "Отключване с Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Питане за Windows Hello при пускане" }, + "autoPromptPolkit": { + "message": "Питане за системно удостоверяване при стартиране" + }, "autoPromptTouchId": { "message": "Питане за Touch ID при пускане" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Потвърждаването с биометрични данни в браузъра изисква включването включването им в настройките за самостоятелното приложение." }, + "biometricsManualSetupTitle": { + "message": "Автоматичното настройване не е налично" + }, + "biometricsManualSetupDesc": { + "message": "Поради начина на инсталиране, поддръжката на биометрични данни не може да бъде включена автоматично. Искате ли да отворите документацията, за да видите как да го направите ръчно?" + }, "personalOwnershipSubmitError": { "message": "Заради някоя политика за голяма организация не може да запазвате елементи в собствения си трезор. Променете собствеността да е на организация и изберете от наличните колекции." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Вашата главна парола не отговаря на една или повече политики на организацията Ви. За да получите достъп до трезора, трябва да промените главната си парола сега. Това означава, че ще бъдете отписан(а) от текущата си сесия и ще трябва да се впишете отново. Активните сесии на други устройства може да продължат да бъдат активни още един час." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Вашата организация е деактивирала шифроването чрез доверени устройства. Задайте главна парола, за да получите достъп до трезора си." + }, "tryAgain": { "message": "Нов опит" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Данни" + }, + "fileSends": { + "message": "Файлови изпращания" + }, + "textSends": { + "message": "Текстови изпращания" } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 83437ea4573..70b45364cc8 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "ব্রাউজার বায়োমেট্রিক্সের জন্য প্রথমে সেটিংসে ডেস্কটপ বায়োমেট্রিক সক্ষম করা প্রয়োজন।" }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "একটি এন্টারপ্রাইজ নীতির কারণে, আপনি আপনার ব্যক্তিগত ভল্টে বস্তুসমূহ সংরক্ষণ করা থেকে সীমাবদ্ধ। একটি প্রতিষ্ঠানের মালিকানা বিকল্পটি পরিবর্তন করুন এবং উপলভ্য সংগ্রহগুলি থেকে চয়ন করুন।" }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 939fee08c76..1e5d34decb7 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Potvrdi za Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Otključaj koristeći Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Biometrija preglednika zahtijeva prethodno omogućenu biometriju u Bitwarden desktop aplikaciji." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Zbog poslovnih smjernica, zabranjeno vam je pohranjivanje predmeta u svoj lični trezor. Promijenite opciju vlasništva u organizaciji i odaberite neku od dostupnih kolekcija." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 61744522b71..8827bcd7355 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -500,10 +500,10 @@ "message": "Crea un compte" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Estableix una contrasenya segura" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Acabeu de crear el vostre compte establint una contrasenya" }, "logIn": { "message": "Inicia sessió" @@ -527,7 +527,7 @@ "message": "Pista de la contrasenya mestra (opcional)" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "Si oblideu la contrasenya, la pista de contrasenya es pot enviar al vostre correu electrònic. $CURRENT$/$MAXIMUM$ caràcters màxim.", "placeholders": { "current": { "content": "$1", @@ -540,22 +540,22 @@ } }, "masterPassword": { - "message": "Master password" + "message": "Contrasenya mestra" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "La contrasenya mestra no es pot recuperar si la oblideu!" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Confirma la contrasenya mestra" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Pista de la contrasenya mestra" }, "joinOrganization": { - "message": "Join organization" + "message": "Uneix-te a l'organització" }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "Acabeu d'unir-vos a aquesta organització establint una contrasenya mestra." }, "settings": { "message": "Configuració" @@ -634,7 +634,7 @@ "message": "El codi de verificació és obligatori." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "L'autenticació s'ha cancel·lat o ha tardat massa. Torna-ho a provar." }, "invalidVerificationCode": { "message": "Codi de verificació no vàlid" @@ -692,7 +692,7 @@ "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "Clau de seguretat OTP de Yubico" }, "yubiKeyDesc": { "message": "Utilitzeu una YubiKey per accedir al vostre compte. Funciona amb els dispositius YubiKey 4, 4 Nano, 4C i NEO." @@ -739,7 +739,7 @@ "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "Per a la configuració avançada, podeu especificar l'URL base de cada servei de manera independent." }, "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." @@ -802,7 +802,7 @@ "message": "Restart registration" }, "expiredLink": { - "message": "Expired link" + "message": "Enllaç caducat" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." @@ -1370,7 +1370,7 @@ "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "Export from" + "message": "Exporta des de" }, "exportVault": { "message": "Exporta caixa forta" @@ -1382,7 +1382,7 @@ "message": "This file export will be password protected and require the file password to decrypt." }, "filePassword": { - "message": "File password" + "message": "Contrasenya del fitxer" }, "exportPasswordDescription": { "message": "This password will be used to export and import this file" @@ -1391,13 +1391,13 @@ "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." }, "passwordProtected": { - "message": "Password protected" + "message": "Protegit amb contrasenya" }, "passwordProtectedOptionDescription": { "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." }, "exportTypeHeading": { - "message": "Export type" + "message": "Tipus d'exportació" }, "accountRestricted": { "message": "Account restricted" @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Configuració addicional de Windows Hello" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verifica per Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Desbloqueja amb Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Sol·liciteu Windows Hello en iniciar" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Sol·liciteu Touch ID en iniciar" }, @@ -1715,16 +1724,16 @@ "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Anul·la la subscripció" }, "atAnyTime": { - "message": "at any time." + "message": "en qualsevol moment." }, "byContinuingYouAgreeToThe": { "message": "By continuing, you agree to the" }, "and": { - "message": "and" + "message": "i" }, "acceptPolicies": { "message": "Si activeu aquesta casella, indiqueu que esteu d’acord amb el següent:" @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "La biometria del navegador primer necessita habilitar la biomètrica d’escriptori a la configuració." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "A causa d'una política empresarial, no podeu guardar elements a la vostra caixa forta personal. Canvieu l'opció Propietat en organització i trieu entre les col·leccions disponibles." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "La vostra contrasenya mestra no compleix una o més de les polítiques de l'organització. Per accedir a la caixa forta, heu d'actualitzar-la ara. Si continueu, es tancarà la sessió actual i us demanarà que torneu a iniciar-la. Les sessions en altres dispositius poden continuar romanent actives fins a una hora." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Torneu-ho a provar" }, @@ -2521,13 +2539,13 @@ "message": "S'ha sol·licitat inici de sessió" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Creant compte en" }, "checkYourEmail": { - "message": "Check your email" + "message": "Comprova el teu correu" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Seguiu l'enllaç del correu electrònic enviat a" }, "andContinueCreatingYourAccount": { "message": "and continue creating your account." @@ -2536,7 +2554,7 @@ "message": "No email?" }, "goBack": { - "message": "Go back" + "message": "Torna arrere" }, "toEditYourEmailAddress": { "message": "to edit your email address." @@ -2827,7 +2845,7 @@ "message": "La contrasenya del fitxer no és vàlida. Utilitzeu la contrasenya que vau introduir quan vau crear el fitxer d'exportació." }, "destination": { - "message": "Destination" + "message": "Destinació" }, "learnAboutImportOptions": { "message": "Obteniu informació sobre les opcions d'importació" @@ -2886,7 +2904,7 @@ "message": "Confirma la contrasenya del fitxer" }, "exportSuccess": { - "message": "Vault data exported" + "message": "S'han exportat les dades de la caixa forta" }, "multifactorAuthenticationCancelled": { "message": "S'ha cancel·lat l'autenticació multifactor" @@ -3010,7 +3028,7 @@ } }, "back": { - "message": "Back", + "message": "Arrere", "description": "Button text to navigate back" }, "removeItem": { @@ -3024,6 +3042,12 @@ } }, "data": { - "message": "Data" + "message": "Dades" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index d28b464fdd4..2ab8dfa806c 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Další nastavení Windows Hello" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Ověřte se pro Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Odemknout pomocí Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Požádat o Windows Hello při spuštění aplikace" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Požádat o Touch ID při spuštění aplikace" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Biometrické prvky v prohlížeči vyžadují, aby byla nastavena biometrie nejprve v aplikaci pro počítač." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Z důvodu podnikových zásad nemůžete ukládat položky do svého osobního trezoru. Změňte vlastnictví položky na organizaci a poté si vyberte z dostupných kolekcí." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Vaše hlavní heslo nesplňuje jednu nebo více zásad Vaší organizace. Pro přístup k trezoru musíte nyní aktualizovat své hlavní heslo. Pokračování Vás odhlásí z Vaší aktuální relace a bude nutné se přihlásit. Aktivní relace na jiných zařízeních mohou zůstat aktivní až po dobu jedné hodiny." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Vaše organizace zakázala šifrování pomocí důvěryhodného zařízení. Nastavte hlavní heslo pro přístup k trezoru." + }, "tryAgain": { "message": "Zkusit znovu" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 3cb285cf21c..19ef9c8ffbb 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index b99ec3b53b0..ad371a4d116 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Yderligere indstillinger for Windows Hello" }, + "unlockWithPolkit": { + "message": "Oplås med systemgodkendelse" + }, "windowsHelloConsentMessage": { "message": "Bekræft for Bitwarden." }, + "polkitConsentMessage": { + "message": "Godkend for at oplåse Bitwarden." + }, "unlockWithTouchId": { "message": "Oplås med Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Anmod om Windows Hello ved app-start" }, + "autoPromptPolkit": { + "message": "Anmod om systemgodkendelse ved start" + }, "autoPromptTouchId": { "message": "Anmod om Touch ID ved app-start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browserbiometri kræver, at computerbiometri er opsat i indstillingerne først." }, + "biometricsManualSetupTitle": { + "message": "Automatisk opsætning utilgængelig" + }, + "biometricsManualSetupDesc": { + "message": "Grundet installationsmetoden kunne biometriunderstøttelse ikke automatisk aktiveres. Åbn dokumentationen til, hvordan dette gøres manuelt?" + }, "personalOwnershipSubmitError": { "message": "Grundet en virksomhedspolitik forhindres du i at gemme emner i din personlige boks. Skift ejerskabsindstillingen til en organisation, og vælg blandt de tilgængelige samlinger." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Din hovedadgangskode overholder ikke en eller flere organisationspolitikker. For at få adgang til boksen skal hovedadgangskode opdateres nu. Fortsættes, logges du ud af den nuværende session og vil skulle logger ind igen. Aktive sessioner på andre enheder kan forblive aktive i op til én time." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Organisationen har deaktiveret betroet enhedskryptering. Opsæt en hovedadgangskode for at tilgå boksen." + }, "tryAgain": { "message": "Forsøg igen" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "Fil-Sends" + }, + "textSends": { + "message": "Tekst-Sends" } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 47f07af7c33..6e5d5644c8f 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Zusätzliche Einstellungen für Windows Hello" }, + "unlockWithPolkit": { + "message": "Mit Systemauthentifizierung entsperren" + }, "windowsHelloConsentMessage": { "message": "Für Bitwarden verifizieren." }, + "polkitConsentMessage": { + "message": "Authentifizieren, um Bitwarden zu entsperren." + }, "unlockWithTouchId": { "message": "Mit Touch ID entsperren" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Beim Start nach Windows Hello fragen" }, + "autoPromptPolkit": { + "message": "Beim Start nach Systemauthentifizierung fragen" + }, "autoPromptTouchId": { "message": "Beim Start nach Touch ID fragen" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Biometrie im Browser setzt voraus, dass Biometrie zuerst in den Einstellungen der Desktop-Anwendung eingerichtet wird." }, + "biometricsManualSetupTitle": { + "message": "Automatische Einrichtung nicht verfügbar" + }, + "biometricsManualSetupDesc": { + "message": "Aufgrund der Installationsmethode konnte die Biometrie-Unterstützung nicht automatisch aktiviert werden. Möchtest du die Dokumentation für die manuelle Aktivierung öffnen?" + }, "personalOwnershipSubmitError": { "message": "Aufgrund einer Unternehmensrichtlinie darfst du keine Einträge in deinem persönlichen Tresor speichern. Ändere die Eigentümer-Option in eine Organisation und wähle aus den verfügbaren Sammlungen." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Dein Master-Passwort entspricht nicht einer oder mehreren Richtlinien deiner Organisation. Um auf den Tresor zugreifen zu können, musst du dein Master-Passwort jetzt aktualisieren. Wenn du fortfährst, wirst du von deiner aktuellen Sitzung abgemeldet und musst dich erneut anmelden. Aktive Sitzungen auf anderen Geräten können noch bis zu einer Stunde lang aktiv bleiben." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Deine Organisation hat die vertrauenswürdige Geräteverschlüsselung deaktiviert. Bitte lege ein Master-Passwort fest, um auf deinen Tresor zuzugreifen." + }, "tryAgain": { "message": "Erneut versuchen" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Daten" + }, + "fileSends": { + "message": "Datei-Sends" + }, + "textSends": { + "message": "Text-Sends" } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index a4b7e5e64a6..2026cfa9720 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -33,7 +33,7 @@ "message": "Συλλογές" }, "searchVault": { - "message": "Αναζήτηση στο θησαυ/κιο" + "message": "Αναζήτηση κρύπτης" }, "addItem": { "message": "Προσθήκη αντικειμένου" @@ -257,7 +257,7 @@ "message": "Άλλες" }, "generatePassword": { - "message": "Δημιουργία κωδικού πρόσβασης" + "message": "Γέννηση κωδικού πρόσβασης" }, "type": { "message": "Τύπος" @@ -364,7 +364,7 @@ "message": "Διαγραφή συνημμένου" }, "deleteItemConfirmation": { - "message": "Είστε βέβαιοι ότι θέλετε να το μετακινήσετε στον κάδο;" + "message": "Σίγουρα θέλετε να το μετακινήσετε στον κάδο;" }, "deletedItem": { "message": "Το αντικείμενο μετακινήθηκε στον κάδο απορριμάτων" @@ -479,7 +479,7 @@ "message": "Το μέγιστο μέγεθος αρχείου είναι 500 MB." }, "encryptionKeyMigrationRequired": { - "message": "Απαιτείται μεταφορά κλειδιού κρυπτογράφησης. Παρακαλούμε συνδεθείτε μέσω του διαδικτυακού θησαυ/κίου για να ενημερώσετε το κλειδί κρυπτογράφησης σας." + "message": "Απαιτείται μεταφορά κλειδιού κρυπτογράφησης. Παρακαλούμε συνδεθείτε μέσω της διαδικτυακής κρύπτης για να ενημερώσετε το κλειδί κρυπτογράφησης σας." }, "editedFolder": { "message": "Ο φάκελος αποθηκεύτηκε" @@ -488,7 +488,7 @@ "message": "Ο φάκελος προστέθηκε" }, "deleteFolderConfirmation": { - "message": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον φάκελο;" + "message": "Σίγουρα θέλετε να διαγράψετε αυτόν τον φάκελο;" }, "deletedFolder": { "message": "Ο φάκελος διαγράφηκε" @@ -552,7 +552,7 @@ "message": "Υπόδειξη κύριου κωδικού πρόσβασης" }, "joinOrganization": { - "message": "Συμμετοχή στον οργανισμό" + "message": "Συμμετοχή σε οργανισμό" }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Ολοκληρώστε τη συμμετοχή σας σε αυτόν τον οργανισμό ορίζοντας έναν κύριο κωδικό πρόσβασης." @@ -757,7 +757,7 @@ "message": "URL διακομιστή API" }, "webVaultUrl": { - "message": "URL διακομιστή διαδικτυακού θησαυ/κίου" + "message": "URL διακομιστή διαδικτυακής κρύπτης" }, "identityUrl": { "message": "URL διακομιστή ταυτότητας" @@ -805,7 +805,7 @@ "message": "Ο σύνδεσμος έληξε" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Παρακαλούμε επανακκινήστε την εγγραφή ή δοκιμάστε να συνδεθείτε." + "message": "Παρακαλούμε επανεκκινήστε την εγγραφή ή δοκιμάστε να συνδεθείτε." }, "youMayAlreadyHaveAnAccount": { "message": "Μπορεί να έχετε ήδη λογαριασμό" @@ -835,7 +835,7 @@ "message": "Φόρτωση..." }, "lockVault": { - "message": "Κλείδωμα θησαυ/κίου" + "message": "Κλείδωμα κρύπτης" }, "passwordGenerator": { "message": "Γεννήτρια κωδικού πρόσβασης" @@ -859,7 +859,7 @@ "message": "Ακολουθήστε μας" }, "syncVault": { - "message": "Συγχρονισμός θησαυ/κίου" + "message": "Συγχρονισμός κρύπτης" }, "changeMasterPass": { "message": "Αλλαγή κύριου κωδικού πρόσβασης" @@ -879,13 +879,13 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "goToWebVault": { - "message": "Πηγαίνετε στο διαδικτυακό θησαυ/κιο" + "message": "Πηγαίνετε στη διαδικτυακή κρύπτη" }, "getMobileApp": { - "message": "Λήψη εφαρμογής για το κινητό" + "message": "Απόκτηση εφαρμογής για το κινητό" }, "getBrowserExtension": { - "message": "Λήψη επέκτασης περιηγητή" + "message": "Απόκτηση επέκτασης περιηγητή" }, "syncingComplete": { "message": "Ο συγχρονισμός ολοκληρώθηκε" @@ -894,7 +894,7 @@ "message": "Ο συγχρονισμός απέτυχε" }, "yourVaultIsLocked": { - "message": "Το θησαυ/κιό σας είναι κλειδωμένο. Επαληθεύστε την ταυτότητά σας για να συνεχίσετε." + "message": "Η κρύπτη σας είναι κλειδωμένη. Επαληθεύστε την ταυτότητά σας για να συνεχίσετε." }, "unlock": { "message": "Ξεκλείδωμα" @@ -916,16 +916,16 @@ "message": "Μη έγκυρος κύριος κωδικός πρόσβασης" }, "twoStepLoginConfirmation": { - "message": "Η σύνδεση δύο βημάτων καθιστά τον λογαριασμό σας πιο ασφαλή απαιτώντας από εσάς να επαληθεύσετε τη σύνδεσή σας με άλλη συσκευή, όπως ένα κλειδί ασφαλείας, μία εφαρμογή αυθεντικοποίησης, ένα SMS, μία τηλεφωνική κλήση, ή ένα μήνυμα ηλ. ταχυδρομείου. Η σύνδεση δύο βημάτων μπορεί να ρυθμιστεί στο διαδικτυακό θησαυ/κιο bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" + "message": "Η σύνδεση δύο βημάτων καθιστά τον λογαριασμό σας πιο ασφαλή απαιτώντας από εσάς να επαληθεύσετε τη σύνδεσή σας με άλλη συσκευή, όπως ένα κλειδί ασφαλείας, μία εφαρμογή αυθεντικοποίησης, ένα SMS, μία τηλεφωνική κλήση, ή ένα μήνυμα ηλ. ταχυδρομείου. Η σύνδεση δύο βημάτων μπορεί να ρυθμιστεί στη διαδικτυακή κρύπτη bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" }, "twoStepLogin": { "message": "Σύνδεση δύο βημάτων" }, "vaultTimeout": { - "message": "Χρονικό όριο λήξης θησαυ/κίου" + "message": "Χρονικό όριο λήξης κρύπτης" }, "vaultTimeoutDesc": { - "message": "Επιλέξτε πότε το θησαυ/κιό σας θα αναλάβει τη δράση χρονικού ορίου λήξης θησαυ/κίου." + "message": "Επιλέξτε πότε η κρύπτη σας θα αναλάβει τη δράση χρονικού ορίου λήξης κρύπτης." }, "immediately": { "message": "Άμεσα" @@ -1280,7 +1280,7 @@ "message": "Σφάλμα Ανανέωσης Διακριτικού Πρόσβασης" }, "errorRefreshingAccessTokenDesc": { - "message": "Δεν βρέθηκε διακριτικό ανανέωσης ή κλειδιά API. Παρακαλούμε δοκιμάστε να αποσυνδεθείτε και να συνδεθείτε ξανά." + "message": "Δε βρέθηκε διακριτικό ανανέωσης ή κλειδιά API. Παρακαλούμε δοκιμάστε να αποσυνδεθείτε και να συνδεθείτε ξανά." }, "help": { "message": "Βοήθεια" @@ -1373,7 +1373,7 @@ "message": "Εξαγωγή από" }, "exportVault": { - "message": "Εξαγωγή θησαυ/κίου" + "message": "Εξαγωγή κρύπτης" }, "fileFormat": { "message": "Τύπος αρχείου" @@ -1403,7 +1403,7 @@ "message": "Ο λογαριασμός περιορίστηκε" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“Κωδικός πρόσβασης αρχείου” και “Επιβεβαίωση κωδικού πρόσβασης αρχείου“ δεν ταιριάζουν." + "message": "Το \"Κωδικός πρόσβασης αρχείου\" και το \"Επιβεβαίωση κωδικού πρόσβασης αρχείου\" δεν ταιριάζουν." }, "hCaptchaUrl": { "message": "hCaptcha Url", @@ -1444,7 +1444,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "Επιβεβαίωση εξαγωγής θησαυ/κίου" + "message": "Επιβεβαίωση εξαγωγής κρύπτης" }, "exportWarningDesc": { "message": "Αυτή η εξαγωγή περιέχει τα δεδομένα σε μη κρυπτογραφημένη μορφή. Δεν πρέπει να αποθηκεύετε ή να στείλετε το εξαγόμενο αρχείο μέσω μη ασφαλών τρόπων (όπως μέσω email). Διαγράψτε το αμέσως μόλις τελειώσετε με τη χρήση του." @@ -1480,7 +1480,7 @@ "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Ασθενής κύριος κωδικός πρόσβασης" + "message": "Αδύναμος κύριος κωδικός πρόσβασης" }, "weakMasterPasswordDesc": { "message": "Ο κύριος κωδικός που έχετε επιλέξει είναι αδύναμος. Θα πρέπει να χρησιμοποιήσετε έναν ισχυρό κύριο κωδικό (ή μια φράση) για την κατάλληλη προστασία του λογαριασμού Bitwarden. Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε αυτόν τον κύριο κωδικό;" @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Πρόσθετες ρυθμίσεις του Windows Hello" }, + "unlockWithPolkit": { + "message": "Ξεκλείδωμα με αυθεντικοποίηση συστήματος" + }, "windowsHelloConsentMessage": { "message": "Επαληθεύστε για το Bitwarden." }, + "polkitConsentMessage": { + "message": "Αυθεντικοποίηση για ξεκλείδωμα του Bitwarden." + }, "unlockWithTouchId": { "message": "Ξεκλείδωμα με Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Να ζητείται Windows Hello κατά την εκκίνηση της εφαρμογής" }, + "autoPromptPolkit": { + "message": "Ρώτησε με για αυθεντικοποίηση συστήματος κατά την εκκίνηση" + }, "autoPromptTouchId": { "message": "Ερώτηση για το Touch ID κατά την εκκίνηση" }, @@ -1541,7 +1550,7 @@ "message": "Διαγραφή λογαριασμού" }, "deleteAccountDesc": { - "message": "Προχωρήστε παρακάτω για να διαγράψετε τον λογαριασμό σας και όλα τα δεδομένα θησαυ/κίου." + "message": "Προχωρήστε παρακάτω για να διαγράψετε τον λογαριασμό σας και όλα τα δεδομένα κρύπτης." }, "deleteAccountWarning": { "message": "Η διαγραφή του λογαριασμού σας είναι μόνιμη. Δεν μπορεί να αναιρεθεί." @@ -1599,13 +1608,13 @@ "message": "Μία ή περισσότερες πολιτικές του οργανισμού επηρεάζουν τις ρυθμίσεις της γεννήτριας." }, "vaultTimeoutAction": { - "message": "Ενέργεια χρονικού ορίου λήξης θησαυ/κίου" + "message": "Ενέργεια χρονικού ορίου λήξης κρύπτης" }, "vaultTimeoutActionLockDesc": { - "message": "Απαιτείται κύριος κωδικός πρόσβασης ή άλλη μέθοδος ξεκλειδώματος για να αποκτήσετε ξανά πρόσβαση στο θησαυ/κιό σας." + "message": "Απαιτείται κύριος κωδικός πρόσβασης ή άλλη μέθοδος ξεκλειδώματος για να αποκτήσετε ξανά πρόσβαση στη κρύπτη σας." }, "vaultTimeoutActionLogOutDesc": { - "message": "Απαιτείται αυθεντικοποίηση για να αποκτήσετε ξανά πρόσβαση στο θησαυ/κιό σας." + "message": "Απαιτείται αυθεντικοποίηση για να αποκτήσετε ξανά πρόσβαση στη κρύπτη σας." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Ρυθμίστε μια μέθοδο ξεκλειδώματος για να αλλάξετε την ενέργεια χρονικού ορίου λήξης θησαυ/κίου." @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Τα βιομετρικά στον περιηγητή απαιτούν την ενεργοποίηση των βιομετρικών επιφάνειας εργασίας στις ρυθμίσεις πρώτα." }, + "biometricsManualSetupTitle": { + "message": "Η αυτόματη ρύθμιση δεν είναι διαθέσιμη" + }, + "biometricsManualSetupDesc": { + "message": "Λόγω της μεθόδου εγκατάστασης, η υποστήριξη των βιομετρικών δεν μπορεί να ενεργοποιηθεί αυτόματα. Θα θέλατε να ανοίξετε το εγχειρίδιο για το πώς να το κάνετε αυτό χειροκίνητα;" + }, "personalOwnershipSubmitError": { "message": "Λόγω μιας επιχειρηματικής πολιτικής, περιορίζεστε από την αποθήκευση αντικειμένων στο ατομικό σας θησαυ/κιό. Αλλάξτε την επιλογή ιδιοκτησίας σε έναν οργανισμό και επιλέξτε από τις διαθέσιμες συλλογές." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Ο κύριος κωδικός πρόσβασής σας δεν πληροί μία ή περισσότερες πολιτικές του οργανισμού σας. Για να αποκτήσετε πρόσβαση στο θησαυ/κιο, πρέπει να ενημερώσετε τον κύριο κωδικό πρόσβασής σας τώρα. Η διαδικασία θα σας αποσυνδέσει από την τρέχουσα συνεδρία σας, απαιτώντας από εσάς να συνδεθείτε ξανά. Οι ενεργές συνεδρίες σε άλλες συσκευές ενδέχεται να συνεχίσουν να είναι ενεργές για μία ώρα." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Ο οργανισμός σας έχει απενεργοποιήσει την κρυπτογράφηση αξιόπιστης συσκευής. Παρακαλώ ορίστε έναν κύριο κωδικό πρόσβασης για να αποκτήσετε πρόσβαση στο θησαυροφυλάκιο σας." + }, "tryAgain": { "message": "Προσπαθήστε ξανά" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Δεδομένα" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 62ae99fc7b8..e24cd7b6412 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on launch" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on launch" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Automatic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organisation and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organisation policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organisation has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index ced2fc61e31..d09708b972d 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on launch" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on launch" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be enabled in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Automatic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organisation policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organisation has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 9cf5ef6f60b..eed26bd0106 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index d71866a8f18..bf331ce430a 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Ajustes adicionales de Windows Hello" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verificar para Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Desbloquear con Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Solicitar Windows Hello al iniciar" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Solicitar Touch ID al iniciar" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "La biometría del navegador requiere habilitar primero la biometría de escritorio en los ajustes." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Debido a una política de organización, tiene restringido el guardar elementos a su caja fuerte personal. Cambie la configuración de propietario a organización y elija entre las colecciones disponibles." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Tu contraseña maestra no cumple con una o más de las políticas de tu organización. Para acceder a la caja fuerte, debes actualizar tu contraseña maestra ahora. Proceder te desconectará de tu sesión actual, requiriendo que vuelva a iniciar sesión. Las sesiones activas en otros dispositivos pueden seguir estando activas durante hasta una hora." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Intentar de nuevo" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index f746cbb30f1..5eb1a6fe0e1 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Kinnita Bitwardenisse sisselogimine." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Lukusta lahti Touch ID-ga" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Küsi avamisel Windows Hello tuvastust" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Küsi avamisel Touch ID tuvastust" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Selleks, et kasutada biomeetriat brauseris, peab selle esmalt Bitwardeni töölaua rakenduse seadetes sisse lülitama." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Ettevõtte poliitika tõttu ei saa sa andmeid oma personaalsesse Hoidlasse salvestada. Vali Omanikuks organisatsioon ja vali mõni saadavaolevatest Kogumikest." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Sinu ülemparool ei vasta ühele või rohkemale organisatsiooni poolt seatud poliitikale. Hoidlale ligipääsemiseks pead oma ülemaprooli uuendama. Jätkamisel logitakse sind praegusest sessioonist välja, mistõttu pead uuesti sisse logima. Teistes seadmetes olevad aktiivsed sessioonid aeguvad umbes ühe tunni jooksul." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index c0f8e046429..146e6e31b83 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Egiaztatu Bitwarden-entzako." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Desblokeatu Touch ID-arekin" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Eskatu Windows Hello abiaraztean" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Eskatu Touch ID abiaraztean" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Nabigatzailearen biometriak lehenik mahaigainaren biometria gaitzeko eskatzen du." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Erakundeko politika bat dela eta, ezin dituzu elementuak zure kutxa gotor pertsonalean gorde. Aldatu jabe aukera erakunde aukera batera, eta aukeratu bilduma erabilgarrien artean." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index d435e1fdde3..613fec6bedc 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "تنظیمات اضافی Windows Hello" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "تأیید برای Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "باز کردن با Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "درخواست Windows Hello در هنگام راه اندازی" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "درخواست Touch ID در هنگام راه اندازی" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "بیومتریک مرورگر ابتدا نیاز به فعالسازی بیومتریک دسکتاپ در تنظیمات دارد." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "به دلیل سیاست پرمیوم، برای ذخیره موارد در گاوصندوق شخصی خود محدود شده اید. گزینه مالکیت را به یک سازمان تغییر دهید و مجموعه های موجود را انتخاب کنید." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "کلمه عبور اصلی شما با یک یا چند سیاست سازمان‌تان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "دوباره سعی کنید" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 6c1fa599281..8ad13891213 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Windows Hello -lisäasetukset." }, + "unlockWithPolkit": { + "message": "Avaa järjestelmän tunnistautumisella" + }, "windowsHelloConsentMessage": { "message": "Vahvista Bitwarden." }, + "polkitConsentMessage": { + "message": "Todenna avataksesi Bitwardenin lukitus." + }, "unlockWithTouchId": { "message": "Avaa Touch ID:llä" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Pyydä Windows Hello -todennusta käynnistettäessä" }, + "autoPromptPolkit": { + "message": "Pyydä järjestelmän tunnistautumista käynnistettäessä" + }, "autoPromptTouchId": { "message": "Pyydä Touch ID -todennusta käynnistettäessä" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Biometria selaimissa edellyttää sen määritystä työpöytäsovelluksen asetuksista." }, + "biometricsManualSetupTitle": { + "message": "Automaattinen määritys ei ole käytettävissä" + }, + "biometricsManualSetupDesc": { + "message": "Biometriatukea ei voitu ottaa automaattisesti käyttöön asennustavan vuoksi. Haluatko avata ohjeet tämän tekemiseksi manuaalisesti?" + }, "personalOwnershipSubmitError": { "message": "Yrityskäytännön johdosta kohteiden tallennus henkilökohtaiseen holviin ei ole mahdollista. Muuta omistusasetus organisaatiolle ja valitse käytettävissä olevista kokoelmista." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Pääsalasanasi ei täytä yhden tai useamman organisaatiokäytännön vaatimuksia ja holvin käyttämiseksi sinun on vaihdettava se nyt. Tämä uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Organisaatiosi on estänyt luotettavan laitesalauksen. Käytä holviasi asettamalla pääsalasana." + }, "tryAgain": { "message": "Yritä uudelleen" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Tiedot" + }, + "fileSends": { + "message": "Tiedosto-Sendit" + }, + "textSends": { + "message": "Teksti-Sendit" } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 7e7d5485eea..326a86060a3 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify para sa Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "I-unlock gamit ang Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Humingi ng Windows Hello sa paglulunsad" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Humingi ng Touch ID sa paglulunsad" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Ang biometrics ng browser ay nangangailangan ng desktop biometrics na mai set up muna sa mga setting." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Dahil sa isang patakaran sa enterprise, pinaghihigpitan ka mula sa pag-save ng mga item sa iyong vault. Baguhin ang pagpipilian sa pagmamay ari sa isang organisasyon at pumili mula sa mga magagamit na koleksyon." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index f1e178410d8..481deb119f5 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Paramètres supplémentaires de Windows Hello" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Vérifier pour Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Déverrouiller avec Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Demander à Windows Hello au démarrage" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Demander Touch ID au démarrage de l'application" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Les options de biométrie dans le navigateur nécessitent au préalable l'activation des options de biométrie dans l'application de bureau." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "En raison d'une politique d'entreprise, il vous est interdit d'enregistrer des éléments dans votre coffre personnel. Sélectionnez une organisation dans l'option Propriété et choisissez parmi les collections disponibles." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Votre mot de passe principal ne répond pas aux exigences de politique de sécurité de cette organisation. Pour pouvoir accéder au coffre, vous devez mettre à jour votre mot de passe principal dès maintenant. En poursuivant, vous serez déconnecté de votre session actuelle et vous devrez vous reconnecter. Les sessions actives sur d'autres appareils peuver rester actives pendant encore une heure." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Essayez de nouveau" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 6d0573130ff..bc71a9703b1 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 61f784ce7eb..60fc0ef2264 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "הגדרות נוספות של Windows Hello" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "אימות עבור Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "שחרור נעילה עם Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "הצג את Windows Hello בפתיחת האפליקציה" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "הצג בקשה של Touch ID בפתיחת האפליקציה" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "בכדי להשתמש באמצעי אימות ביומטריים בדפדפן, אפשר תכונה זו באפליקציה בשולחן העבודה." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "בשל מדיניות ארגונית, אתה מוגבל לשמירת פריטים בכספת האישית שלך. שנה את ההגדרות בעלות החשבון לחשבון ארגוני ובחר מתוך האוספים הזמינים." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "לנסות שוב" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index a0eedecd256..d1edf8716bc 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index b5eaec06bcb..4180773f2b9 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -239,7 +239,7 @@ "message": "gđica." }, "mx": { - "message": "Mx" + "message": "gx." }, "dr": { "message": "dr." @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Dodatne Windows Hello postavke" }, + "unlockWithPolkit": { + "message": "Otključaj autentifikacijom sustava" + }, "windowsHelloConsentMessage": { "message": "Otključaj trezor." }, + "polkitConsentMessage": { + "message": "Otključaj Bitwarden autentifikacijom." + }, "unlockWithTouchId": { "message": "Otključaj koristeći Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Zahtijevaj Windows Hello pri pokretanju" }, + "autoPromptPolkit": { + "message": "Traži autentifikaciju sustava pri pokretanju" + }, "autoPromptTouchId": { "message": "Zahtijevaj Touch ID pri pokretanju" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Biometrija preglednika zahtijeva prethodno omogućenu biometriju u Bitwarden desktop aplikaciji." }, + "biometricsManualSetupTitle": { + "message": "Automatsko postavljanje nije dostupno" + }, + "biometricsManualSetupDesc": { + "message": "Zbog načina instalacije, biometrijska podrška nije mogla biti automatski omogućena. Želiš li otvoriti dokumentaciju o tome kako to učiniti ručno?" + }, "personalOwnershipSubmitError": { "message": "Pravila tvrtke onemogućuju spremanje stavki u osobni trezor. Promijeni vlasništvo stavke na tvrtku i odaberi dostupnu Zbirku." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Tvoja glavna lozinka ne zadovoljava pravila ove organizacije. Za pristup trezoru moraš odmah ažurirati svoju glavnu lozinku. Ako nastaviš, odjaviti ćeš se iz trenutne sesije te ćeš se morati ponovno prijaviti. Aktivne sesije na drugim uređajima mogu ostati aktivne do jedan sat." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Tvoja je organizacija onemogućila šifriranje pouzdanog uređaja. Postavi glavnu lozinku za pristup svom trezoru." + }, "tryAgain": { "message": "Pokušaj ponovno" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Podaci" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index e699667a6a4..12c0ae560a8 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Kiegészítő Windows Hello beállítások" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Bitwarden ellenőrzés." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Feloldás Touch ID segítségével" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Windows Hello kérése indításkor" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Érintés AZ kérése indításkor" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "A böngésző biometrikus adataihoz először az asztali biometrikus adatokat kell engedélyezni a beállításokban." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Egy vállalati házirend miatt korlátozásra került az elemek személyes tárolóba történő mentése. Módosítsuk a Tulajdon opciót egy szervezetre és válasszunk az elérhető gyűjtemények közül." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "A mesterjelszó nem felel meg egy vagy több szervezeti szabályzatnak. A széf eléréséhez frissíteni kell a meszerjelszót. A továbblépés kijelentkeztet az aktuális munkamenetből és újra be kell jelentkezni. A többi eszközön lévő aktív munkamenetek akár egy óráig is aktívak maradhatnak." }, + "tdeDisabledMasterPasswordRequired": { + "message": "A szervezete letiltotta a megbízható eszközök titkosítását. Állítsunk be egy mesterjelszót a széf eléréséhez." + }, "tryAgain": { "message": "Próbáluk újra" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Adat" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 2e822e292ac..b57fc49adf2 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verifikasi untuk Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Buka kunci dengan Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Minta Windows Hello saat diluncurkan" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Minta Touch ID saat diluncurkan" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Biometrik browser mengharuskan biometrik desktop diaktifkan di pengaturan terlebih dahulu." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Karena Kebijakan Perusahaan, Anda dilarang menyimpan item ke lemari besi pribadi Anda. Ubah opsi Kepemilikan ke organisasi dan pilih dari Koleksi yang tersedia." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index ab23afe2773..4a0261f4cfb 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Impostazioni aggiuntive di Windows Hello" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verifica per Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Sblocca con Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Richiedi Windows Hello all'avvio" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Richiedi Touch ID all'avvio" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "L'autenticazione biometrica del browser richiede che l'autenticazione biometrica del desktop sia stata già impostata nelle impostazioni." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "A causa di una politica aziendale, non puoi salvare elementi nella tua cassaforte personale. Cambia l'opzione di proprietà in un'organizzazione e scegli tra le raccolte disponibili." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "La tua password principale non soddisfa uno o più politiche della tua organizzazione. Per accedere alla cassaforte, aggiornala ora. Procedere ti farà uscire dalla sessione corrente, richiedendoti di accedere di nuovo. Le sessioni attive su altri dispositivi potrebbero continuare a rimanere attive per un massimo di un'ora." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Riprova" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 8eb3510bc17..3067df84bb0 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "追加の Windows Hello 設定" }, + "unlockWithPolkit": { + "message": "システム認証でロック解除" + }, "windowsHelloConsentMessage": { "message": "Bitwarden の認証を行います。" }, + "polkitConsentMessage": { + "message": "認証して Bitwarden のロックを解除します。" + }, "unlockWithTouchId": { "message": "Touch ID でロック解除" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "起動時に Windows Hello を要求する" }, + "autoPromptPolkit": { + "message": "起動時にシステム認証を要求する" + }, "autoPromptTouchId": { "message": "起動時に Touch ID を要求する" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "ブラウザで生体認証を利用するには、最初に設定でデスクトップ生体認証を有効にする必要があります。" }, + "biometricsManualSetupTitle": { + "message": "自動設定は利用できません" + }, + "biometricsManualSetupDesc": { + "message": "インストール方法により、生体認証を自動的に有効化できませんでした。手動で設定する方法の説明を開きますか?" + }, "personalOwnershipSubmitError": { "message": "組織のポリシーにより、個人保管庫へのアイテムの保存が制限されています。 所有権を組織に変更し、利用可能なコレクションから選択してください。" }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "マスターパスワードが組織のポリシーに適合していません。保管庫にアクセスするには、今すぐマスターパスワードを更新しなければなりません。続行すると現在のセッションからログアウトし、再度ログインする必要があります。 他のデバイス上のアクティブなセッションは、最大1時間アクティブであり続けることがあります。" }, + "tdeDisabledMasterPasswordRequired": { + "message": "あなたの組織は信頼できるデバイスの暗号化を無効化しました。保管庫にアクセスするにはマスターパスワードを設定してください。" + }, "tryAgain": { "message": "再試行" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "データ" + }, + "fileSends": { + "message": "ファイル Send" + }, + "textSends": { + "message": "テキスト Send" } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 6d0573130ff..bc71a9703b1 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 6d0573130ff..bc71a9703b1 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 46fbbb6d230..cdf612baac0 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "ಬಿಟ್‌ವಾರ್ಡೆನ್‌ಗಾಗಿ ಪರಿಶೀಲಿಸಿ." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "ಟಚ್ ಐಡಿ ಯೊಂದಿಗೆ ಅನ್ಲಾಕ್ ಮಾಡಿ" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "ಬ್ರೌಸರ್ ಬಯೋಮೆಟ್ರಿಕ್ಸ್ ಮೊದಲು ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಡೆಸ್ಕ್ಟಾಪ್ ಬಯೋಮೆಟ್ರಿಕ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಬೇಕಾಗುತ್ತದೆ." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 01d8550a720..c7c22ccbd64 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Bitwarden에서 인증을 요청합니다." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Touch ID를 사용하여 잠금 해제" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "실행 시 Windows Hello 요구하기" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "실행 시 Touch ID 요구하기" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "브라우저에서 생체 인식을 사용하기 위해서는 설정에서 데스크톱 생체 인식을 먼저 활성화해야 합니다." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "엔터프라이즈 정책으로 인해 개인 보관함에 항목을 저장할 수 없습니다. 조직에서 소유권 설정을 변경한 다음, 사용 가능한 컬렉션 중에서 선택해주세요." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index bca956d3e3c..5ce46436e35 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Papildomi Windows Hello nustatymai" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Patvirtinti Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Atrakinti naudojant Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Paprašyti Windows Hello paleidus programą" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Prašyti Touch ID paleidus programėlę" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Pirma reikia nustatymuose nustatyti darbalaukio biometrinius duomenys, prieš juos naudojant naršyklėje." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Dėl įmonės politikos jums neleidžiama saugoti daiktų asmeninėje saugykloje. Pakeiskite nuosavybės parinktį į organizaciją ir pasirinkite iš galimų rinkinių." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Jūsų pagrindinis slaptažodis neatitinka vieno ar kelių organizacijos slaptažodžiui keliamų reikalavimų. Norėdami prisijungti prie saugyklos, jūs turite atnaujinti savo pagrindinį slaptažodį. Jeigu nuspręsite tęsti, jūs būsite atjungti nuo dabartinės sesijos ir jums reikės vėl prisijungti. Visos aktyvios sesijos kituose įrenginiuose gali išlikti aktyvios iki vienos valandos." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Bandyti dar kartą" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index ef698a0b35c..6480f8dadae 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Windows Hello papildu iestatījumi" }, + "unlockWithPolkit": { + "message": "Atslēgt ar sistēmas autentifikāciju" + }, "windowsHelloConsentMessage": { "message": "Apstiprināt Bitwarden." }, + "polkitConsentMessage": { + "message": "Autentificēt, lai atslēgtu Bitwarden." + }, "unlockWithTouchId": { "message": "Atslēgt ar Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Palaišanā vaicāt pēc Windows Hello" }, + "autoPromptPolkit": { + "message": "Palaišanas laikā vaicāt pēc autentifikācijas" + }, "autoPromptTouchId": { "message": "Palaišanā vaicāt pēc Touch ID" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Vispirms ir nepieciešams iespējot biometriju darbvirsmas iestatījumos, lai to varētu izmantot pārlūkā." }, + "biometricsManualSetupTitle": { + "message": "Automātiskā uzstādīšana nav pieejama" + }, + "biometricsManualSetupDesc": { + "message": "Uzstādīšanas veida dēļ nevarēja automātiski iespējot biometrijas nodrošinājumu. Vai atvērt dokumentāciju par to, kā to izdarīt pašrocīgi?" + }, "personalOwnershipSubmitError": { "message": "Uzņēmuma nosacījumi liedz saglabāt vienumus privātajā glabātavā. Ir jānorāda piederība apvienībai un jāizvēlas kāds no pieejamajiem krājumiem." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Galvenā parole neatbilst vienam vai vairākiem apvienības nosacījumiem. Ir jāatjaunina galvenā parole, lai varētu piekļūt glabātavai. Turpinot notiks atteikšanās no pašreizējās sesijas, un būs nepieciešams pieteikties no jauna. Citās ierīcēs esošās sesijas var turpināt darboties līdz vienai stundai." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Tava apvienība ir atspējojusi uzticamo ierīču šifrēšanu. Lūgums iestatīt galveno paroli, lai piekļūtu savai glabātavai." + }, "tryAgain": { "message": "Jāmēģina vēlreiz" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Dati" + }, + "fileSends": { + "message": "Datņu Send" + }, + "textSends": { + "message": "Teksta Send" } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 6c70b5e5e6c..dd6850cfa60 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verifikuj za Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Otključaj sa Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index bd1e9dbd15f..bba2772f621 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Bitwarden വേണ്ടി പരിശോധിച്ചുറപ്പിക്കുക." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Touch ID ഉപയോഗിച്ച് അൺലോക്കുചെയ്യുക" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 6d0573130ff..bc71a9703b1 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index e09bc197d72..a771badfb0e 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index fb2430e75e1..50819e4968f 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -500,7 +500,7 @@ "message": "Opprett en konto" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Velg et sterkt passord" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" @@ -552,7 +552,7 @@ "message": "Master password hint" }, "joinOrganization": { - "message": "Join organization" + "message": "Bli med i organisasjon" }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." @@ -595,7 +595,7 @@ "message": "You successfully logged in" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Du kan lukke dette vinduet" }, "masterPassDoesntMatch": { "message": "Superpassord-bekreftelsen er ikke samsvarende." @@ -634,7 +634,7 @@ "message": "En verifiseringskode er påkrevd." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "Autentiseringen ble avbrutt eller tok for lang tid. Prøv igjen." }, "invalidVerificationCode": { "message": "Ugyldig verifiseringskode" @@ -799,10 +799,10 @@ "message": "Din innloggingsøkt har utløpt." }, "restartRegistration": { - "message": "Restart registration" + "message": "Start registrering på nytt" }, "expiredLink": { - "message": "Expired link" + "message": "Utløpt lenke" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." @@ -1382,7 +1382,7 @@ "message": "This file export will be password protected and require the file password to decrypt." }, "filePassword": { - "message": "File password" + "message": "Filpassord" }, "exportPasswordDescription": { "message": "This password will be used to export and import this file" @@ -1400,7 +1400,7 @@ "message": "Export type" }, "accountRestricted": { - "message": "Account restricted" + "message": "Konto begrenset" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Ytterligere Windows Hello-innstillinger" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Bekreft for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Lås opp med Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Spør etter Windows Hello ved oppstart" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Spør om Touch ID ved oppstart" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Biometri i nettleserutvidelsen krever først aktivering i innstillinger i skrivebordsprogrammet." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "På grunn av bedrifsretningslinjer er du begrenset fra å lagre objekter til ditt personlige hvelv. Endre alternativ for eierskap til en organisasjon og velg blant tilgjengelige samlinger." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Prøv igjen" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index ecce20d5cb8..847da864d3e 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index f143382c846..ebceb9dbe8e 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -442,10 +442,10 @@ "description": "Minimum Special Characters" }, "ambiguous": { - "message": "Dubbelzinnige tekens vermijden" + "message": "Vermijd dubbelzinnige tekens" }, "searchCollection": { - "message": "Verzameling doorzoeken" + "message": "Doorzoek collectie" }, "searchFolder": { "message": "Map doorzoeken" @@ -458,22 +458,22 @@ "description": "Search item type" }, "newAttachment": { - "message": "Nieuwe bijlage toevoegen" + "message": "Voeg nieuwe bijlage toe" }, "deletedAttachment": { - "message": "Bijlage is verwijderd" + "message": "Bijlage verwijderd" }, "deleteAttachmentConfirmation": { "message": "Weet je zeker dat je deze bijlage wilt verwijderen?" }, "attachmentSaved": { - "message": "De bijlage is opgeslagen." + "message": "Bijlage opgeslagen" }, "file": { "message": "Bestand" }, "selectFile": { - "message": "Selecteer een bestand." + "message": "Selecteer een bestand" }, "maxFileSize": { "message": "Maximale bestandsgrootte is 500 MB." @@ -491,22 +491,22 @@ "message": "Weet je zeker dat je deze map wilt verwijderen?" }, "deletedFolder": { - "message": "Map is verwijderd" + "message": "Map verwijderd" }, "loginOrCreateNewAccount": { "message": "Log in of maak een nieuw account aan om toegang te krijgen tot je beveiligde kluis." }, "createAccount": { - "message": "Account aanmaken" + "message": "Maak een account aan" }, "setAStrongPassword": { - "message": "Sterk wachtwoord instellen" + "message": "Stel een sterk wachtwoord in" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Rond het aanmaken van je account af met het instellen van een wachtwoord" }, "logIn": { - "message": "Inloggen" + "message": "Log in" }, "submit": { "message": "Opslaan" @@ -552,7 +552,7 @@ "message": "Hoofdwachtwoordhint" }, "joinOrganization": { - "message": "Lid van organisatie worden" + "message": "Join organisatie" }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Voltooi je lidmaatschap aan deze organisatie door een hoofdwachtwoord in te stellen." @@ -610,16 +610,16 @@ "message": "Er is een onverwachte fout opgetreden." }, "itemInformation": { - "message": "Item" + "message": "Item-informatie" }, "noItemsInList": { "message": "Er zijn geen items om weer te geven." }, "sendVerificationCode": { - "message": "Stuur een verificatiecode naar je e-mail" + "message": "Stuur verificatiecode naar je e-mail" }, "sendCode": { - "message": "Code versturen" + "message": "Verstuur code" }, "codeSent": { "message": "Code verstuurd" @@ -698,7 +698,7 @@ "message": "Gebruik een YubiKey om toegang te krijgen tot je account. Werkt met YubiKey 4, 4 Nano, 4C en Neo-apparaten." }, "duoDescV2": { - "message": "Door Duo Security gegenereerde code invoeren.", + "message": "Voer een door Duo Security gegenereerde code in.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -847,7 +847,7 @@ "message": "Hulp en feedback" }, "getHelp": { - "message": "Hulp vragen" + "message": "Vraag ondersteuning" }, "fileBugReport": { "message": "Rapporteer een fout (bug)" @@ -882,10 +882,10 @@ "message": "Ga naar de webkluis" }, "getMobileApp": { - "message": "Download de mobiele app" + "message": "Download mobiele app" }, "getBrowserExtension": { - "message": "Download de browserextensie" + "message": "Download browserextensie" }, "syncingComplete": { "message": "Synchronisatie voltooid" @@ -979,7 +979,7 @@ "message": "Beveiliging" }, "clearClipboard": { - "message": "Klembord wissen", + "message": "Wis klembord", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { @@ -987,7 +987,7 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "enableFavicon": { - "message": "Websitepictogrammen weergeven" + "message": "Toon website-pictogrammen" }, "faviconDesc": { "message": "Een herkenbare afbeelding naast iedere login weergeven." @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Extra Windows Hello-instellingen" }, + "unlockWithPolkit": { + "message": "Ontgrendel met systeemauthenticatie" + }, "windowsHelloConsentMessage": { "message": "Verifiëren voor Bitwarden." }, + "polkitConsentMessage": { + "message": "Verifieer om Bitwarden te ontgrendelen." + }, "unlockWithTouchId": { "message": "Ontgrendelen met Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Vraag om Windows Hello bij opstarten" }, + "autoPromptPolkit": { + "message": "Vraag naar systeemverificatie bij het opstarten" + }, "autoPromptTouchId": { "message": "Vraag om Touch ID bij opstarten" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Voor browserbiometrie moet je eerst desktopbiometrie inschakelen in de instellingen." }, + "biometricsManualSetupTitle": { + "message": "Automatisch installeren niet beschikbaar" + }, + "biometricsManualSetupDesc": { + "message": "Vanwege de installatiemethode kon biometrische ondersteuning niet automatisch worden ingeschakeld. Wil je de documentatie lezen over hoe je dit handmatig kunt doen?" + }, "personalOwnershipSubmitError": { "message": "Wegens bedrijfsbeleid mag je geen wachtwoorden opslaan in je persoonlijke kluis. Verander het eigenaarschap naar een organisatie en kies uit een van de beschikbare collecties." }, @@ -1977,7 +1992,7 @@ "message": "Wordt verwijderd" }, "webAuthnAuthenticate": { - "message": "Authenticeer WebAuthn" + "message": "Verifieer WebAuthn" }, "hideEmail": { "message": "Verberg mijn e-mailadres voor ontvangers." @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Je hoofdwachtwoord voldoet niet aan en of meerdere oganisatiebeleidsonderdelen. Om toegang te krijgen tot de kluis, moet je je hoofdwachtwoord nu bijwerken. Doorgaan zal je huidige sessie uitloggen, waarna je opnieuw moet inloggen. Actieve sessies op andere apparaten blijven mogelijk nog een uur actief." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Je organisatie heeft het versleutelen van vertrouwde apparaten uitgeschakeld. Stel een hoofdwachtwoord in om toegang te krijgen tot je kluis." + }, "tryAgain": { "message": "Opnieuw proberen" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Gegevens" + }, + "fileSends": { + "message": "Bestand-Sends" + }, + "textSends": { + "message": "Tekst-Sends" } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index ca99bfcbe47..c19b45212ea 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index da8b7401ffc..b82d3631bae 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 06ceca87e97..20258cc6a49 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Dodatkowe ustawienia Windows Hello" }, + "unlockWithPolkit": { + "message": "Odblokuj za pomocą uwierzytelniania systemowego" + }, "windowsHelloConsentMessage": { "message": "Zweryfikuj dla Bitwarden." }, + "polkitConsentMessage": { + "message": "Uwierzytelnij, aby odblokować Bitwarden." + }, "unlockWithTouchId": { "message": "Odblokuj za pomocą Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Poproś o Windows Hello przy uruchomieniu" }, + "autoPromptPolkit": { + "message": "Zapytaj o uwierzytelnianie systemowe przy uruchomieniu" + }, "autoPromptTouchId": { "message": "Poproś o Touch ID przy uruchomieniu" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Aby włączyć dane biometryczne w przeglądarce, musisz włączyć tę samą funkcję w ustawianiach aplikacji." }, + "biometricsManualSetupTitle": { + "message": "Automatyczna konfiguracja niedostępna" + }, + "biometricsManualSetupDesc": { + "message": "Ze względu na metodę instalacji, biometria nie może być automatycznie włączona. Czy chcesz otworzyć dokumentację dotyczącą tego, jak to zrobić ręcznie?" + }, "personalOwnershipSubmitError": { "message": "Ze względu na zasadę przedsiębiorstwa, nie możesz zapisywać elementów w osobistym sejfie. Zmień właściciela elementu na organizację i wybierz jedną z dostępnych kolekcji." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Twoje hasło główne nie spełnia jednej lub kilku zasad organizacji. Aby uzyskać dostęp do sejfu, musisz teraz zaktualizować swoje hasło główne. Kontynuacja wyloguje Cię z bieżącej sesji, wymagając zalogowania się ponownie. Aktywne sesje na innych urządzeniach mogą pozostać aktywne przez maksymalnie jedną godzinę." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Spróbuj ponownie" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Dane" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index b330517b37e..b3d00976b6f 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -45,7 +45,7 @@ "message": "Compartilhar" }, "moveToOrganization": { - "message": "Mover para a Organização" + "message": "Mover para a organização" }, "movedItemToOrg": { "message": "$ITEMNAME$ movido para $ORGNAME$", @@ -527,7 +527,7 @@ "message": "Dica da Senha Mestra (opcional)" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "Se você esquecer sua senha, a dica da senha pode ser enviada ao seu e-mail. $CURRENT$/$MAXIMUM$ caracteres máximos.", "placeholders": { "current": { "content": "$1", @@ -540,22 +540,22 @@ } }, "masterPassword": { - "message": "Master password" + "message": "Senha mestra" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Sua senha mestra não pode ser recuperada se você a esquecer!" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Confirme a senha mestra" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Dica da senha mestra" }, "joinOrganization": { - "message": "Join organization" + "message": "Juntar-se à organização" }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "Termine de juntar-se nessa organização definindo uma senha mestra." }, "settings": { "message": "Configurações" @@ -634,7 +634,7 @@ "message": "Requer o código de verificação." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "A autenticação foi cancelada ou demorou muito. Por favor tente novamente." }, "invalidVerificationCode": { "message": "Código de verificação inválido" @@ -688,17 +688,17 @@ "message": "Aplicativo de Autenticação" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Insira um código gerado por um aplicativo autenticador como o Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "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." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "Insira um código gerado pelo Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -715,7 +715,7 @@ "message": "E-mail" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Digite o código enviado para seu e-mail." }, "loginUnavailable": { "message": "Sessão Indisponível" @@ -799,16 +799,16 @@ "message": "A sua sessão expirou." }, "restartRegistration": { - "message": "Restart registration" + "message": "Reiniciar registro" }, "expiredLink": { - "message": "Expired link" + "message": "Link expirado" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Por favor, reinicie o registro ou tente fazer login." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Você pode já ter uma conta" }, "logOutConfirmation": { "message": "Você tem certeza que deseja sair?" @@ -979,7 +979,7 @@ "message": "Segurança" }, "clearClipboard": { - "message": "Limpar Área de Transferência", + "message": "Limpar área de transferência", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { @@ -993,7 +993,7 @@ "message": "Mostre uma imagem reconhecível ao lado de cada credencial." }, "enableMinToTray": { - "message": "Minimizar para Ícone da Bandeja" + "message": "Minimizar para ícone da bandeja" }, "enableMinToTrayDesc": { "message": "Ao minimizar a janela, mostra um ícone na bandeja do sistema." @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Configurações adicionais do Windows Hello" }, + "unlockWithPolkit": { + "message": "Desbloquear com autenticação de sistema" + }, "windowsHelloConsentMessage": { "message": "Verifique para o Bitwarden." }, + "polkitConsentMessage": { + "message": "Autentice para desbloquear o Bitwarden." + }, "unlockWithTouchId": { "message": "Desbloquear com o Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Perguntar para iniciar o Hello do Windows" }, + "autoPromptPolkit": { + "message": "Pedir autenticação do sistema na inicialização" + }, "autoPromptTouchId": { "message": "Pedir pelo Touch ID ao iniciar" }, @@ -1712,7 +1721,7 @@ "message": "A sua nova senha mestra não cumpre aos requisitos da política." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Obtenha conselhos, novidades, e oportunidades de pesquisa do Bitwarden em sua caixa de entrada." }, "unsubscribe": { "message": "Cancelar subscrição" @@ -1802,7 +1811,13 @@ "message": "Biometria não ativada" }, "biometricsNotEnabledDesc": { - "message": "A biometria com o navegador requer que a biometria de desktop seja ativada nas configurações primeiro." + "message": "A biometria do navegador exige que a biometria do desktop seja configurada primeiro nas configurações." + }, + "biometricsManualSetupTitle": { + "message": "Configuração automática não disponível" + }, + "biometricsManualSetupDesc": { + "message": "Devido ao método de instalação, o suporte a dados biométricos 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ê está restrito de salvar itens para seu cofre pessoal. Altere a opção de Propriedade para uma organização e escolha entre as Coleções disponíveis." @@ -1866,7 +1881,7 @@ "message": "Contagem Atual de Acessos" }, "disableSend": { - "message": "Desative este Send para que ninguém possa acessá-lo.", + "message": "Desative este envio para que ninguém possa acessá-lo.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendPasswordDesc": { @@ -1882,7 +1897,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinkLabel": { - "message": "Link do Send", + "message": "Enviar link", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "textHiddenByDefault": { @@ -1890,26 +1905,26 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send Criado", + "message": "Envio adicionado", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send Editado", + "message": "Envio salvo", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "Send Excluído", + "message": "Enviar excluído", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { - "message": "Nova Senha" + "message": "Nova senha" }, "whatTypeOfSend": { "message": "Que tipo de Send é este?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "Criar Send", + "message": "Novo envio", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -1945,7 +1960,7 @@ "message": "Copiar o link para compartilhar este Send para minha área de transferência ao salvar." }, "sendDisabled": { - "message": "Send desativado", + "message": "Envio removido", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -1986,10 +2001,10 @@ "message": "Uma ou mais políticas da organização estão afetando as suas opções de Send." }, "emailVerificationRequired": { - "message": "Verificação de E-mail Necessária" + "message": "Verificação de e-mail necessária" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "E-mail verificado" }, "emailVerificationRequiredDesc": { "message": "Você precisa verificar o seu e-mail para usar este recurso." @@ -2004,7 +2019,7 @@ "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha mestra para verificar sua identidade." }, "updatedMasterPassword": { - "message": "Senha Mestra Atualizada" + "message": "Senha mestra atualizada" }, "updateMasterPassword": { "message": "Atualizar Senha Mestra" @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "A sua senha mestra não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha mestra agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Sua organização desativou a criptografia confiável do dispositivo. Por favor, defina uma senha mestra para acessar o seu cofre." + }, "tryAgain": { "message": "Tentar novamente" }, @@ -2058,7 +2076,7 @@ "message": "Minutos" }, "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 definiram o tempo limite máximo permitido do cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", "placeholders": { "hours": { "content": "$1", @@ -2100,10 +2118,10 @@ "message": "O tempo limite do seu cofre excede as restrições definidas por sua organização." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Convite aceito" }, "resetPasswordPolicyAutoEnroll": { - "message": "Inscrição Automática" + "message": "Inscrição automática" }, "resetPasswordAutoEnrollInviteWarning": { "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha mestra." @@ -2217,23 +2235,23 @@ "message": "O que você gostaria de gerar?" }, "passwordType": { - "message": "Tipo de Senha" + "message": "Tipo de senha" }, "regenerateUsername": { - "message": "Regenerar Usuário" + "message": "Gerar nome de usuário novamente" }, "generateUsername": { - "message": "Gerar Usuário" + "message": "Gerar usuário" }, "usernameType": { - "message": "Tipo de Usuário" + "message": "Tipo de usuário" }, "plusAddressedEmail": { - "message": "E-mail alternativo (com um +)", + "message": "Mais e-mail endereçado", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Use as capacidades de sub-endereçamento do seu provedor de e-mail." + "message": "Use os recursos de subendereçamento do seu provedor de e-mail." }, "catchallEmail": { "message": "E-mail pega-tudo" @@ -2245,7 +2263,7 @@ "message": "Aleatório" }, "randomWord": { - "message": "Palavra Aleatória" + "message": "Palavra aleatória" }, "websiteName": { "message": "Nome do site" @@ -2745,7 +2763,7 @@ "message": "Submenu" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "Ativar/desativar navegação lateral" }, "skipToContent": { "message": "Ir para o conteúdo" @@ -2803,7 +2821,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "Erro ao se conectar com o serviço Duo. Use um método de verificação de duas etapas diferente ou contate o Duo para assistência." }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicie o Duo e siga os passos para finalizar o login." @@ -2827,7 +2845,7 @@ "message": "Senha do arquivo inválida, por favor informe a senha utilizada quando criou o arquivo de exportação." }, "destination": { - "message": "Destination" + "message": "Destino" }, "learnAboutImportOptions": { "message": "Saiba mais sobre suas opções de importação" @@ -2886,7 +2904,7 @@ "message": "Confirmar senha do arquivo" }, "exportSuccess": { - "message": "Vault data exported" + "message": "Dados do cofre exportados" }, "multifactorAuthenticationCancelled": { "message": "Autenticação de múltiplos fatores cancelada" @@ -3024,6 +3042,12 @@ } }, "data": { - "message": "Data" + "message": "Dado" + }, + "fileSends": { + "message": "Arquivos enviados" + }, + "textSends": { + "message": "Texto enviado" } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 7e98dcae50e..e95095e56c7 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Definições adicionais do Windows Hello" }, + "unlockWithPolkit": { + "message": "Desbloqueio com autenticação do sistema" + }, "windowsHelloConsentMessage": { "message": "Verificar para o Bitwarden." }, + "polkitConsentMessage": { + "message": "Autenticar para desbloquear o Bitwarden." + }, "unlockWithTouchId": { "message": "Desbloquear com Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Pedir o Windows Hello ao iniciar a aplicação" }, + "autoPromptPolkit": { + "message": "Pedir a autenticação do sistema no arranque" + }, "autoPromptTouchId": { "message": "Pedir o Touch ID ao iniciar a aplicação" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "A biometria do navegador requer que a biometria do computador seja primeiro configurada nas definições." }, + "biometricsManualSetupTitle": { + "message": "Configuração automática não disponível" + }, + "biometricsManualSetupDesc": { + "message": "Devido ao método de instalação, não foi possível ativar automaticamente o suporte biométrico. Gostaria de abrir a documentação sobre como o fazer manualmente?" + }, "personalOwnershipSubmitError": { "message": "Devido a uma política empresarial, está impedido de guardar itens no seu cofre pessoal. Altere a opção Propriedade para uma organização e escolha entre as coleções disponíveis." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "A sua palavra-passe mestra não cumpre uma ou mais políticas da sua organização. Para aceder ao cofre, tem de atualizar a sua palavra-passe mestra agora. Ao prosseguir, terminará a sua sessão atual e terá de iniciar sessão novamente. As sessões ativas noutros dispositivos poderão continuar ativas até uma hora." }, + "tdeDisabledMasterPasswordRequired": { + "message": "A sua organização desativou a encriptação de dispositivos fiáveis. Por favor, defina uma palavra-passe mestra para aceder ao seu cofre." + }, "tryAgain": { "message": "Tentar novamente" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Dados" + }, + "fileSends": { + "message": "Sends de ficheiros" + }, + "textSends": { + "message": "Sends de texto" } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index e62db26b816..209fc4dc3e5 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verificați pentru Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Deblocare cu Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Solicitați Windows Hello la pornire" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Solicitați Touch ID la pornire" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Biometria browserului necesită ca mai întâi să fie configurată biometria desktopului în setări." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Din cauza unei politici a întreprinderii, nu vă puteți salva elemente în seiful individual. Schimbați opțiunea de proprietate la o organizație și alegeți din colecțiile disponibile." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index fdb2e124830..c4770627505 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Дополнительные настройки Windows Hello" }, + "unlockWithPolkit": { + "message": "Разблокировать с помощью системной аутентификации" + }, "windowsHelloConsentMessage": { "message": "Верификация для Bitwarden." }, + "polkitConsentMessage": { + "message": "Для разблокировки Bitwarden пройдите аутентификацию." + }, "unlockWithTouchId": { "message": "Разблокировать с Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Запрашивать Windows Hello при запуске приложения" }, + "autoPromptPolkit": { + "message": "Запрашивать системную аутентификацию при запуске" + }, "autoPromptTouchId": { "message": "Запрашивать Touch ID при запуске приложения" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Для активации биометрии в браузере сначала необходимо включить биометрию в приложении для компьютера." }, + "biometricsManualSetupTitle": { + "message": "Автоматическая настройка недоступна" + }, + "biometricsManualSetupDesc": { + "message": "Из-за метода инсталляции не удалось автоматически включить поддержку биометрии. Вы хотите открыть документацию чтобы узнать, как это сделать вручную?" + }, "personalOwnershipSubmitError": { "message": "В соответствии с корпоративной политикой вам запрещено сохранять элементы в личном хранилище. Измените владельца на организацию и выберите из доступных Коллекций." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Ваш мастер-пароль не соответствует требованиям политики вашей организации. Для доступа к хранилищу вы должны обновить свой мастер-пароль прямо сейчас. При этом текущий сеанс будет завершен и потребуется повторная авторизация. Сеансы на других устройствах могут оставаться активными в течение часа." }, + "tdeDisabledMasterPasswordRequired": { + "message": "В вашей организации отключено шифрование доверенных устройств. Пожалуйста, установите мастер-пароль для доступа к вашему хранилищу." + }, "tryAgain": { "message": "Попробуйте снова" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Данные" + }, + "fileSends": { + "message": "Файловая Send" + }, + "textSends": { + "message": "Текстовая Send" } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index b2f2469ccf3..29a230aba1e 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 5edd4f4880a..4d39730d0a7 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Ďalšie nastavenia Windows Hello" }, + "unlockWithPolkit": { + "message": "Odomknúť systémovým overením" + }, "windowsHelloConsentMessage": { "message": "Overiť sa pre Bitwarden." }, + "polkitConsentMessage": { + "message": "Overením odomknete Bitwarden." + }, "unlockWithTouchId": { "message": "Odomknúť s Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Pri spustení požiadať o Windows Hello" }, + "autoPromptPolkit": { + "message": "Pri spustení požiadať o systémové overenie" + }, "autoPromptTouchId": { "message": "Pri spustení požiadať o Touch ID" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Biometria prehliadača vyžaduje, aby bola najskôr v nastaveniach povolená biometria počítača." }, + "biometricsManualSetupTitle": { + "message": "Automatické nastavenie nie je k dispozícii" + }, + "biometricsManualSetupDesc": { + "message": "Vzhľadom na spôsob inštalácie nebolo možné automaticky povoliť podporu biometrie. Chcete otvoriť dokumentáciu, ako to urobiť manuálne?" + }, "personalOwnershipSubmitError": { "message": "Z dôvodu podnikovej politiky máte obmedzené ukladanie položiek do osobného trezora. Zmeňte možnosť vlastníctvo na organizáciu a vyberte si z dostupných zbierok." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Vaše hlavné heslo nespĺňa jednu alebo viacero podmienok vašej organizácie. Ak chcete získať prístup k trezoru, musíte teraz aktualizovať svoje hlavné heslo. Pokračovaním sa odhlásite z aktuálnej relácie a budete sa musieť znova prihlásiť. Aktívne relácie na iných zariadeniach môžu zostať aktívne až jednu hodinu." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Vaša organizácia zakázala šifrovanie dôveryhodného zariadenia. Na prístup k trezoru nastavte hlavné heslo." + }, "tryAgain": { "message": "Skúsiť znova" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Údaje" + }, + "fileSends": { + "message": "Sendy so súborom" + }, + "textSends": { + "message": "Textové Sendy" } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 7916795d1a4..c19ec0ab516 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Preverite za Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Odkleni z biometriko" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Biometrično preverjanje ob zagonu aplikacije" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 172875b1ee5..13ec3acf231 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Додатна подешавања Windows Hello" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Потврди за Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Откључај са Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Захтевај Windows Hello при покретању" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Захтевај Touch ID при покретању" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Биометрија прегледача захтева да у поставкама прво буде омогућена биометрија desktop-а." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Због смерница за предузећа, ограничено вам је чување предмета у вашем личном трезору. Промените опцију власништва у организацију и изаберите из доступних колекција." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Ваша главна лозинка не испуњава једну или више смерница ваше организације. Да бисте приступили сефу, морате одмах да ажурирате главну лозинку. Ако наставите, одјавићете се са ваше тренутне сесије, што захтева да се поново пријавите. Активне сесије на другим уређајима могу да остану активне до један сат." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Ваша организација је онемогућила шифровање поузданог уређаја. Поставите главну лозинку за приступ вашем трезору." + }, "tryAgain": { "message": "Покушајте поново" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Подаци" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index b164bb5f19e..77badeaa0bf 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Ytterligare inställningar för Windows Hello" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Bekräfta för Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Lås upp med Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Be om Windows Hello vid appstart" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Be om Touch ID vid appstart" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Biometri i webbläsaren kräver att biometri på skrivbordet aktiveras i inställningarna först." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "På grund av en av företagets policyer är du begränsad från att spara objekt till ditt personliga valv. Ändra ägarskap till en organisation och välj från tillgängliga samlingar." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Ditt huvudlösenord följer inte ett eller flera av din organisations regler. För att komma åt ditt valv så måste du ändra ditt huvudlösenord nu. Om du gör det kommer du att loggas du ut ur din nuvarande session så du måste logga in på nytt. Aktiva sessioner på andra enheter kommer fortsatt vara aktiva i upp till en timme." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Försök igen" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 6d0573130ff..bc71a9703b1 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 2cd71c42ebb..54bc05182b5 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Additional Windows Hello settings" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Verify for Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Unlock with Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Ask for Windows Hello on app start" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Ask for Touch ID on app start" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 49579f05845..4fcd925335f 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Ekstra Windows Hello ayarları" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Bitwarden için doğrulayın." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Kilidi Touch ID ile aç" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Uygulamayı başlatırken Windows Hello doğrulaması iste" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Uygulamayı başlatırken Touch ID iste" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Tarayıcıda biyometriyi kullanmak için önce ayarlardan masaüstü biyometrisini ayarlamanız gerekir." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Bir kuruluş ilkesi nedeniyle kişisel kasanıza hesap kaydetmeniz kısıtlanmış. Sahip seçeneğini bir kuruluş olarak değiştirin ve mevcut koleksiyonlar arasından seçim yapın." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Ana parolanız kuruluş ilkelerinizi karşılamıyor. Kasanıza erişmek için ana parolanızı güncellemelisiniz. Devam ettiğinizde oturumunuz kapanacak ve yeniden oturum açmanız gerekecektir. Diğer cihazlardaki aktif oturumlar bir saate kadar aktif kalabilir." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Yeniden dene" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Veri" + }, + "fileSends": { + "message": "Dosya Send'leri" + }, + "textSends": { + "message": "Metin Send'leri" } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index b015beacb10..ce6697faf65 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -21,7 +21,7 @@ "message": "Картка" }, "typeIdentity": { - "message": "Особисті дані" + "message": "Посвідчення" }, "typeSecureNote": { "message": "Захищена нотатка" @@ -151,7 +151,7 @@ "message": "Код безпеки" }, "identityName": { - "message": "Назва" + "message": "Назва посвідчення" }, "company": { "message": "Компанія" @@ -346,7 +346,7 @@ "message": "Видалити" }, "nameRequired": { - "message": "Потрібна назва." + "message": "Необхідно ввести назву." }, "addedItem": { "message": "Запис додано" @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Додаткові налаштування Windows Hello" }, + "unlockWithPolkit": { + "message": "Розблокувати за допомогою системної автентифікації" + }, "windowsHelloConsentMessage": { "message": "Перевірити на Bitwarden." }, + "polkitConsentMessage": { + "message": "Автентифікуйтесь для розблокування Bitwarden." + }, "unlockWithTouchId": { "message": "Розблокувати з Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Запитувати Windows Hello під час запуску" }, + "autoPromptPolkit": { + "message": "Запитувати системну автентифікацію під час запуску" + }, "autoPromptTouchId": { "message": "Запитувати Touch ID під час запуску" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Для використання біометрії в браузері необхідно спершу налаштувати її в програмі на комп'ютері." }, + "biometricsManualSetupTitle": { + "message": "Автоналаштування недоступне" + }, + "biometricsManualSetupDesc": { + "message": "У зв'язку з методом встановлення, не можна автоматично ввімкнути підтримку біометрії. Бажаєте переглянути документацію про те, як зробити це вручну?" + }, "personalOwnershipSubmitError": { "message": "Згідно з політикою компанії, вам заборонено зберігати записи в особистому сховищі. Змініть опцію власника на організацію та виберіть серед доступних збірок." }, @@ -1814,7 +1829,7 @@ "message": "Політика організації впливає на ваші параметри власності." }, "personalOwnershipPolicyInEffectImports": { - "message": "Політика організації заблокувала імпортування елементів до вашого особистого сховища." + "message": "Політика організації заблокувала імпортування записів до вашого особистого сховища." }, "allSends": { "message": "Усі відправлення", @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Ваш головний пароль не відповідає одній або більше політикам вашої організації. Щоб отримати доступ до сховища, вам необхідно оновити свій головний пароль зараз. Продовживши, ви вийдете з поточного сеансу, після чого потрібно буде повторно виконати вхід. Сеанси на інших пристроях можуть залишатися активними протягом однієї години." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Ваша організація вимкнула шифрування довірених пристроїв. Встановіть головний пароль для доступу до сховища." + }, "tryAgain": { "message": "Спробуйте знову" }, @@ -2196,7 +2214,7 @@ "message": "Експортування сховища організації" }, "exportingOrganizationVaultDesc": { - "message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Елементи особистих сховищ або інших організацій не будуть включені.", + "message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Записи особистих сховищ або інших організацій не будуть включені.", "placeholders": { "organization": { "content": "$1", @@ -2782,7 +2800,7 @@ "message": "Дані успішно імпортовано" }, "importSuccessNumberOfItems": { - "message": "Всього імпортовано $AMOUNT$ елементів.", + "message": "Всього імпортовано $AMOUNT$ записів.", "placeholders": { "amount": { "content": "$1", @@ -2849,7 +2867,7 @@ } }, "importUnassignedItemsError": { - "message": "Файл містить непризначені елементи." + "message": "Файл містить непризначені записи." }, "selectFormat": { "message": "Оберіть формат імпортованого файлу" @@ -3025,5 +3043,11 @@ }, "data": { "message": "Дані" + }, + "fileSends": { + "message": "Відправлення файлів" + }, + "textSends": { + "message": "Відправлення тексту" } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 9e5b6b5e36a..035c5bb845f 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "Cài đặt Windows Hello bổ sung" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "Xác minh cho Bitwarden." }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "Mở khóa với Touch ID" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "Yêu cầu xác minh Windows Hello khi mở" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "Yêu cầu xác minh Touch ID khi mở" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "Sinh trắc học trên trình duyệt yêu cầu sinh trắc học trên máy tính phải được cài đặt trước." }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "Do chính sách doanh nghiệp, bạn bị hạn chế lưu các mục vào kho cá nhân của mình. Thay đổi tùy chọn quyền sở hữu thành một tổ chức và chọn từ các bộ sưu tập có sẵn." }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "Mật khẩu chính của bạn không đáp ứng chính sách tổ chức của bạn. Để truy cập kho, bạn phải cập nhật mật khẩu chính của mình ngay bây giờ. Việc tiếp tục sẽ đăng xuất bạn khỏi phiên hiện tại và bắt buộc đăng nhập lại. Các phiên hoạt động trên các thiết bị khác có thể tiếp tục duy trì hoạt động trong tối đa một giờ." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Thử lại" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Dữ liệu" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 36f4bad1a07..a715d09c9ac 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -157,7 +157,7 @@ "message": "公司" }, "ssn": { - "message": "社会保险号码" + "message": "社会保障号码" }, "passportNumber": { "message": "护照号码" @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "额外的 Windows Hello 设置" }, + "unlockWithPolkit": { + "message": "使用系统身份验证解锁" + }, "windowsHelloConsentMessage": { "message": "验证 Bitwarden。" }, + "polkitConsentMessage": { + "message": "验证以解锁 Bitwarden。" + }, "unlockWithTouchId": { "message": "使用触控 ID 解锁" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "应用程序启动时要求使用 Windows Hello" }, + "autoPromptPolkit": { + "message": "启动时请求系统身份验证" + }, "autoPromptTouchId": { "message": "应用程序启动时要求使用触控 ID" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "需要先在桌面应用程序的设置中设置生物识别,才能使用浏览器中的生物识别。" }, + "biometricsManualSetupTitle": { + "message": "自动设置不可用" + }, + "biometricsManualSetupDesc": { + "message": "由于安装方式的原因,生物识别支持无法自动启用。您想要打开关于如何手动执行此操作的文档吗?" + }, "personalOwnershipSubmitError": { "message": "由于某个企业策略,您不能将项目保存到您的个人密码库。将所有权选项更改为组织,并从可用的集合中选择。" }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, + "tdeDisabledMasterPasswordRequired": { + "message": "您的组织禁用了信任设备加密。要访问您的密码库,请设置一个主密码。" + }, "tryAgain": { "message": "请重试" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "数据" + }, + "fileSends": { + "message": "文件 Send" + }, + "textSends": { + "message": "文本 Send" } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 33d7acd2340..96c358d1482 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -1510,9 +1510,15 @@ "additionalWindowsHelloSettings": { "message": "額外的 Windows Hello 設定" }, + "unlockWithPolkit": { + "message": "Unlock with system authentication" + }, "windowsHelloConsentMessage": { "message": "驗證 Bitwarden。" }, + "polkitConsentMessage": { + "message": "Authenticate to unlock Bitwarden." + }, "unlockWithTouchId": { "message": "使用 Touch ID 解鎖" }, @@ -1525,6 +1531,9 @@ "autoPromptWindowsHello": { "message": "啟動時詢問 Windows Hello" }, + "autoPromptPolkit": { + "message": "Ask for system authentication on launch" + }, "autoPromptTouchId": { "message": "啟動時詢問 Touch ID" }, @@ -1804,6 +1813,12 @@ "biometricsNotEnabledDesc": { "message": "需先在桌面應用程式的設定中啟用生物特徵辨識,才能使用瀏覽器的生物特徵辨識。" }, + "biometricsManualSetupTitle": { + "message": "Autometic setup not available" + }, + "biometricsManualSetupDesc": { + "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + }, "personalOwnershipSubmitError": { "message": "由於某個企業原則,您被限制為儲存項目到您的個人密碼庫。將擁有權變更為組織,並從可用的集合中選擇。" }, @@ -2015,6 +2030,9 @@ "updateWeakMasterPasswordWarning": { "message": "您的主密碼不符合一個或多個組織原則要求。您必須立即更新您的主密碼才能存取密碼庫。進行此動作將登出您目前的工作階段,需要您重新登入。其他裝置上的工作階段可能繼續長達一小時。" }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "再試一次" }, @@ -3025,5 +3043,11 @@ }, "data": { "message": "Data" + }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" } } From cbe7ae68cc031c322957eb706b4718b858517fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Fri, 9 Aug 2024 08:54:00 -0400 Subject: [PATCH 20/59] [PM-10107] evaluate the override password type policy (#10277) --- .../components/generator.component.ts | 20 ++++++-- .../password-generator-policy-options.ts | 4 +- .../core/src/data/generator-types.ts | 5 ++ libs/tools/generator/core/src/data/index.ts | 1 + .../core/src/types/generator-type.ts | 7 ++- ...legacy-password-generation.service.spec.ts | 8 ++-- .../src/legacy-password-generation.service.ts | 2 +- ...fault-generator-navigation.service.spec.ts | 2 +- .../generator-navigation-evaluator.spec.ts | 46 +++++++++---------- .../src/generator-navigation-evaluator.ts | 17 ++++--- .../src/generator-navigation-policy.spec.ts | 16 +++---- .../src/generator-navigation-policy.ts | 12 +++-- 12 files changed, 84 insertions(+), 56 deletions(-) create mode 100644 libs/tools/generator/core/src/data/generator-types.ts diff --git a/libs/angular/src/tools/generator/components/generator.component.ts b/libs/angular/src/tools/generator/components/generator.component.ts index 94bd3fc2876..b68e861a061 100644 --- a/libs/angular/src/tools/generator/components/generator.component.ts +++ b/libs/angular/src/tools/generator/components/generator.component.ts @@ -34,7 +34,6 @@ export class GeneratorComponent implements OnInit, OnDestroy { usernameGeneratingPromise: Promise; typeOptions: any[]; - passTypeOptions: any[]; usernameTypeOptions: any[]; subaddressOptions: any[]; catchallOptions: any[]; @@ -48,6 +47,11 @@ export class GeneratorComponent implements OnInit, OnDestroy { enforcedPasswordPolicyOptions: PasswordGeneratorPolicyOptions; usernameWebsite: string = null; + get passTypeOptions() { + return this._passTypeOptions.filter((o) => !o.disabled); + } + private _passTypeOptions: { name: string; value: GeneratorType; disabled: boolean }[]; + private destroy$ = new Subject(); private isInitialized$ = new BehaviorSubject(false); @@ -79,9 +83,9 @@ export class GeneratorComponent implements OnInit, OnDestroy { { name: i18nService.t("password"), value: "password" }, { name: i18nService.t("username"), value: "username" }, ]; - this.passTypeOptions = [ - { name: i18nService.t("password"), value: "password" }, - { name: i18nService.t("passphrase"), value: "passphrase" }, + this._passTypeOptions = [ + { name: i18nService.t("password"), value: "password", disabled: false }, + { name: i18nService.t("passphrase"), value: "passphrase", disabled: false }, ]; this.usernameTypeOptions = [ { @@ -138,6 +142,14 @@ export class GeneratorComponent implements OnInit, OnDestroy { this.passwordOptions.type = this.passwordOptions.type === "passphrase" ? "passphrase" : "password"; + const overrideType = this.enforcedPasswordPolicyOptions.overridePasswordType ?? ""; + const isDisabled = overrideType.length + ? (value: string, policyValue: string) => value !== policyValue + : (_value: string, _policyValue: string) => false; + for (const option of this._passTypeOptions) { + option.disabled = isDisabled(option.value, overrideType); + } + if (this.usernameOptions.type == null) { this.usernameOptions.type = "word"; } diff --git a/libs/common/src/admin-console/models/domain/password-generator-policy-options.ts b/libs/common/src/admin-console/models/domain/password-generator-policy-options.ts index c52962a0a1b..4ce27173ca2 100644 --- a/libs/common/src/admin-console/models/domain/password-generator-policy-options.ts +++ b/libs/common/src/admin-console/models/domain/password-generator-policy-options.ts @@ -5,7 +5,7 @@ import Domain from "../../../platform/models/domain/domain-base"; */ export class PasswordGeneratorPolicyOptions extends Domain { /** The default kind of credential to generate */ - defaultType: "password" | "passphrase" | "" = ""; + overridePasswordType: "password" | "passphrase" | "" = ""; /** The minimum length of generated passwords. * When this is less than or equal to zero, it is ignored. @@ -70,7 +70,7 @@ export class PasswordGeneratorPolicyOptions extends Domain { */ inEffect() { return ( - this.defaultType || + this.overridePasswordType || this.minLength > 0 || this.numberCount > 0 || this.specialCount > 0 || diff --git a/libs/tools/generator/core/src/data/generator-types.ts b/libs/tools/generator/core/src/data/generator-types.ts new file mode 100644 index 00000000000..c68131f6042 --- /dev/null +++ b/libs/tools/generator/core/src/data/generator-types.ts @@ -0,0 +1,5 @@ +/** Types of passwords that may be configured by the password generator */ +export const PasswordTypes = Object.freeze(["password", "passphrase"] as const); + +/** Types of generators that may be configured by the password generator */ +export const GeneratorTypes = Object.freeze([...PasswordTypes, "username"] as const); diff --git a/libs/tools/generator/core/src/data/index.ts b/libs/tools/generator/core/src/data/index.ts index d87e7f7c529..b4aa4fe3b93 100644 --- a/libs/tools/generator/core/src/data/index.ts +++ b/libs/tools/generator/core/src/data/index.ts @@ -17,3 +17,4 @@ export * from "./forwarders"; export * from "./integrations"; export * from "./policies"; export * from "./username-digits"; +export * from "./generator-types"; diff --git a/libs/tools/generator/core/src/types/generator-type.ts b/libs/tools/generator/core/src/types/generator-type.ts index f17eeb9c92b..717b0e994e1 100644 --- a/libs/tools/generator/core/src/types/generator-type.ts +++ b/libs/tools/generator/core/src/types/generator-type.ts @@ -1,2 +1,7 @@ +import { GeneratorTypes, PasswordTypes } from "../data/generator-types"; + /** The kind of credential being generated. */ -export type GeneratorType = "password" | "passphrase" | "username"; +export type GeneratorType = (typeof GeneratorTypes)[number]; + +/** The kinds of passwords that can be generated. */ +export type PasswordType = (typeof PasswordTypes)[number]; diff --git a/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts b/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts index 57d618c04a4..4c1dee7a5d9 100644 --- a/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts +++ b/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts @@ -270,7 +270,7 @@ describe("LegacyPasswordGenerationService", () => { const navigation = createNavigationGenerator( {}, { - defaultType: "password", + overridePasswordType: "password", }, ); const generator = new LegacyPasswordGenerationService( @@ -284,7 +284,7 @@ describe("LegacyPasswordGenerationService", () => { const [, policy] = await generator.getOptions(); expect(policy).toEqual({ - defaultType: "password", + overridePasswordType: "password", minLength: 20, numberCount: 10, specialCount: 11, @@ -402,7 +402,7 @@ describe("LegacyPasswordGenerationService", () => { const navigation = createNavigationGenerator( {}, { - defaultType: "password", + overridePasswordType: "password", }, ); const generator = new LegacyPasswordGenerationService( @@ -416,7 +416,7 @@ describe("LegacyPasswordGenerationService", () => { const [, policy] = await generator.enforcePasswordGeneratorPoliciesOnOptions({}); expect(policy).toEqual({ - defaultType: "password", + overridePasswordType: "password", minLength: 20, numberCount: 10, specialCount: 11, diff --git a/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.ts b/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.ts index e9bc3bbf2b8..e62e4eb9a19 100644 --- a/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.ts +++ b/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.ts @@ -248,7 +248,7 @@ export class LegacyPasswordGenerationService implements PasswordGenerationServic ...options, ...navigationEvaluator.sanitize(navigationApplied), }; - if (options.type === "password") { + if (navigationSanitized.type === "password") { const applied = passwordEvaluator.applyPolicy(navigationSanitized); const sanitized = passwordEvaluator.sanitize(applied); return [sanitized, policy]; diff --git a/libs/tools/generator/extensions/navigation/src/default-generator-navigation.service.spec.ts b/libs/tools/generator/extensions/navigation/src/default-generator-navigation.service.spec.ts index d1aa5a98a80..1e558ab352e 100644 --- a/libs/tools/generator/extensions/navigation/src/default-generator-navigation.service.spec.ts +++ b/libs/tools/generator/extensions/navigation/src/default-generator-navigation.service.spec.ts @@ -69,7 +69,7 @@ describe("DefaultGeneratorNavigationService", () => { organizationId: "" as any, enabled: true, type: PolicyType.PasswordGenerator, - data: { defaultType: "password" }, + data: { overridePasswordType: "password" }, }), ]); }, diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts index 6fa8f2ef8fc..218121a3a75 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts @@ -4,18 +4,18 @@ import { GeneratorNavigationEvaluator } from "./generator-navigation-evaluator"; describe("GeneratorNavigationEvaluator", () => { describe("policyInEffect", () => { it.each([["passphrase"], ["password"]] as const)( - "returns true if the policy has a defaultType (= %p)", - (defaultType) => { - const evaluator = new GeneratorNavigationEvaluator({ defaultType }); + "returns true if the policy has a overridePasswordType (= %p)", + (overridePasswordType) => { + const evaluator = new GeneratorNavigationEvaluator({ overridePasswordType }); expect(evaluator.policyInEffect).toEqual(true); }, ); it.each([[undefined], [null], ["" as any]])( - "returns false if the policy has a falsy defaultType (= %p)", - (defaultType) => { - const evaluator = new GeneratorNavigationEvaluator({ defaultType }); + "returns false if the policy has a falsy overridePasswordType (= %p)", + (overridePasswordType) => { + const evaluator = new GeneratorNavigationEvaluator({ overridePasswordType }); expect(evaluator.policyInEffect).toEqual(false); }, @@ -23,7 +23,7 @@ describe("GeneratorNavigationEvaluator", () => { }); describe("applyPolicy", () => { - it("returns the input options", () => { + it("returns the input options when a policy is not in effect", () => { const evaluator = new GeneratorNavigationEvaluator(null); const options = { type: "password" as const }; @@ -31,19 +31,27 @@ describe("GeneratorNavigationEvaluator", () => { expect(result).toEqual(options); }); + + it.each([["passphrase"], ["password"]] as const)( + "defaults options to the policy's default type (= %p) when a policy is in effect", + (overridePasswordType) => { + const evaluator = new GeneratorNavigationEvaluator({ overridePasswordType }); + + const result = evaluator.applyPolicy({}); + + expect(result).toEqual({ type: overridePasswordType }); + }, + ); }); describe("sanitize", () => { - it.each([["passphrase"], ["password"]] as const)( - "defaults options to the policy's default type (= %p) when a policy is in effect", - (defaultType) => { - const evaluator = new GeneratorNavigationEvaluator({ defaultType }); + it("retains the options type when it is set", () => { + const evaluator = new GeneratorNavigationEvaluator({ overridePasswordType: "passphrase" }); - const result = evaluator.sanitize({}); + const result = evaluator.sanitize({ type: "password" }); - expect(result).toEqual({ type: defaultType }); - }, - ); + expect(result).toEqual({ type: "password" }); + }); it("defaults options to the default generator navigation type when a policy is not in effect", () => { const evaluator = new GeneratorNavigationEvaluator(null); @@ -52,13 +60,5 @@ describe("GeneratorNavigationEvaluator", () => { expect(result.type).toEqual(DefaultGeneratorNavigation.type); }); - - it("retains the options type when it is set", () => { - const evaluator = new GeneratorNavigationEvaluator({ defaultType: "passphrase" }); - - const result = evaluator.sanitize({ type: "password" }); - - expect(result).toEqual({ type: "password" }); - }); }); }); diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts index 772342a73af..75871e056c4 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts @@ -1,4 +1,4 @@ -import { PolicyEvaluator } from "@bitwarden/generator-core"; +import { PasswordTypes, PolicyEvaluator } from "@bitwarden/generator-core"; import { DefaultGeneratorNavigation } from "./default-generator-navigation"; import { GeneratorNavigation } from "./generator-navigation"; @@ -17,7 +17,7 @@ export class GeneratorNavigationEvaluator /** {@link PolicyEvaluator.policyInEffect} */ get policyInEffect(): boolean { - return this.policy?.defaultType ? true : false; + return PasswordTypes.includes(this.policy?.overridePasswordType); } /** Apply policy to the input options. @@ -25,7 +25,13 @@ export class GeneratorNavigationEvaluator * @returns A new password generation request with policy applied. */ applyPolicy(options: GeneratorNavigation): GeneratorNavigation { - return options; + const result = { ...options }; + + if (this.policyInEffect) { + result.type = this.policy.overridePasswordType ?? result.type; + } + + return result; } /** Ensures internal options consistency. @@ -33,12 +39,9 @@ export class GeneratorNavigationEvaluator * @returns A passphrase generation request with cascade applied. */ sanitize(options: GeneratorNavigation): GeneratorNavigation { - const defaultType = this.policyInEffect - ? this.policy.defaultType - : DefaultGeneratorNavigation.type; return { ...options, - type: options.type ?? defaultType, + type: options.type ?? DefaultGeneratorNavigation.type, }; } } diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.spec.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.spec.ts index 3c90f8a7e87..e4f0b08a3d5 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.spec.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.spec.ts @@ -38,26 +38,26 @@ describe("leastPrivilege", () => { }); it("should take the %p from the policy", () => { - const policy = createPolicy({ defaultType: "passphrase" }); + const policy = createPolicy({ overridePasswordType: "passphrase" }); const result = preferPassword({ ...DisabledGeneratorNavigationPolicy }, policy); - expect(result).toEqual({ defaultType: "passphrase" }); + expect(result).toEqual({ overridePasswordType: "passphrase" }); }); it("should override passphrase with password", () => { - const policy = createPolicy({ defaultType: "password" }); + const policy = createPolicy({ overridePasswordType: "password" }); - const result = preferPassword({ defaultType: "passphrase" }, policy); + const result = preferPassword({ overridePasswordType: "passphrase" }, policy); - expect(result).toEqual({ defaultType: "password" }); + expect(result).toEqual({ overridePasswordType: "password" }); }); it("should not override password", () => { - const policy = createPolicy({ defaultType: "passphrase" }); + const policy = createPolicy({ overridePasswordType: "passphrase" }); - const result = preferPassword({ defaultType: "password" }, policy); + const result = preferPassword({ overridePasswordType: "password" }, policy); - expect(result).toEqual({ defaultType: "password" }); + expect(result).toEqual({ overridePasswordType: "password" }); }); }); diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts index f52344d1fd8..7dcf96c20e5 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts @@ -2,14 +2,14 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; // FIXME: use index.ts imports once policy abstractions and models // implement ADR-0002 import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { GeneratorType } from "@bitwarden/generator-core"; +import { PasswordType } from "@bitwarden/generator-core"; /** Policy settings affecting password generator navigation */ export type GeneratorNavigationPolicy = { /** The type of generator that should be shown by default when opening * the password generator. */ - defaultType?: GeneratorType; + overridePasswordType?: PasswordType; }; /** Reduces a policy into an accumulator by preferring the password generator @@ -27,13 +27,15 @@ export function preferPassword( return acc; } - const isOverridable = acc.defaultType !== "password" && policy.data.defaultType; - const result = isOverridable ? { ...acc, defaultType: policy.data.defaultType } : acc; + const isOverridable = acc.overridePasswordType !== "password" && policy.data.overridePasswordType; + const result = isOverridable + ? { ...acc, overridePasswordType: policy.data.overridePasswordType } + : acc; return result; } /** The default options for password generation policy. */ export const DisabledGeneratorNavigationPolicy: GeneratorNavigationPolicy = Object.freeze({ - defaultType: undefined, + overridePasswordType: null, }); From 46ecde5d63cd6460f5a295ea0f6121edb81f5c19 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:09:40 -0400 Subject: [PATCH 21/59] PM-9603 - AnonLayout Dynamic Content Support - Add ability to avoid translating subtitle as we have designs where a user's email needs to go in the subtitle slot. (#10459) --- .../extension-anon-layout-wrapper.component.ts | 10 +++++++++- .../anon-layout-wrapper.component.ts | 17 +++++++++++++++-- .../anon-layout/anon-layout-wrapper.stories.ts | 5 ++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts index df6e313342b..a16ca5862cf 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts @@ -149,7 +149,15 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy { } if (data.pageSubtitle) { - this.pageSubtitle = this.i18nService.t(data.pageSubtitle); + // If you pass just a string, we translate it by default + if (typeof data.pageSubtitle === "string") { + this.pageSubtitle = this.i18nService.t(data.pageSubtitle); + } else { + // if you pass an object, you can specify if you want to translate it or not + this.pageSubtitle = data.pageSubtitle.translate + ? this.i18nService.t(data.pageSubtitle.subtitle) + : data.pageSubtitle.subtitle; + } } if (data.pageIcon) { diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts index 1c082323b1d..a71f9101c93 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts @@ -10,7 +10,12 @@ import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service export interface AnonLayoutWrapperData { pageTitle?: string; - pageSubtitle?: string; + pageSubtitle?: + | string + | { + subtitle: string; + translate: boolean; + }; pageIcon?: Icon; showReadonlyHostname?: boolean; maxWidth?: "md" | "3xl"; @@ -99,7 +104,15 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { } if (data.pageSubtitle) { - this.pageSubtitle = this.i18nService.t(data.pageSubtitle); + // If you pass just a string, we translate it by default + if (typeof data.pageSubtitle === "string") { + this.pageSubtitle = this.i18nService.t(data.pageSubtitle); + } else { + // if you pass an object, you can specify if you want to translate it or not + this.pageSubtitle = data.pageSubtitle.translate + ? this.i18nService.t(data.pageSubtitle.subtitle) + : data.pageSubtitle.subtitle; + } } if (data.pageIcon) { diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts index 80fabe40612..7e6fda54a82 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts @@ -177,7 +177,10 @@ const initialData: AnonLayoutWrapperData = { const changedData: AnonLayoutWrapperData = { pageTitle: "enterpriseSingleSignOn", - pageSubtitle: "checkYourEmail", + pageSubtitle: { + subtitle: "user@email.com (non-translated)", + translate: false, + }, pageIcon: RegistrationCheckEmailIcon, }; From d1c3ec4a7679dda69c9d653f980d75bc786ac854 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Fri, 9 Aug 2024 11:33:09 -0400 Subject: [PATCH 22/59] [PM-8144] Migrate auto-fill policy logic from Tab to new Vault component (#10450) * little expriment with setting the auto fill policy in the background * removed unused reference * removed ng container --- apps/browser/src/_locales/en/messages.json | 3 +++ .../autofill/popup/settings/autofill.component.html | 4 ++++ .../autofill/popup/settings/autofill.component.ts | 3 +++ .../services/abstractions/autofill.service.ts | 1 + .../src/autofill/services/autofill.service.ts | 13 +++++++++++++ apps/browser/src/background/runtime.background.ts | 3 ++- .../autofill/services/autofill-settings.service.ts | 5 ++--- 7 files changed, 28 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 1af1cb66387..08e22f5f939 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4170,5 +4170,8 @@ }, "systemDefault": { "message": "System default" + }, + "enterprisePolicyRequirementsApplied": { + "message": "Enterprise policy requirements have been applied to this setting" } } diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html index 0933bc54217..77f96612c85 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill.component.html @@ -145,8 +145,12 @@ type="checkbox" (change)="updateAutofillOnPageLoad()" [(ngModel)]="enableAutofillOnPageLoad" + [disabled]="autofillOnPageLoadFromPolicy$ | async" /> {{ "enableAutoFillOnPageLoad" | i18n }} + {{ + "enterprisePolicyRequirementsApplied" | i18n + }} {{ "defaultAutoFillOnPageLoad" | i18n }} diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index 617041a2b12..dc6a4a0880e 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -87,6 +87,9 @@ export class AutofillComponent implements OnInit { DisablePasswordManagerUris.Unknown; protected browserShortcutsURI: BrowserShortcutsUri = BrowserShortcutsUris.Unknown; protected browserClientIsUnknown: boolean; + protected autofillOnPageLoadFromPolicy$ = + this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$; + enableAutofillOnPageLoad = false; enableInlineMenu = false; enableInlineMenuOnIconSelect = false; diff --git a/apps/browser/src/autofill/services/abstractions/autofill.service.ts b/apps/browser/src/autofill/services/abstractions/autofill.service.ts index 9bdb85a3f2a..896b9db95ca 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill.service.ts @@ -81,5 +81,6 @@ export abstract class AutofillService { fromCommand: boolean, cipherType?: CipherType, ) => Promise; + setAutoFillOnPageLoadOrgPolicy: () => Promise; isPasswordRepromptRequired: (cipher: CipherView, tab: chrome.tabs.Tab) => Promise; } diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index d9ae4e99237..c10957b4265 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -570,6 +570,19 @@ export default class AutofillService implements AutofillServiceInterface { return totpCode; } + /** + * Activates the autofill on page load org policy. + */ + async setAutoFillOnPageLoadOrgPolicy(): Promise { + const autofillOnPageLoadOrgPolicy = await firstValueFrom( + this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$, + ); + + if (autofillOnPageLoadOrgPolicy) { + await this.autofillSettingsService.setAutofillOnPageLoad(true); + } + } + /** * Gets the active tab from the current window. * Throws an error if no tab is found. diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 8b216b9a674..660f85617cc 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -228,7 +228,7 @@ export default class RuntimeBackground { // `getAllDecryptedForUrl` and is anticipated to be refactored await this.main.refreshBadge(); await this.main.refreshMenu(false); - + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); break; } case "addToLockedVaultPendingNotifications": @@ -248,6 +248,7 @@ export default class RuntimeBackground { }, 2000); await this.configService.ensureConfigFetched(); await this.main.updateOverlayCiphers(); + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); } break; case "openPopup": diff --git a/libs/common/src/autofill/services/autofill-settings.service.ts b/libs/common/src/autofill/services/autofill-settings.service.ts index eb6191d10bf..123f69550c3 100644 --- a/libs/common/src/autofill/services/autofill-settings.service.ts +++ b/libs/common/src/autofill/services/autofill-settings.service.ts @@ -146,9 +146,8 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti this.autofillOnPageLoadPolicyToastHasDisplayedState = this.stateProvider.getActive( AUTOFILL_ON_PAGE_LOAD_POLICY_TOAST_HAS_DISPLAYED, ); - this.autofillOnPageLoadPolicyToastHasDisplayed$ = this.autofillOnPageLoadState.state$.pipe( - map((x) => x ?? false), - ); + this.autofillOnPageLoadPolicyToastHasDisplayed$ = + this.autofillOnPageLoadPolicyToastHasDisplayedState.state$.pipe(map((x) => x ?? false)); this.autoCopyTotpState = this.stateProvider.getActive(AUTO_COPY_TOTP); this.autoCopyTotp$ = this.autoCopyTotpState.state$.pipe(map((x) => x ?? true)); From a64a676ebd225d3d4f0e2102185aef026357c42c Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Fri, 9 Aug 2024 13:22:13 -0400 Subject: [PATCH 23/59] Added missing feature flag for setting autofill policy on load (#10467) * Added missing feature flag * Fixed comment * reverted change * moved feature flag check higher up --- apps/browser/src/background/runtime.background.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 660f85617cc..33a18fbad2a 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -33,6 +33,7 @@ export default class RuntimeBackground { private pageDetailsToAutoFill: any[] = []; private onInstalledReason: string = null; private lockedVaultPendingNotifications: LockedVaultPendingNotificationsData[] = []; + private extensionRefreshIsActive: boolean = false; constructor( private main: MainBackground, @@ -89,6 +90,10 @@ export default class RuntimeBackground { return false; }; + this.extensionRefreshIsActive = await this.configService.getFeatureFlag( + FeatureFlag.ExtensionRefresh, + ); + this.messageListener.allMessages$ .pipe( mergeMap(async (message: any) => { @@ -228,7 +233,10 @@ export default class RuntimeBackground { // `getAllDecryptedForUrl` and is anticipated to be refactored await this.main.refreshBadge(); await this.main.refreshMenu(false); - await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); + + if (this.extensionRefreshIsActive) { + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); + } break; } case "addToLockedVaultPendingNotifications": @@ -248,7 +256,10 @@ export default class RuntimeBackground { }, 2000); await this.configService.ensureConfigFetched(); await this.main.updateOverlayCiphers(); - await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); + + if (this.extensionRefreshIsActive) { + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); + } } break; case "openPopup": From 5932c981d203e793c582c365916771b6bddf6062 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:03:32 +0000 Subject: [PATCH 24/59] Autosync the updated translations (#10462) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 31 +++- apps/web/src/locales/ar/messages.json | 31 +++- apps/web/src/locales/az/messages.json | 51 ++++-- apps/web/src/locales/be/messages.json | 31 +++- apps/web/src/locales/bg/messages.json | 39 +++- apps/web/src/locales/bn/messages.json | 31 +++- apps/web/src/locales/bs/messages.json | 31 +++- apps/web/src/locales/ca/messages.json | 165 ++++++++++------- apps/web/src/locales/cs/messages.json | 55 ++++-- apps/web/src/locales/cy/messages.json | 31 +++- apps/web/src/locales/da/messages.json | 35 +++- apps/web/src/locales/de/messages.json | 111 +++++++----- apps/web/src/locales/el/messages.json | 155 +++++++++------- apps/web/src/locales/en_GB/messages.json | 37 +++- apps/web/src/locales/en_IN/messages.json | 37 +++- apps/web/src/locales/eo/messages.json | 31 +++- apps/web/src/locales/es/messages.json | 31 +++- apps/web/src/locales/et/messages.json | 31 +++- apps/web/src/locales/eu/messages.json | 31 +++- apps/web/src/locales/fa/messages.json | 31 +++- apps/web/src/locales/fi/messages.json | 59 ++++-- apps/web/src/locales/fil/messages.json | 31 +++- apps/web/src/locales/fr/messages.json | 35 +++- apps/web/src/locales/gl/messages.json | 31 +++- apps/web/src/locales/he/messages.json | 31 +++- apps/web/src/locales/hi/messages.json | 31 +++- apps/web/src/locales/hr/messages.json | 57 ++++-- apps/web/src/locales/hu/messages.json | 55 ++++-- apps/web/src/locales/id/messages.json | 31 +++- apps/web/src/locales/it/messages.json | 31 +++- apps/web/src/locales/ja/messages.json | 55 ++++-- apps/web/src/locales/ka/messages.json | 31 +++- apps/web/src/locales/km/messages.json | 31 +++- apps/web/src/locales/kn/messages.json | 31 +++- apps/web/src/locales/ko/messages.json | 31 +++- apps/web/src/locales/lv/messages.json | 35 +++- apps/web/src/locales/ml/messages.json | 31 +++- apps/web/src/locales/mr/messages.json | 31 +++- apps/web/src/locales/my/messages.json | 31 +++- apps/web/src/locales/nb/messages.json | 31 +++- apps/web/src/locales/ne/messages.json | 31 +++- apps/web/src/locales/nl/messages.json | 35 +++- apps/web/src/locales/nn/messages.json | 31 +++- apps/web/src/locales/or/messages.json | 31 +++- apps/web/src/locales/pl/messages.json | 31 +++- apps/web/src/locales/pt_BR/messages.json | 221 +++++++++++++---------- apps/web/src/locales/pt_PT/messages.json | 35 +++- apps/web/src/locales/ro/messages.json | 31 +++- apps/web/src/locales/ru/messages.json | 35 +++- apps/web/src/locales/si/messages.json | 31 +++- apps/web/src/locales/sk/messages.json | 55 ++++-- apps/web/src/locales/sl/messages.json | 31 +++- apps/web/src/locales/sr/messages.json | 53 ++++-- apps/web/src/locales/sr_CS/messages.json | 31 +++- apps/web/src/locales/sv/messages.json | 41 ++++- apps/web/src/locales/te/messages.json | 31 +++- apps/web/src/locales/th/messages.json | 31 +++- apps/web/src/locales/tr/messages.json | 33 +++- apps/web/src/locales/uk/messages.json | 135 ++++++++------ apps/web/src/locales/vi/messages.json | 31 +++- apps/web/src/locales/zh_CN/messages.json | 77 +++++--- apps/web/src/locales/zh_TW/messages.json | 31 +++- 62 files changed, 2261 insertions(+), 587 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 10e048dccb4..4acd8bbdb56 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum Aantal Woorde" }, - "defaultType": { - "message": "Verstektipe" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Gebruikersvoorkeure" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Lukrake woord" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Diens" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index f8a09828e8b..51a51b96121 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 5c37092cc9e..5b5ead8efbe 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -49,22 +49,22 @@ "message": "Kimlik doğrulayıcı açarı" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Avto-doldurma seçimləri" }, "websiteUri": { - "message": "Website (URI)" + "message": "Veb sayt (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Veb sayt əlavə edildi" }, "addWebsite": { - "message": "Add website" + "message": "Veb sayt əlavə et" }, "deleteWebsite": { - "message": "Delete website" + "message": "Veb saytı sil" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "İlkin ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "$WEBSITE$ ilə uyuşma aşkarlamasını göstər", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "$WEBSITE$ ilə uyuşma aşkarlamasını gizlət", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Səhifə yüklənəndə avto-doldurulsun?" }, "number": { "message": "Nömrə" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum söz sayı" }, - "defaultType": { - "message": "İlkin növ" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "İstifadəçi tərcihi" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Təsadüfi söz" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Xidmət" }, @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Xana əlavə et" }, "items": { "message": "Elementlər" @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "satın alınmış yerlər silindi" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "$COUNT$ üzv üçün", "placeholders": { diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 1d748799e45..e83a65f4642 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Мінімум лічбаў або слоў" }, - "defaultType": { - "message": "Прадвызначаны тып" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Параметры карыстальніка" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Выпадковае слова" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Сэрвіс" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index bc83f0038e2..1131c36192b 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Минимален брой думи" }, - "defaultType": { - "message": "Стандартен вид" + "overridePasswordTypePolicy": { + "message": "Тип парола", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Потребителски настройки" @@ -5323,7 +5324,7 @@ "message": "Вашата главна парола не отговаря на една или повече политики на организацията Ви. За да получите достъп до трезора, трябва да промените главната си парола сега. Това означава, че ще бъдете отписан(а) от текущата си сесия и ще трябва да се впишете отново. Активните сесии на други устройства може да продължат да бъдат активни още един час." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Вашата организация е променила настройките за шифроване. Задайте главна парола, за да получите достъп до трезора си." }, "maximumVaultTimeout": { "message": "Време за достъп" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Произволна дума" }, + "usernameGenerator": { + "message": "Генератор на потребителски имена" + }, + "useThisPassword": { + "message": "Използване на тази парола" + }, + "useThisUsername": { + "message": "Използване на това потребителско име" + }, + "securePasswordGenerated": { + "message": "Създадена е сигурна парола! Не забравяйте и да промените паролата си в уеб сайта." + }, + "useGeneratorHelpTextPartOne": { + "message": "Използвайте генератора", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "за да създадете сигурна и уникална парола", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Услуга" }, @@ -6785,7 +6806,7 @@ "description": "Message to be displayed when there is an error during bulk project deletion." }, "softDeleteSuccessToast": { - "message": "Secret sent to trash", + "message": "Тайната е преместена в кошчето", "description": "Notification to be displayed when a secret is successfully sent to the trash." }, "hardDeleteSuccessToast": { @@ -7678,7 +7699,7 @@ "message": "Заявено е одобрение на устройство." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Потребителят е задал главна парола в следствие на деактивирането на шифроването чрез доверени устройства." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Започнете свой безплатен 7-дневен пробен период на Битуорден за $ORG$", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "Файлови изпращания" + }, + "textSends": { + "message": "Текстови изпращания" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { @@ -8870,7 +8897,7 @@ "message": "Достъп до създаване на групи" }, "syncGroupsAndUsersFromDirectory": { - "message": "Синхронизиране на групите и потребителите от директория" + "message": "Синхронизиране на групите и потребителите с директорийни услуги" }, "upgradeFamilyMessage": { "message": "Споделяне със семейство и приятели" diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index b732c124481..81bd06d6425 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 5cee54ff2f1..a8b3cb41e27 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index c9c8d9102f9..d818b2accff 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -43,28 +43,28 @@ "message": "Nom del titular de la targeta" }, "loginCredentials": { - "message": "Login credentials" + "message": "Credencials d'inici de sessió" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Clau autenticadora" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Opcions d'emplenament automàtic" }, "websiteUri": { - "message": "Website (URI)" + "message": "Lloc web (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Lloc web afegit" }, "addWebsite": { - "message": "Add website" + "message": "Afig un lloc web" }, "deleteWebsite": { - "message": "Delete website" + "message": "Suprimeix el lloc web" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Per defecte ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Habilita l'emplenament automàtic en carregar la pàgina?" }, "number": { "message": "Número" @@ -191,10 +191,10 @@ "message": "Clau d'autenticació (TOTP)" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Feu que la verificació en dos passos siga perfecta" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden pot emmagatzemar i omplir codis de verificació en dos passos. Copieu i enganxeu la clau en aquest camp." }, "totpHelperWithCapture": { "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." @@ -235,7 +235,7 @@ "description": "This is the folder for uncategorized items" }, "selfOwnershipLabel": { - "message": "You", + "message": "Tu", "description": "Used as a label to indicate that the user is the owner of an item." }, "addFolder": { @@ -467,10 +467,10 @@ "message": "Element" }, "itemDetails": { - "message": "Item details" + "message": "Detalls de l'element" }, "itemName": { - "message": "Item name" + "message": "Nom d'element" }, "cannotRemoveViewOnlyCollections": { "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", @@ -695,13 +695,13 @@ "message": "Restart registration" }, "expiredLink": { - "message": "Expired link" + "message": "Enllaç caducat" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "És possible que ja tingueu un compte" }, "logOutConfirmation": { "message": "Segur que voleu tancar la sessió?" @@ -824,10 +824,10 @@ "message": "Crea un compte" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Estableix una contrasenya segura" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Acabeu de crear el vostre compte establint una contrasenya" }, "newAroundHere": { "message": "Nou per ací?" @@ -839,7 +839,7 @@ "message": "Inicia sessió" }, "verifyIdentity": { - "message": "Verify your Identity" + "message": "Verificació de la vostra identitat" }, "logInInitiated": { "message": "S'ha iniciat la sessió" @@ -878,7 +878,7 @@ "message": "Pista de la contrasenya mestra" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "Si oblideu la contrasenya, la pista de contrasenya es pot enviar al vostre correu electrònic. $CURRENT$/$MAXIMUM$ caràcters màxim.", "placeholders": { "current": { "content": "$1", @@ -946,7 +946,7 @@ "message": "Adreça electrònica" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "La caixa forta està bloquejada" }, "uuid": { "message": "UUID" @@ -1077,7 +1077,7 @@ "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "Clau de seguretat OTP de Yubico" }, "yubiKeyDesc": { "message": "Utilitzeu una YubiKey per accedir al vostre compte. Funciona amb els dispositius YubiKey 4, 4 Nano, 4C i NEO." @@ -1151,7 +1151,7 @@ "message": "Esteu segur que voleu continuar?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "Trieu una carpeta a la que vulgueu afegir els $COUNT$ elements seleccionats.", "placeholders": { "count": { "content": "$1", @@ -1486,7 +1486,7 @@ "message": "El vostre compte s'ha tancat i s'han eliminat totes les dades associades." }, "deleteOrganizationWarning": { - "message": "Deleting your organization is permanent. It cannot be undone." + "message": "La supressió de l'organització és permanent. No es pot desfer." }, "myAccount": { "message": "El meu compte" @@ -1553,7 +1553,7 @@ "message": "Error en desxifrar el fitxer exportat. La vostra clau de xifratge no coincideix amb la clau de xifratge utilitzada per exportar les dades." }, "destination": { - "message": "Destination" + "message": "Destinació" }, "learnAboutImportOptions": { "message": "Obteniu informació sobre les opcions d'importació" @@ -1762,7 +1762,7 @@ "message": "." }, "continueToExternalUrlTitle": { - "message": "Continue to $URL$?", + "message": "Voleu continuar cap a $URL$?", "placeholders": { "url": { "content": "$1", @@ -1774,10 +1774,10 @@ "message": "You are leaving Bitwarden and launching an external website in a new window." }, "twoStepContinueToBitwardenUrlTitle": { - "message": "Continue to bitwarden.com?" + "message": "Voleu continuar cap a bitwarden.com?" }, "twoStepContinueToBitwardenUrlDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website." + "message": "L'autenticador Bitwarden us permet emmagatzemar claus d'autenticació i generar codis TOTP per a fluxos de verificació en dos passos. Més informació al lloc web bitwarden.com." }, "twoStepAuthenticatorScanCodeV2": { "message": "Scan the QR code below with your authenticator app or enter the key." @@ -1789,7 +1789,7 @@ "message": "Clau" }, "twoStepAuthenticatorEnterCodeV2": { - "message": "Verification code" + "message": "Codi de verificació" }, "twoStepAuthenticatorReaddDesc": { "message": "En cas que hàgeu d'afegir-lo a un altre dispositiu, a continuació teniu el codi QR (o clau) requerit per l'aplicació autenticadora." @@ -1870,7 +1870,7 @@ "message": "Introduïu la informació de l'aplicació Bitwarden des del vostre tauler d'Administrador de Duo." }, "twoFactorDuoClientId": { - "message": "Client Id" + "message": "ID de client" }, "twoFactorDuoClientSecret": { "message": "Client Secret" @@ -3395,7 +3395,7 @@ "message": "No email?" }, "goBack": { - "message": "Go back" + "message": "Torna arrere" }, "toEditYourEmailAddress": { "message": "to edit your email address." @@ -3869,10 +3869,10 @@ "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Anul·la subscripció" }, "atAnyTime": { - "message": "at any time." + "message": "en qualsevol moment." }, "byContinuingYouAgreeToThe": { "message": "By continuing, you agree to the" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Número mínim de paraules" }, - "defaultType": { - "message": "Tipus per defecte" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Preferència d'usuari" @@ -4878,16 +4879,16 @@ "message": "Maintain tight control over machine and human access to secrets with SSO integrations, event logs, and access rotation." }, "tryItNow": { - "message": "Try it now" + "message": "Prova-ho ara" }, "sendRequest": { - "message": "Send request" + "message": "Envia una sol·licitud" }, "addANote": { - "message": "Add a note" + "message": "Afig una nota" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Administrador de secrets de Bitwarden" }, "moreProductsFromBitwarden": { "message": "More products from Bitwarden" @@ -4902,7 +4903,7 @@ "message": "Access request for secrets manager email sent to admins." }, "requestAccessSMDefaultEmailContent": { - "message": "Hi,\n\nI am requesting a subscription to Bitwarden Secrets Manager for our team. Your support would mean a great deal!\n\nBitwarden Secrets Manager is an end-to-end encrypted secrets management solution for securely storing, sharing, and deploying machine credentials like API keys, database passwords, and authentication certificates.\n\nSecrets Manager will help us to:\n\n- Improve security\n- Streamline operations\n- Prevent costly secret leaks\n\nTo request a free trial for our team, please reach out to Bitwarden.\n\nThank you for your help!" + "message": "Hola,\n\nEstem sol·licitant una subscripció a Bitwarden Secrets Manager per al nostre equip. El vostre suport significaria molt!\n\nBitwarden Secrets Manager és una solució de gestió de secrets xifrats d'extrem a extrem per emmagatzemar, compartir i desplegar de manera segura les credencials de la màquina, com ara claus API, contrasenyes de bases de dades i certificats d'autenticació.\n\nSecrets Manager ens ajudarà a:\n\n- Millorar la seguretat\n- Agilitar les operacions\n- Evitar costoses filtracions de secrets\n\nPer sol·licitar una prova gratuïta per al nostre equip, poseu-vos en contacte amb Bitwarden.\n\nGràcies per la vostra ajuda!" }, "giveMembersAccess": { "message": "Give members access:" @@ -5821,10 +5822,10 @@ "message": "Girar el token de sincronització de facturació invalidarà el token anterior." }, "selfHostedServer": { - "message": "self-hosted" + "message": "autoallotjat" }, "customEnvironment": { - "message": "Custom environment" + "message": "Entorn personalitzat" }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" @@ -5836,22 +5837,22 @@ "message": "You must add either the base Server URL or at least one custom environment." }, "apiUrl": { - "message": "API server URL" + "message": "URL del servidor API" }, "webVaultUrl": { - "message": "Web vault server URL" + "message": "URL del servidor de la caixa forta web" }, "identityUrl": { - "message": "Identity server URL" + "message": "URL del servidor d'identitat" }, "notificationsUrl": { - "message": "Notifications server URL" + "message": "URL del servidor de notificacions" }, "iconsUrl": { - "message": "Icons server URL" + "message": "URL del servidor d'icones" }, "environmentSaved": { - "message": "Environment URLs saved" + "message": "S'han guardat les URL de l'entorn" }, "selfHostingTitle": { "message": "Autoallotjat" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Paraula aleatòria" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Servei" }, @@ -6126,11 +6147,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Generat per Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Lloc web: $WEBSITE$. Generat per Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Afig un camp" }, "items": { "message": "Elements" @@ -8470,34 +8491,34 @@ "message": "Gestioneu la facturació des del portal de proveïdors" }, "startYour7DayFreeTrialOfBitwarden": { - "message": "Start your 7-Day free trial of Bitwarden" + "message": "Inicieu la vostra prova gratuïta de 7 dies de Bitwarden" }, "startYour7DayFreeTrialOfBitwardenForTeams": { - "message": "Start your 7-Day free trial of Bitwarden for Teams" + "message": "Inicieu la vostra prova gratuïta de 7 dies de Bitwarden per a Equips" }, "startYour7DayFreeTrialOfBitwardenForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden for Families" + "message": "Inicieu la vostra prova gratuïta de 7 dies de Bitwarden per a Famílies" }, "startYour7DayFreeTrialOfBitwardenForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden for Enterprise" + "message": "Inicieu la vostra prova gratuïta de 7 dies de Bitwarden per a Empresa" }, "startYour7DayFreeTrialOfBitwardenSecretsManager": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager" + "message": "Inicieu la vostra prova gratuïta de 7 dies de l'Administrador de secrets de Bitwarden" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForTeams": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Teams" + "message": "Inicieu la vostra prova gratuïta de 7 dies de l'Administrador de secrets de Bitwarden per a Equips" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Families" + "message": "Inicieu la vostra prova gratuïta de 7 dies de l'Administrador de secrets de Bitwarden per a Famílies" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Enterprise" + "message": "Inicieu la vostra prova gratuïta de 7 dies de l'Administrador de secrets de Bitwarden per a Empresa" }, "startYour7DayFreeTrialOfBitwardenPasswordManager": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager" + "message": "Inicieu la vostra prova gratuïta de 7 dies del Gestor de contrasenyes de Bitwarden" }, "startYour7DayFreeTrialOfBitwardenPasswordManagerForTeams": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager for Teams" + "message": "Inicieu la vostra prova gratuïta de 7 dies del Gestor de contrasenyes de Bitwarden per a Equips" }, "startYour7DayFreeTrialOfBitwardenPasswordManagerForFamilies": { "message": "Start your 7-Day free trial of Bitwarden Password Manager for Families" @@ -8634,7 +8655,7 @@ "message": "Quickly view member access across the organization by upgrading to an Enterprise plan." }, "date": { - "message": "Date" + "message": "Data" }, "exportClientReport": { "message": "Export client report" @@ -8691,7 +8712,7 @@ "message": "This action will remove your access to this secret." }, "invoice": { - "message": "Invoice" + "message": "Factura" }, "unassignedSeatsAvailable": { "message": "You have $SEATS$ unassigned seats available.", @@ -8707,7 +8728,7 @@ "message": "Contact your provider admin to purchase additional seats." }, "open": { - "message": "Open", + "message": "Obri", "description": "The status of an invoice." }, "uncollectible": { @@ -8731,7 +8752,7 @@ "description": "A paragraph on the Billing History page of the Provider Portal letting users know they can download a CSV report for their invoices that does not include prorations." }, "noInvoicesToList": { - "message": "There are no invoices to list", + "message": "No hi ha cap factura per llistar", "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": { @@ -8749,10 +8770,10 @@ "message": "After making updates in the Bitwarden cloud server, upload your license file to apply the most recent changes." }, "addToFolder": { - "message": "Add to folder" + "message": "Afig a la carpeta" }, "selectFolder": { - "message": "Select folder" + "message": "Selecciona carpeta" }, "personalItemTransferWarningSingular": { "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." @@ -8789,11 +8810,17 @@ } }, "data": { - "message": "Data" + "message": "Dades" }, "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { @@ -8849,7 +8876,7 @@ "message": "Upgrade your plan to invite more members and gain access to additional Bitwarden features" }, "upgradeDiscount": { - "message": "Save $AMOUNT$%", + "message": "Guarda $AMOUNT$%", "placeholders": { "amount": { "content": "$1", diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index b19a2c26bd8..95030d1efa8 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -49,22 +49,22 @@ "message": "Ověřovací klíč" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Volby automatického vyplňování" }, "websiteUri": { - "message": "Website (URI)" + "message": "Webová stránka (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Webová stránka přidána" }, "addWebsite": { - "message": "Add website" + "message": "Přidat webovou stránku" }, "deleteWebsite": { - "message": "Delete website" + "message": "Smazat webovou stránku" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Výchozí ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Zobrazit detekci shody $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Skrýt detekci shody $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Automaticky vyplnit při načtení stránky?" }, "number": { "message": "Číslo" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimální počet slov" }, - "defaultType": { - "message": "Výchozí typ" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Uživatelská volba" @@ -5323,7 +5324,7 @@ "message": "Vaše hlavní heslo nesplňuje jednu nebo více zásad Vaší organizace. Pro přístup k trezoru musíte nyní aktualizovat své hlavní heslo. Pokračování Vás odhlásí z Vaší aktuální relace a bude nutné se přihlásit. Aktivní relace na jiných zařízeních mohou zůstat aktivní až po dobu jedné hodiny." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Vaše organizace aktualizovala možnosti dešifrování. Nastavte hlavní heslo pro přístup k Vašemu trezoru." }, "maximumVaultTimeout": { "message": "Časový limit trezoru" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Náhodné slovo" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Služba" }, @@ -7678,7 +7699,7 @@ "message": "Požadované schválení zařízení." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Uživatel nastavil hlavní heslo během TDE offboardingu." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Začněte 7denní bezplatnou zkušební verzi Bitwardenu pro $ORG$", @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Přidat pole" }, "items": { "message": "Položky" @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "zakoupení uživatelé odebráni" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "pro $COUNT$ členů", "placeholders": { diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 78c7dc51dc8..e1ddd6cc4fb 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 3c95df80c8b..06aeb98463f 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum antal ord" }, - "defaultType": { - "message": "Standard type" + "overridePasswordTypePolicy": { + "message": "Adgangskodetype", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Brugerpræference" @@ -5323,7 +5324,7 @@ "message": "Din hovedadgangskode opfylder ikke et eller flere organisationspolitikkrav. For at kunne tilgå boksen skal hovedadgangskode derfor opdateres nu. Fortsættes, logges du ud af den nuværende session og vil skulle logge ind igen. Aktive sessioner på andre enheder kan forblive aktive i op til én time." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Organisationen har opdateret brugernes dekrypteringsmuligheder. Opsæt en hovedadgangskode for at tilgå boksen." }, "maximumVaultTimeout": { "message": "Boks-timeout" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Tilfældigt ord" }, + "usernameGenerator": { + "message": "Brugernavngenerator" + }, + "useThisPassword": { + "message": "Anvend denne adgangskode" + }, + "useThisUsername": { + "message": "Anvend dette brugernavn" + }, + "securePasswordGenerated": { + "message": "Sikker adgangskode genereret! Glem ikke at opdatere adgangskoden på webstedet også." + }, + "useGeneratorHelpTextPartOne": { + "message": "Anvend generatoren", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "til at oprette en stærk, unik adgangskode", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Tjeneste" }, @@ -7678,7 +7699,7 @@ "message": "Godkendelse af anmodet enhed." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Bruger opsatte en hovedadgangskode under TDE-offboarding." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Start din 7-dages gratis prøveperiode af Bitwarden til $ORG$", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "købte pladser fjernet" }, + "fileSends": { + "message": "Fil-Sends" + }, + "textSends": { + "message": "Tekst-Sends" + }, "includesXMembers": { "message": "for $COUNT$ medlem", "placeholders": { diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index e5d046ebabd..5a4dae11bf0 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -49,22 +49,22 @@ "message": "Authenticator-Schlüssel" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Auto-Ausfüllen Optionen" }, "websiteUri": { "message": "Website (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Website hinzugefügt" }, "addWebsite": { - "message": "Add website" + "message": "Website hinzufügen" }, "deleteWebsite": { - "message": "Delete website" + "message": "Website löschen" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Standard ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Übereinstimmungs-Erkennung anzeigen $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Übereinstimmungs-Erkennung verstecken $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Auto-Ausfüllen beim Laden einer Seite?" }, "number": { "message": "Nummer" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Mindestanzahl an Wörtern" }, - "defaultType": { - "message": "Standardtyp" + "overridePasswordTypePolicy": { + "message": "Passworttyp", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Benutzereinstellung" @@ -5323,7 +5324,7 @@ "message": "Dein Master-Passwort entspricht nicht einer oder mehreren Richtlinien deiner Organisation. Um auf den Tresor zugreifen zu können, musst du dein Master-Passwort jetzt aktualisieren. Wenn du fortfährst, wirst du von deiner aktuellen Sitzung abgemeldet und musst dich erneut anmelden. Aktive Sitzungen auf anderen Geräten können noch bis zu einer Stunde lang aktiv bleiben." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Deine Organisation hat deine Entschlüsselungsoptionen aktualisiert. Bitte lege ein Master-Passwort fest, um auf deinen Tresor zuzugreifen." }, "maximumVaultTimeout": { "message": "Tresor-Timeout" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Zufälliges Wort" }, + "usernameGenerator": { + "message": "Benutzernamen-Generator" + }, + "useThisPassword": { + "message": "Dieses Passwort verwenden" + }, + "useThisUsername": { + "message": "Diesen Benutzernamen verwenden" + }, + "securePasswordGenerated": { + "message": "Sicheres Passwort generiert! Vergiss nicht, auch dein Passwort auf der Website zu aktualisieren." + }, + "useGeneratorHelpTextPartOne": { + "message": "Verwende den Generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": ", um ein starkes einzigartiges Passwort zu erstellen", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Dienst" }, @@ -7678,7 +7699,7 @@ "message": "Geräte-Genehmigung anfragen." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Benutzer hat ein Master-Passwort während der VGV-Abmeldung festgelegt." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Starte deine 7-tägige kostenlose Testversion von Bitwarden für $ORG$", @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Feld hinzufügen" }, "items": { "message": "Einträge" @@ -8470,49 +8491,49 @@ "message": "Rechnungen über das Anbieter-Portal verwalten" }, "startYour7DayFreeTrialOfBitwarden": { - "message": "Start your 7-Day free trial of Bitwarden" + "message": "Starte deine 7-tägige kostenlose Testversion von Bitwarden" }, "startYour7DayFreeTrialOfBitwardenForTeams": { - "message": "Start your 7-Day free trial of Bitwarden for Teams" + "message": "Starte deine 7-tägige kostenlose Testversion des Bitwarden Teams-Tarifs" }, "startYour7DayFreeTrialOfBitwardenForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden for Families" + "message": "Starte deine 7-tägige kostenlose Testversion des Bitwarden Families-Tarifs" }, "startYour7DayFreeTrialOfBitwardenForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden for Enterprise" + "message": "Starte deine 7-tägige kostenlose Testversion des Bitwarden Enterprise-Tarifs" }, "startYour7DayFreeTrialOfBitwardenSecretsManager": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager" + "message": "Starte deine 7-tägige kostenlose Testversion vom Bitwarden Secrets Manager" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForTeams": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Teams" + "message": "Starte deine 7-tägige kostenlose Testversion im Teams-Tarif des Bitwarden Secrets Managers" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Families" + "message": "Starte deine 7-tägige kostenlose Testversion im Families-Tarif des Bitwarden Secrets Managers" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Enterprise" + "message": "Starte deine 7-tägige kostenlose Testversion im Enterprise-Tarif des Bitwarden Secrets Managers" }, "startYour7DayFreeTrialOfBitwardenPasswordManager": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager" + "message": "Starte deine 7-tägige kostenlose Testversion vom Bitwarden Password Manager" }, "startYour7DayFreeTrialOfBitwardenPasswordManagerForTeams": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager for Teams" + "message": "Starte deine 7-tägige kostenlose Testversion im Teams-Tarif des Bitwarden Password Managers" }, "startYour7DayFreeTrialOfBitwardenPasswordManagerForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager for Families" + "message": "Starte deine 7-tägige kostenlose Testversion im Families-Tarif des Bitwarden Password Managers" }, "startYour7DayFreeTrialOfBitwardenPasswordManagerForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager for Enterprise" + "message": "Starte deine 7-tägige kostenlose Testversion im Enterprise-Tarif des Bitwarden Password Managers" }, "enterTeamsOrgInfo": { - "message": "Enter your Teams organization information" + "message": "Gib die Informationen deiner Teams-Organisation ein" }, "enterFamiliesOrgInfo": { - "message": "Enter your Families organization information" + "message": "Gib die Informationen deiner Families-Organisation ein" }, "enterEnterpriseOrgInfo": { - "message": "Enter your Enterprise organization information" + "message": "Gib die Informationen deiner Enterprise-Organisation ein" }, "viewItemsIn": { "message": "Einträge in $NAME$ anzeigen", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "erworbene Benutzerplätze entfernt" }, + "fileSends": { + "message": "Datei-Sends" + }, + "textSends": { + "message": "Text-Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { @@ -8813,10 +8840,10 @@ } }, "optionalOnPremHosting": { - "message": "Optional on-premises hosting" + "message": "Optionales Hosten vor Ort" }, "upgradeFreeOrganization": { - "message": "Upgrade your $NAME$ organization ", + "message": "Upgrade deine $NAME$-Organisation ", "placeholders": { "name": { "content": "$1", @@ -8828,7 +8855,7 @@ "message": "SSO-Authentifizierung" }, "familiesPlanInvLimitReachedManageBilling": { - "message": "Families organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.", + "message": "Families-Organisationen können bis zu $SEATCOUNT$ Mitglieder haben. Upgrade auf einen kostenpflichtigen Tarif, um mehr Mitglieder einzuladen.", "placeholders": { "seatcount": { "content": "$1", @@ -8837,7 +8864,7 @@ } }, "familiesPlanInvLimitReachedNoManageBilling": { - "message": "Families organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.", + "message": "Families-Organisationen können bis zu $SEATCOUNT$ Mitglieder haben. Kontaktiere deinen Organisationseigentümer zum Upgraden.", "placeholders": { "seatcount": { "content": "$1", @@ -8846,10 +8873,10 @@ } }, "upgradePlan": { - "message": "Upgrade your plan to invite more members and gain access to additional Bitwarden features" + "message": "Upgrade deinen Tarif, um mehr Mitglieder einzuladen und Zugriff auf zusätzliche Bitwarden-Funktionen zu erhalten" }, "upgradeDiscount": { - "message": "Save $AMOUNT$%", + "message": "$AMOUNT$% sparen", "placeholders": { "amount": { "content": "$1", @@ -8858,27 +8885,27 @@ } }, "upgradeEnterpriseMessage": { - "message": "Advanced capabilities for larger businesses" + "message": "Erweiterte Möglichkeiten für größere Unternehmen" }, "upgradeTeamsMessage": { - "message": "Businesses looking for powerful security" + "message": "Unternehmen auf der Suche nach leistungsstarker Sicherheit" }, "teamsInviteMessage": { - "message": "Invite unlimited members" + "message": "Unbegrenzte Mitglieder einladen" }, "accessToCreateGroups": { - "message": "Access to create groups" + "message": "Zugang zum Erstellen von Gruppen" }, "syncGroupsAndUsersFromDirectory": { - "message": "Sync groups and users from a directory" + "message": "Gruppen und Benutzer aus einem Verzeichnis synchronisieren" }, "upgradeFamilyMessage": { - "message": "Share with families and friends" + "message": "Mit Familien und Freunden teilen" }, "accessToPremiumFeatures": { - "message": "Access to Premium features" + "message": "Zugriff auf Premium Funktionen" }, "additionalStorageGbMessage": { - "message": "GB additional storage" + "message": "GB zusätzlicher Speicher" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 2cab015c2a8..98b7122cf71 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -46,25 +46,25 @@ "message": "Στοιχεία σύνδεσης" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Κλειδί αυθεντικοποίησης" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Επιλογές αυτόματης συμπλήρωσης" }, "websiteUri": { - "message": "Website (URI)" + "message": "Ιστοσελίδα (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Η ιστοσελίδα προστέθηκε" }, "addWebsite": { - "message": "Add website" + "message": "Προσθήκη ιστοσελίδας" }, "deleteWebsite": { - "message": "Delete website" + "message": "Διαγραφή ιστοσελίδας" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Προεπιλογή ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Αυτόματη συμπλήρωση κατά τη φόρτωση της σελίδας;" }, "number": { "message": "Αριθμός" @@ -1498,7 +1498,7 @@ "message": "Εισαγωγή Δεδομένων" }, "onboardingImportDataDetailsPartOne": { - "message": "If you don't have any data to import, you can create a ", + "message": "Εάν δεν έχετε δεδομένα για εισαγωγή, μπορείτε να δημιουργήσετε ένα ", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { @@ -1506,11 +1506,11 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { - "message": " instead.", + "message": " αντ' αυτού.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." }, "onboardingImportDataDetailsPartTwoWithOrgs": { - "message": " instead. You may need to wait until your administrator confirms your organization membership.", + "message": " αντ 'αυτού. Ίσως χρειαστεί να περιμένετε μέχρι ο διαχειριστής σας να επιβεβαιώσει τη συνδρομή του οργανισμού σας.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -1523,7 +1523,7 @@ "message": "Τα δεδομένα έχουν εισαχθεί με επιτυχία στο vault σας." }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "Ένα σύνολο $AMOUNT$ αντικειμένων εισήχθησαν.", "placeholders": { "amount": { "content": "$1", @@ -1553,7 +1553,7 @@ "message": "Σφάλμα αποκρυπτογράφησης του εξαγόμενου αρχείου. Το κλειδί κρυπτογράφησης δεν ταιριάζει με το κλειδί κρυπτογράφησης που χρησιμοποιήθηκε για την εξαγωγή των δεδομένων." }, "destination": { - "message": "Destination" + "message": "Προορισμός" }, "learnAboutImportOptions": { "message": "Μάθετε για τις επιλογές εισαγωγής σας" @@ -1565,7 +1565,7 @@ "message": "Επιλέξτε μια συλλογή" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "Επιλέξτε αυτή την επιλογή εάν θέλετε τα περιεχόμενα του εισαγόμενου αρχείου να μετακινηθούν σε $DESTINATION$", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -1750,7 +1750,7 @@ "message": "Εισάγετε τον κύριο κωδικό για να τροποποιήσετε τις ρυθμίσεις σύνδεσης δύο βημάτων." }, "twoStepAuthenticatorInstructionPrefix": { - "message": "Download an authenticator app such as" + "message": "Κατεβάστε μια εφαρμογή αυθεντικοποίησης όπως" }, "twoStepAuthenticatorInstructionInfix1": { "message": "," @@ -1774,7 +1774,7 @@ "message": "You are leaving Bitwarden and launching an external website in a new window." }, "twoStepContinueToBitwardenUrlTitle": { - "message": "Continue to bitwarden.com?" + "message": "Συνέχεια στο bitwarden.com;" }, "twoStepContinueToBitwardenUrlDesc": { "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website." @@ -2255,7 +2255,7 @@ } }, "bitwardenFamiliesPlan": { - "message": "Bitwarden Families plan." + "message": "Οικογενειακό πρόγραμμα Bitwarden." }, "addons": { "message": "Πρόσθετα" @@ -2331,7 +2331,7 @@ } }, "paymentChargedWithUnpaidSubscription": { - "message": "Your payment method will be charged for any unpaid subscriptions." + "message": "Η μέθοδος πληρωμής σας θα χρεωθεί για οποιεσδήποτε απλήρωτες συνδρομές." }, "paymentChargedWithTrial": { "message": "Το πακέτο σας έρχεται με δωρεάν δοκιμή 7 ημερών. Ο τρόπος πληρωμής σας δεν θα χρεωθεί μέχρι να τελειώσει η δοκιμή. Η χρέωση θα πραγματοποιείται σε επαναλαμβανόμενη βάση κάθε $INTERVAL$. Μπορείτε να το ακυρώσετε οποιαδήποτε στιγμή." @@ -2343,7 +2343,7 @@ "message": "Πληροφορίες Χρέωσης" }, "billingTrialSubLabel": { - "message": "Your payment method will not be charged during the 7 day free trial." + "message": "Η μέθοδος πληρωμής σας δεν θα χρεωθεί κατά τη διάρκεια της δωρεάν δοκιμής 7 ημερών." }, "creditCard": { "message": "Πιστωτική Κάρτα" @@ -2403,7 +2403,7 @@ "message": "Διαχείριση Συνδρομής" }, "launchCloudSubscription": { - "message": "Launch Cloud Subscription" + "message": "Εκκίνηση Συνδρομής Cloud" }, "storage": { "message": "Αποθήκευση" @@ -2711,7 +2711,7 @@ } }, "trialThankYou": { - "message": "Thanks for signing up for Bitwarden for $PLAN$!", + "message": "Ευχαριστούμε για την εγγραφή σας στο Bitwarden για $PLAN$!", "placeholders": { "plan": { "content": "$1", @@ -2720,7 +2720,7 @@ } }, "trialSecretsManagerThankYou": { - "message": "Thanks for signing up for Bitwarden Secrets Manager for $PLAN$!", + "message": "Ευχαριστούμε για την εγγραφή σας στο Διαχειριστή Μυστικών Bitwarden για $PLAN$!", "placeholders": { "plan": { "content": "$1", @@ -4026,7 +4026,7 @@ "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "Φράση δακτυλικού αποτυπώματος" }, "dontAskFingerprintAgain": { "message": "Ποτέ μην παρακινείτε να επαληθεύσετε φράσεις fingerprint για τους προσκεκλημένους χρήστες (Δεν συνιστάται)", @@ -4077,7 +4077,7 @@ "message": "Κλώνος" }, "masterPassPolicyTitle": { - "message": "Master password requirements" + "message": "Απαιτήσεις κύριου κωδικού πρόσβασης" }, "masterPassPolicyDesc": { "message": "Ορίστε ελάχιστες απαιτήσεις, για ισχύ του κύριου κωδικού." @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Ελάχιστος Αριθμός Χαρακτήρων" }, - "defaultType": { - "message": "Προεπιλεγμένος Τύπος" + "overridePasswordTypePolicy": { + "message": "Τύπος κωδικού πρόσβασης", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Προτιμήσεις Χρήστη" @@ -4268,13 +4269,13 @@ "message": "Μπορείτε να κλείσετε αυτήν την καρτέλα τώρα και να συνεχίσετε στην επέκταση." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Έχετε συνδεθεί επιτυχώς" }, "thisWindowWillCloseIn5Seconds": { - "message": "This window will automatically close in 5 seconds" + "message": "Αυτό το παράθυρο θα κλείσει αυτόματα σε 5 δευτερόλεπτα" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Μπορείτε να κλείσετε αυτό το παράθυρο" }, "includeAllTeamsFeatures": { "message": "Όλα τα χαρακτηριστικά του Teams, συν:" @@ -4286,7 +4287,7 @@ "message": "Επιλογή μηνιαίας ή ετήσιας χρέωσης" }, "abilityToAddMoreThanNMembers": { - "message": "Ability to add more than $COUNT$ members", + "message": "Δυνατότητα προσθήκης περισσότερων από $COUNT$ μελών", "placeholders": { "count": { "content": "$1", @@ -4310,7 +4311,7 @@ "message": "SSO identifier" }, "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", + "message": "Δώστε αυτό το αναγνωριστικό στα μέλη σας για να συνδεθείτε με SSO. Για να παρακάμψετε αυτό το βήμα, ρυθμίστε την ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" }, "unlinkSso": { @@ -5247,7 +5248,7 @@ "message": "Προσθήκη Υπάρχοντος Οργανισμού" }, "addNewOrganization": { - "message": "Add new organization" + "message": "Προσθήκη νέου οργανισμού" }, "myProvider": { "message": "Ο Πάροχος Μου" @@ -5389,10 +5390,10 @@ "message": "Το χρονικό όριο του vault υπερβαίνει τον περιορισμό που ορίστηκε από τον οργανισμό σας." }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "Το ελάχιστο προσαρμοσμένο χρονικό όριο λήξης είναι 1 λεπτό." }, "vaultTimeoutRangeError": { - "message": "Vault timeout is not within allowed range." + "message": "Το χρονικό όριο λήξης του θησαυ/κίου δεν είναι εντός του επιτρεπόμενου εύρους." }, "disablePersonalVaultExport": { "message": "Απενεργοποίηση Εξαγωγής Προσωπικών Vault" @@ -5809,10 +5810,10 @@ "message": "Set up billing sync" }, "generateToken": { - "message": "Generate token" + "message": "Δημιουργία διακριτικού" }, "rotateToken": { - "message": "Rotate token" + "message": "Περιστροφή διακριτικού" }, "rotateBillingSyncTokenWarning": { "message": "If you proceed, you will need to re-setup billing sync on your self-hosted server." @@ -5881,13 +5882,13 @@ "message": "Sent (awaiting sync)" }, "sent": { - "message": "Sent" + "message": "Στάλθηκε" }, "requestRemoved": { - "message": "Removed (awaiting sync)" + "message": "Αφαιρέθηκε (αναμονή συγχρονισμού)" }, "requested": { - "message": "Requested" + "message": "Ζητήθηκε" }, "formErrorSummaryPlural": { "message": "$COUNT$ πεδία παραπάνω χρειάζονται την προσοχή σας.", @@ -5993,14 +5994,14 @@ "message": "Επιστροφή στις Αναφορές" }, "organizationPicker": { - "message": "Organization picker" + "message": "Επιλογέας οργανισμού" }, "currentOrganization": { "message": "Τρέχων οργανισμός", "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." }, "accountLoggedInAsName": { - "message": "Account: Logged in as $NAME$", + "message": "Λογαριασμός: Συνδεδεμένος ως $NAME$", "placeholders": { "name": { "content": "$1", @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Τυχαία Λέξη" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Υπηρεσία" }, @@ -6086,14 +6107,14 @@ } }, "lastSync": { - "message": "Last sync", + "message": "Τελευταίος συγχρονισμός", "description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\"" }, "sponsorshipsSynced": { "message": "Self-hosted sponsorships synced." }, "billingManagedByProvider": { - "message": "Managed by $PROVIDER$", + "message": "Διαχειρίζεται από $PROVIDER$", "placeholders": { "provider": { "content": "$1", @@ -6106,13 +6127,13 @@ "description": "This text is displayed if an organization's billing is managed by a Provider. It tells the user to contact the Provider for assistance." }, "forwardedEmail": { - "message": "Forwarded email alias" + "message": "Προωθημένο ψευδώνυμο διεύθυνσης ηλ. ταχυδρομείου" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "Δημιουργήστε ένα ψευδώνυμο διεύθυνσης ηλ. ταχυδρομείου με μια εξωτερική υπηρεσία προώθησης." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ σφάλμα: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -6126,7 +6147,7 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Δημιουργήθηκε από το Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { @@ -6218,10 +6239,10 @@ "description": "Part of a URL." }, "apiAccessToken": { - "message": "API access token" + "message": "Διακριτικό πρόσβασης API" }, "deviceVerification": { - "message": "Device verification" + "message": "Επαλήθευση συσκευής" }, "enableDeviceVerification": { "message": "Turn on device verification" @@ -6242,7 +6263,7 @@ } }, "premiumSubcriptionRequired": { - "message": "Premium subscription required" + "message": "Απαιτείται συνδρομή Premium" }, "scim": { "message": "SCIM provisioning", @@ -6276,7 +6297,7 @@ "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateKey": { - "message": "Rotate key" + "message": "Περιστροφή κλειδιού" }, "scimApiKey": { "message": "Κλειδί API SCIM", @@ -6386,10 +6407,10 @@ "message": "Ενεργοποίηση" }, "on": { - "message": "On" + "message": "Ενεργό" }, "off": { - "message": "Off" + "message": "Ανενεργό" }, "members": { "message": "Μέλη" @@ -6425,25 +6446,25 @@ "message": "Πορτοκαλί" }, "lavender": { - "message": "Lavender" + "message": "Λεβάντα" }, "yellow": { - "message": "Yellow" + "message": "Κιτρινο" }, "indigo": { - "message": "Indigo" + "message": "Λιλά" }, "teal": { - "message": "Teal" + "message": "Τιρκουάζ" }, "salmon": { "message": "Salmon" }, "pink": { - "message": "Pink" + "message": "Ροζ" }, "customColor": { - "message": "Custom Color" + "message": "Προσαρμοσμένο χρώμα" }, "selectPlaceholder": { "message": "-- Επιλογή --" @@ -6680,16 +6701,16 @@ "description": "Notifies that the selected secrets have been moved to the trash" }, "hardDeleteSecretConfirmation": { - "message": "Are you sure you want to permanently delete this secret?" + "message": "Είστε σίγουροι ότι θέλετε να διαγράψετε οριστικά αυτό το μυστικό;" }, "hardDeleteSecretsConfirmation": { - "message": "Are you sure you want to permanently delete these secrets?" + "message": "Είστε σίγουροι ότι θέλετε να διαγράψετε οριστικά αυτά τα μυστικά;" }, "hardDeletesSuccessToast": { - "message": "Secrets permanently deleted" + "message": "Τα μυστικά διαγράφηκαν οριστικά" }, "smAccess": { - "message": "Access", + "message": "Πρόσβαση", "description": "Title indicating what permissions a service account has" }, "projectCommaSecret": { @@ -6713,7 +6734,7 @@ "description": "Instructions for selecting projects or secrets for a new service account" }, "newSaTypeToFilter": { - "message": "Type to filter", + "message": "Πληκτρολογήστε για φιλτράρισμα", "description": "Instructions for filtering a list of projects or secrets" }, "deleteProjectsToast": { @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 47418aeddc2..f0d25683f28 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Auto-fill on page load?" }, "number": { "message": "Number" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -5323,7 +5324,7 @@ "message": "Your master password does not meet one or more of your organisation policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Your organisation has updated your decryption options. Please set a master password to access your vault." }, "maximumVaultTimeout": { "message": "Vault timeout" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -7678,7 +7699,7 @@ "message": "Requested device approval." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "User set a master password during TDE off boarding." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Start your 7-Day free trial of Bitwarden for $ORG$", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index ac912da5b41..f3607d4cf28 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Auto-fill on page load?" }, "number": { "message": "Number" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -5323,7 +5324,7 @@ "message": "Your master password does not meet one or more of your organisation policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Your organisation has updated your decryption options. Please set a master password to access your vault." }, "maximumVaultTimeout": { "message": "Vault timeout" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -7678,7 +7699,7 @@ "message": "Requested device approval." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "User set a master password during TDE off boarding." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Start your 7-Day free trial of Bitwarden for $ORG$", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index a4793ca4c36..d172d0c8e87 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimuma Nombro de Vortoj" }, - "defaultType": { - "message": "Apriora Tipo" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Uzanta Prefero" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Vorto Hazarda" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index c587cbaeb00..8bb900aa2d8 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Número mínimo de palabras" }, - "defaultType": { - "message": "Tipo por defecto" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Preferencia de usuario" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Palabra aleatoria" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Servicio" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index c676f3a25e8..37cbfca519a 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimaalne sõnade arv" }, - "defaultType": { - "message": "Vaiketüüp" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Kasutaja eelistus" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Juhuslik sõna" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Teenus" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 8d25da01a12..1008a980831 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Gutxieneko hitz kopurua" }, - "defaultType": { - "message": "Modu lehenetsia" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Erabiltzailearen hobespenak" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Ausazko hitza" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Zerbitzua" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 4bcb29a38bc..ab17248443f 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "حداقل تعداد کلمات" }, - "defaultType": { - "message": "نوع پیش‌فرض" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "تنظیمات کاربر" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "کلمه تصادفی" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "سرویس" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 8d0099ce705..78cd44e4504 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -49,22 +49,22 @@ "message": "Todennusavain" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Automaattitäytön asetukset" }, "websiteUri": { - "message": "Website (URI)" + "message": "Verkkosivusto (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Verkkosivusto lisättiin" }, "addWebsite": { - "message": "Add website" + "message": "Lisää verkkosivusto" }, "deleteWebsite": { - "message": "Delete website" + "message": "Poista verkkosivusto" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Oletus ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Näytä vastaavuuden tunnistus $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Piilota vastaavuuden tunnistus $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Automaattitäytetäänkö sivun avautuessa?" }, "number": { "message": "Numero" @@ -1100,7 +1100,7 @@ "message": "Suojausavain" }, "webAuthnDesc": { - "message": "Vahvista kirjautuminen WebAuthn-todennuslaitteella." + "message": "Vahvista kirjautuminen WebAuthn-suojausavaimella." }, "webAuthnMigrated": { "message": "(siirretty FIDO:sta)" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Sanojen vähimmäismäärä" }, - "defaultType": { - "message": "Oletustyyppi" + "overridePasswordTypePolicy": { + "message": "Salasanan tyyppi", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Käyttäjän asetus" @@ -5323,7 +5324,7 @@ "message": "Pääsalasanasi ei täytä yhden tai useamman organisaatiokäytännön vaatimuksia ja holvin käyttämiseksi sinun on vaihdettava se nyt. Tämä uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Organisaatiosi on päivittänyt salauksen purkuasetukset. Käytä holviasi asettamalla pääsalasana." }, "maximumVaultTimeout": { "message": "Holvin aikakatkaisu" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Satunnainen sana" }, + "usernameGenerator": { + "message": "Käyttäjätunnusgeneraattori" + }, + "useThisPassword": { + "message": "Käytä tätä salasanaa" + }, + "useThisUsername": { + "message": "Käytä tätä käyttäjätunnusta" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Käytä generaattoria", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Palvelu" }, @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Lisää kenttä" }, "items": { "message": "Kohteet" @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "ostetut käyttäpaikat poistettiin" }, + "fileSends": { + "message": "Tiedosto-Sendit" + }, + "textSends": { + "message": "Teksti-Sendit" + }, "includesXMembers": { "message": "$COUNT$ jäsenelle", "placeholders": { @@ -8846,7 +8873,7 @@ } }, "upgradePlan": { - "message": "Upgrade your plan to invite more members and gain access to additional Bitwarden features" + "message": "Päivitä tilauksesi kutsuaksesi lisää jäseniä ja saadaksesi käyttöösi Bitwardenin lisäominaisuuksia" }, "upgradeDiscount": { "message": "Säästä $AMOUNT$%", @@ -8858,7 +8885,7 @@ } }, "upgradeEnterpriseMessage": { - "message": "Advanced capabilities for larger businesses" + "message": "Kehittyneet ominaisuudet suuremmille yrityksille" }, "upgradeTeamsMessage": { "message": "Vahvaa suojausta etsiville yrityksille" diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index f502579d9a4..3cca56c18c0 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum na bilang ng mga salita" }, - "defaultType": { - "message": "Default na uri" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Kagustuhan ng gumagamit" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random na salita" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Serbisyo" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 605581821c7..44780737c55 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Nombre minimum de mots" }, - "defaultType": { - "message": "Type par défaut" + "overridePasswordTypePolicy": { + "message": "Type de Mot de Passe", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Choix laissé à l'utilisateur" @@ -5323,7 +5324,7 @@ "message": "Votre mot de passe principal ne répond pas aux exigences des politiques de sécurité de cette organisation. Pour pouvoir accéder au coffre, vous devez mettre à jour votre mot de passe principal dès maintenant. En poursuivant, vous serez déconnecté de votre session actuelle et vous devrez vous reconnecter. Les sessions actives sur d'autres appareils peuver rester actives pendant encore une heure." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Votre organisation a mis à jour vos options de déchiffrement. Veuillez définir un mot de passe principal pour accéder à votre coffre." }, "maximumVaultTimeout": { "message": "Délai d'expiration du coffre" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Mot Aléatoire" }, + "usernameGenerator": { + "message": "Générateur de nom d'utilisateur" + }, + "useThisPassword": { + "message": "Utiliser ce mot de passe" + }, + "useThisUsername": { + "message": "Utiliser ce nom d'utilisateur" + }, + "securePasswordGenerated": { + "message": "Mot de passe sécuritaire généré! N'oubliez pas de mettre à jour votre mot de passe sur le site Web aussi." + }, + "useGeneratorHelpTextPartOne": { + "message": "Utiliser le générateur", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "pour créer un mot de passe fort unique", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -7678,7 +7699,7 @@ "message": "Approbation de l'appareil demandée." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "L'utilisateur a défini un mot de passe principal lors de la prise en charge TDE." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Commencez votre essai gratuit de 7 jours de Bitwarden pour $ORG$", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "licenses achetées retirées" }, + "fileSends": { + "message": "Déposer des Sends" + }, + "textSends": { + "message": "Texter des Sends" + }, "includesXMembers": { "message": "pour $COUNT$ membre(s)", "placeholders": { diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index e2a3a42e575..3b3ab45fbd5 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 800dc2d5c3d..c8d0e831c11 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "מספר מינימאלי של מילים" }, - "defaultType": { - "message": "סוג ברירת מחדל" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "העדפות משתמש" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 7c2e2a03fa9..e3f29272067 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index a948eb6e5a8..54aa78de787 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -49,22 +49,22 @@ "message": "Kôd za provjeru" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Postavke auto-ispune" }, "websiteUri": { - "message": "Website (URI)" + "message": "Web stranica (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Web stranica dodana" }, "addWebsite": { - "message": "Add website" + "message": "Dodaj web stranicu" }, "deleteWebsite": { - "message": "Delete website" + "message": "Izbriši web stranicu" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Zadano ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Prikaži otkrivanje podudaranja $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Sakrij otkrivanje podudaranja $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Auto-ispuna kod učitavanja" }, "number": { "message": "Broj" @@ -176,7 +176,7 @@ "message": "gđica." }, "mx": { - "message": "Mx" + "message": "gx." }, "dr": { "message": "dr." @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Najmanji broj riječi" }, - "defaultType": { - "message": "Zadana vrsta" + "overridePasswordTypePolicy": { + "message": "Vrsta lozinke", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Korisnički definirano" @@ -5323,7 +5324,7 @@ "message": "Tvoja glavna lozinka ne zadovoljava pravila ove organizacije. Za pristup trezoru moraš odmah ažurirati svoju glavnu lozinku. Ako nastaviš, odjaviti ćeš se iz trenutne sesije te ćeš se morati ponovno prijaviti. Aktivne sesije na drugim uređajima mogu ostati aktivne do jedan sat." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Tvoja je organizacija ažurirala tvoje opcije dešifriranja. Postavi glavnu lozinku za pristup svom trezoru." }, "maximumVaultTimeout": { "message": "Istek trezora" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Nasumična riječ" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Usluga" }, @@ -7678,7 +7699,7 @@ "message": "Zatraži odobrenje uređaja." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Korisnik je postavio glavnu lozinku tijekom TDE offboardinga." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Pokreni besplatnih 7 dana Bitwardena za $ORG$", @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Dodaj polje" }, "items": { "message": "Stavke" @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "kupljenih mjesta uklonjeno" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "za ovoliko članova: $COUNT$", "placeholders": { diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 05434ea765b..ce27a1e09f6 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -49,22 +49,22 @@ "message": "Hitelesítő kulcs" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Automatikus kitöltés opciók" }, "websiteUri": { - "message": "Website (URI)" + "message": "Webhely (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "A webhely hozzáadásra került." }, "addWebsite": { - "message": "Add website" + "message": "Webhely hozzáadása" }, "deleteWebsite": { - "message": "Delete website" + "message": "Webhely törlése" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Alapértelmezés ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "$WEBSITE$ egyező érzékelés megjelenítése", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "$WEBSITE$ egyező érzékelés elrejtése", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Automatikus kitöltés oldalbetöltésnél?" }, "number": { "message": "Szám" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Szavak minimális száma" }, - "defaultType": { - "message": "Alapértelmezett típus" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Felhasználói előbeállítás" @@ -5323,7 +5324,7 @@ "message": "A mesterjelszó nem felel meg egy vagy több szervezeti szabályzatnak. A széf eléréséhez frissíteni kell a meszerjelszót. A továbblépés kijelentkeztet az aktuális munkamenetből és újra be kell jelentkezni. A többi eszközön lévő aktív munkamenetek akár egy óráig is aktívak maradhatnak." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "A szervezete frissítette a visszafejtési beállításokat. Állítsunk be egy mestarjelszót a széf eléréséhez." }, "maximumVaultTimeout": { "message": "Széf időkifutás" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Véletlenszerű szó" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Szolgáltatás" }, @@ -7678,7 +7699,7 @@ "message": "A kért eszköz jóváhagyása." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "A felhasználó beállított egy mesterjelszót a TDE kilptetés során." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Kezdjük el a Bitwarden 7 napos ingyenes próbaverzió használatát $ORG$ áron", @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Mező hozzáadása" }, "items": { "message": "Elemek" @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "megvásárolt hely eltávolításra került." }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "$COUNT$ member részére", "placeholders": { diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index ed8b2ea9dc0..a4b6632ea12 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Jumlah Kata Minimum" }, - "defaultType": { - "message": "Jenis Default" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Preferensi Pengguna" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Kata acak" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index fcee9dbc38f..7a66cbab289 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Numero minimo di parole" }, - "defaultType": { - "message": "Tipo predefinito" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Preferenza utente" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Parola casuale" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Servizio" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 1c3bf3a64e5..91a1ed82cd1 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -49,22 +49,22 @@ "message": "認証キー" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "自動入力オプション" }, "websiteUri": { - "message": "Website (URI)" + "message": "ウェブサイト (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "ウェブサイトを追加しました" }, "addWebsite": { - "message": "Add website" + "message": "ウェブサイトを追加" }, "deleteWebsite": { - "message": "Delete website" + "message": "ウェブサイトを削除" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "デフォルト ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "一致検出 $WEBSITE$を表示", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "一致検出 $WEBSITE$を非表示", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "ページ読み込み時に自動入力する" }, "number": { "message": "番号" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "文字の最小数" }, - "defaultType": { - "message": "デフォルトのタイプ" + "overridePasswordTypePolicy": { + "message": "パスワードの種類", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "ユーザー設定" @@ -5323,7 +5324,7 @@ "message": "マスターパスワードが組織のポリシーに適合していません。保管庫にアクセスするには、今すぐマスターパスワードを更新しなければなりません。続行すると現在のセッションからログアウトし、再度ログインする必要があります。 他のデバイス上のアクティブなセッションは、最大1時間アクティブであり続けることがあります。" }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "組織が復号オプションを更新しました。保管庫にアクセスするにはマスターパスワードを設定してください。" }, "maximumVaultTimeout": { "message": "保管庫のタイムアウト" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "ランダムな単語" }, + "usernameGenerator": { + "message": "ユーザー名生成" + }, + "useThisPassword": { + "message": "このパスワードを使用する" + }, + "useThisUsername": { + "message": "このユーザー名を使用する" + }, + "securePasswordGenerated": { + "message": "安全なパスワードを生成しました! ウェブサイト上でパスワードを更新することを忘れないでください。" + }, + "useGeneratorHelpTextPartOne": { + "message": "生成機能", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "を使うと強力で一意なパスワードを作れます", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "サービス" }, @@ -7678,7 +7699,7 @@ "message": "デバイスの承認を要求しました。" }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "ユーザーは、TDE オフボーディング中にマスターパスワードを設定しました。" }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "$ORG$ 向けの Bitwarden 7日間無料トライアルを開始", @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "フィールドを追加" }, "items": { "message": "アイテム" @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "購入済みシートが削除されました" }, + "fileSends": { + "message": "ファイル Send" + }, + "textSends": { + "message": "テキスト Send" + }, "includesXMembers": { "message": "$COUNT$ メンバー向け", "placeholders": { diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index bcdb646777b..a7d61bf0412 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index e2a3a42e575..3b3ab45fbd5 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 134f44bf107..a79ae8a6e1d 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "ಪದಗಳ ಕನಿಷ್ಠ ಸಂಖ್ಯೆ" }, - "defaultType": { - "message": "ಡೀಫಾಲ್ಟ್ ಪ್ರಕಾರ" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "ಬಳಕೆದಾರರ ಆದ್ಯತೆ" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index d7b1986eef8..be2642211fd 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "단어 최소 개수" }, - "defaultType": { - "message": "기본 유형" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "사용자 설정" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index c83bf1f4e84..6c218174952 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Mazākais pieļaujamais vārdu skaits" }, - "defaultType": { - "message": "Noklusējuma veids" + "overridePasswordTypePolicy": { + "message": "Paroles veids", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Lietotāja izvēle" @@ -5323,7 +5324,7 @@ "message": "Galvenā parole neatbilst vienam vai vairākiem apvienības nosacījumiem. Ir jāatjaunina galvenā parole, lai varētu piekļūt glabātavai. Turpinot notiks atteikšanās no pašreizējās sesijas, un būs nepieciešams pieteikties no jauna. Citās ierīcēs esošās sesijas var turpināt darboties līdz vienai stundai." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Tava apvienība ir atjauninājusi tavas atšifrēšanas iespējas. Lūgums iestatīt galveno paroli, lai piekļūtu savai glabātavai." }, "maximumVaultTimeout": { "message": "Glabātavas noildze" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Nejaušs vārds" }, + "usernameGenerator": { + "message": "Lietotājvārdu veidotājs" + }, + "useThisPassword": { + "message": "Izmantot šo paroli" + }, + "useThisUsername": { + "message": "Izmantot šo lietotājvārdu" + }, + "securePasswordGenerated": { + "message": "Droša parole izveidota. Neaizmirsti arī atjaunināt savu paroli tīmekļvietnē!" + }, + "useGeneratorHelpTextPartOne": { + "message": "Veidotājs ir izmantojams", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": ", lai izveidotu spēcīgu un vienreizēju paroli", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Pakalpojums" }, @@ -7678,7 +7699,7 @@ "message": "Pieprasīts ierīces apstiprinājums." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Lietotājs iestatīja galveno paroli uzticamo ierīču šifrēšanas atstāšanas laikā." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Uzsāc savu 7 dienu bezmaksas Bitwarden izmēģinājumu $ORG$!", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "iegādātās vietas noņemtas" }, + "fileSends": { + "message": "Datņu Send" + }, + "textSends": { + "message": "Teksta Send" + }, "includesXMembers": { "message": "$COUNT$ dalībniekiem", "placeholders": { diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 2f6386980ea..30cda30f1df 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "വാക്കുകളുടെ ഏറ്റവും കുറഞ്ഞ എണ്ണം" }, - "defaultType": { - "message": "സ്ഥിരസ്ഥിതി തരം" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "ഉപയോക്തൃ മുൻഗണന" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index e2a3a42e575..3b3ab45fbd5 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index e2a3a42e575..3b3ab45fbd5 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 97a029d98ef..272279d8b36 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum antall ord" }, - "defaultType": { - "message": "Standardtype" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Brukerinnstillinger" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Vilkårlig ord" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Tjeneste" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index f05f94623b5..1360ff2724d 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 793173266f5..a3223ba94ab 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum aantal woorden" }, - "defaultType": { - "message": "Standaardtype" + "overridePasswordTypePolicy": { + "message": "Type wachtwoord", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Persoonlijke voorkeur" @@ -5323,7 +5324,7 @@ "message": "Je hoofdwachtwoord voldoet niet aan en of meerdere oganisatiebeleidsonderdelen. Om toegang te krijgen tot de kluis, moet je je hoofdwachtwoord nu bijwerken. Doorgaan zal je huidige sessie uitloggen, waarna je opnieuw moet inloggen. Actieve sessies op andere apparaten blijven mogelijk nog een uur actief." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Je organisatie heeft je versleutelingsopties gewijzigd. Stel een hoofdwachtwoord in om toegang te krijgen tot je kluis." }, "maximumVaultTimeout": { "message": "Time-out van de kluis" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Willekeurig woord" }, + "usernameGenerator": { + "message": "Gebruikersnaamgenerator" + }, + "useThisPassword": { + "message": "Dit wachtwoord gebruiken" + }, + "useThisUsername": { + "message": "Deze gebruikersnaam gebruiken" + }, + "securePasswordGenerated": { + "message": "Veilig wachtwoord aangemaakt! Vergeet niet om je wachtwoord ook op de website bij te werken." + }, + "useGeneratorHelpTextPartOne": { + "message": "Gebruik de generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "om een sterk uniek wachtwoord te maken", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Dienst" }, @@ -7678,7 +7699,7 @@ "message": "Apparaatgoedkeuring gevraagd." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Gebruiker geeft een hoofdwachtwoord ingesteld tijdens TDE-offboarding." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Begin je 7-daagse gratis Bitwarden-proefperiode voor $ORG$", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "gekochte plaatsen verwijderd" }, + "fileSends": { + "message": "Bestand-Sends" + }, + "textSends": { + "message": "Tekst-Sends" + }, "includesXMembers": { "message": "voor $COUNT$ lid", "placeholders": { diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 4af2c56731a..09743c125f0 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index e2a3a42e575..3b3ab45fbd5 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 59da91b350a..16f135a85ad 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimalna liczba słów" }, - "defaultType": { - "message": "Domyślny rodzaj" + "overridePasswordTypePolicy": { + "message": "Rodzaj hasła", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Ustawienie użytkownika" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Losowe słowo" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Usługa" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "zakupione miejsca usunięte" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index d4dc73076e6..e2df4cf7081 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -49,22 +49,22 @@ "message": "Chave do autenticador" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Opções de autopreenchimento" }, "websiteUri": { - "message": "Website (URI)" + "message": "Site (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Site adicionado" }, "addWebsite": { - "message": "Add website" + "message": "Adicionar site" }, "deleteWebsite": { - "message": "Delete website" + "message": "Excluir site" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Padrão ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Exibir detecção de correspondência $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Ocultar detecção de correspondência $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Preenchimento automático ao carregar a página?" }, "number": { "message": "Número" @@ -1681,7 +1681,7 @@ "message": "Ative o login em duas etapas para sua organização." }, "twoStepLoginEnterpriseDescStart": { - "message": "Forçar as opções de login em duas etapas do Bitwarden para membros usando o ", + "message": "Reforçar as opções de login em duas etapas do Bitwarden para membros usando o ", "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": { @@ -1694,7 +1694,7 @@ "message": "Se você tem SSO configurado ou planeja ter, o Login em Duas Etapas já pode ser requerido por meio do seu Provedor de Identidade." }, "twoStepLoginRecoveryWarning": { - "message": "Ativar o login em duas etapas pode bloquear você permanentemente da sua conta no Bitwarden. Um código de recuperação permite que você acesse sua conta no caso de não poder mais usar seu provedor de login em duas etapas normalmente (por exemplo, você perde seu dispositivo). O suporte do Bitwarden não poderá ajudá-lo se você perder o acesso à sua conta. Recomendamos que você anote ou imprima o código de recuperação e mantenha-o em um local seguro." + "message": "A configuração de login em duas etapas pode bloqueá-lo permanentemente da sua conta no Bitwarden. Um código de recuperação permite que você acesse sua conta no caso de não poder mais usar seu provedor de login em duas etapas normalmente (exemplo: você perde seu dispositivo). O suporte do Bitwarden não será capaz de ajudá-lo se você perder o acesso à sua conta. Recomendamos que você anote ou imprima o código de recuperação e o mantenha em um lugar seguro." }, "viewRecoveryCode": { "message": "Ver código de recuperação" @@ -1780,7 +1780,7 @@ "message": "O Bitwarden Authenticator permite que você armazene as chaves de autenticador e gera 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": "Escaneie o código QR abaixo com o seu aplicativo de autenticação ou insira a chave." }, "twoStepAuthenticatorQRCanvasError": { "message": "Não foi possível carregar o código QR. Tente novamente ou use a chave abaixo." @@ -1888,7 +1888,7 @@ "message": "Insira o código de verificação de 6 dígitos que foi enviado pro seu e-mail" }, "sendEmail": { - "message": "Enviar E-mail" + "message": "Enviar e-mail" }, "twoFactorU2fAdd": { "message": "Adicione uma chave de segurança FIDO U2F à sua conta" @@ -1918,7 +1918,7 @@ "message": "Salvar o formulário." }, "twoFactorU2fWarning": { - "message": "Devido as limitações da plataforma, o FIDO U2F não pode ser usado em todas os aplicativos do Bitwarden. Você deve habilitar outro provedor de login em duas etapas para poder acessar sua conta quando o FIDO U2F não puder ser usado. Plataformas Suportadas:" + "message": "Devido as limitações da plataforma, o FIDO U2F não pode ser usado em todas as aplicações do Bitwarden. Deve configurar outro provedor de login 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 habilitado para U2F (Chrome, Opera, Vivaldi ou Firefox com o FIDO U2F ativado)." @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Número Mínimo de Palavras" }, - "defaultType": { - "message": "Tipo Padrão" + "overridePasswordTypePolicy": { + "message": "Tipo de senha", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Preferência do Usuário" @@ -4851,73 +4852,73 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, **or** try it now.'" }, "developmentDevOpsAndITTeamsChooseBWSecret": { - "message": "Development, DevOps, and IT teams choose Bitwarden Secrets Manager to securely manage and deploy their infrastructure and machine secrets." + "message": "Desenvolvimento, DevOps e equipes de TI escolhem o Gerenciador de Segredos do Bitwarden para gerenciar e implantar seus segredos de infraestrutura e máquina de forma segura." }, "centralizeSecretsManagement": { - "message": "Centralize secrets management." + "message": "Centralize o gerenciamento de segredos." }, "centralizeSecretsManagementDescription": { - "message": "Securely store and manage secrets in one location to prevent secret sprawl across your organization." + "message": "Armazene e gerencie com segurança segredos em um único local para impedir sprawl secreto em sua organização." }, "preventSecretLeaks": { - "message": "Prevent secret leaks." + "message": "Prevenir fugas secretas." }, "preventSecretLeaksDescription": { - "message": "Protect secrets with end-to-end encryption. No more hard coding secrets or sharing through .env files." + "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." }, "enhanceDeveloperProductivity": { - "message": "Enhance developer productivity." + "message": "Melhorar a produtividade do desenvolvedor." }, "enhanceDeveloperProductivityDescription": { - "message": "Programmatically retrieve and deploy secrets at runtime so developers can focus on what matters most, like improving code quality." + "message": "Recuperar e implantar 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": "Strengthen business security." + "message": "Fortalecer a segurança do negócio." }, "strengthenBusinessSecurityDescription": { - "message": "Maintain tight control over machine and human access to secrets with SSO integrations, event logs, and access rotation." + "message": "Mantenha controle apertado sobre o acesso de máquina e humano a segredos com integrações SSO, logs de eventos e rotação de acesso." }, "tryItNow": { - "message": "Try it now" + "message": "Experimente agora" }, "sendRequest": { - "message": "Send request" + "message": "Enviar solicitação" }, "addANote": { - "message": "Add a note" + "message": "Adicione uma anotação" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Gerenciador de Segredos Bitwarden" }, "moreProductsFromBitwarden": { - "message": "More products from Bitwarden" + "message": "Mais produtos do Bitwarden" }, "requestAccessToSecretsManager": { - "message": "Request access to Secrets Manager" + "message": "Solicitar acesso ao gerente secreto" }, "youNeedApprovalFromYourAdminToTrySecretsManager": { - "message": "You need approval from your administrator to try Secrets Manager." + "message": "Você precisa de aprovação do seu administrador para testar o Gerenciador de Segredos." }, "smAccessRequestEmailSent": { - "message": "Access request for secrets manager email sent to admins." + "message": "Solicitação de acesso ao e-mail gerente de segredos enviada aos administradores." }, "requestAccessSMDefaultEmailContent": { - "message": "Hi,\n\nI am requesting a subscription to Bitwarden Secrets Manager for our team. Your support would mean a great deal!\n\nBitwarden Secrets Manager is an end-to-end encrypted secrets management solution for securely storing, sharing, and deploying machine credentials like API keys, database passwords, and authentication certificates.\n\nSecrets Manager will help us to:\n\n- Improve security\n- Streamline operations\n- Prevent costly secret leaks\n\nTo request a free trial for our team, please reach out to Bitwarden.\n\nThank you for your help!" + "message": "Olá,\n\nestou solicitando uma assinatura do Bitwarden Secrets Manager 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, Compartilhar e implantar credenciais de máquina como chaves API, senhas de banco de dados e certificados de autenticação.\n\nGerenciador de segredos nos ajudará a:\n\n- Melhorar a segurança\n- Operações de streaming\n- Evitar fugas de segredos dispendiosas\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": "Give members access:" + "message": "Dar acesso aos membros:" }, "viewAndSelectTheMembers": { - "message": "view and select the members you want to give access to Secrets Manager." + "message": "veja e selecione os membros que você deseja dar acesso ao Gerenciador de Segredos." }, "openYourOrganizations": { - "message": "Open your organization's" + "message": "Abrir sua organização" }, "usingTheMenuSelect": { - "message": "Using the menu, select" + "message": "Usando o menu, selecione" }, "toGrantAccessToSelectedMembers": { - "message": "to grant access to selected members." + "message": "para conceder acesso aos membros selecionados." }, "sendVaultCardTryItNow": { "message": "teste agora", @@ -5323,7 +5324,7 @@ "message": "A sua senha mestra não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha mestra agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Sua organização atualizou suas opções de descriptografia. Por favor, defina uma senha mestra para acessar seu cofre." }, "maximumVaultTimeout": { "message": "Tempo Limite do Cofre" @@ -5605,7 +5606,7 @@ "message": "Resgatado" }, "redeemedAccount": { - "message": "Conta Resgatada" + "message": "Conta resgatada" }, "revokeAccount": { "message": "Revogar conta $NAME$", @@ -5626,10 +5627,10 @@ } }, "freeFamiliesPlan": { - "message": "Plano Familiar Gratuito" + "message": "Plano de famílias gratuitas" }, "redeemNow": { - "message": "Resgatar Agora" + "message": "Resgatar agora" }, "recipient": { "message": "Destinatário" @@ -5638,13 +5639,13 @@ "message": "Remover Patrocínio" }, "removeSponsorshipConfirmation": { - "message": "Depois de remover um patrocínio, você será responsável por essa assinatura e faturas relacionadas. Tem certeza de que quer continuar?" + "message": "Após remover um patrocínio, você será responsável por esta assinatura e faturas relacionadas. Você tem certeza que deseja continuar?" }, "sponsorshipCreated": { - "message": "Patrocínio Criado" + "message": "Patrocínio criado" }, "emailSent": { - "message": "E-mail Enviado" + "message": "E-mail enviado" }, "revokeSponsorshipConfirmation": { "message": "Depois de remover esta conta, o proprietário da organização das Famílias será responsável por essa assinatura e faturas relacionadas. Tem certeza de que quer continuar?" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Palavra Aleatória" }, + "usernameGenerator": { + "message": "Gerador de usuário" + }, + "useThisPassword": { + "message": "Use esta senha" + }, + "useThisUsername": { + "message": "Use este nome de usuário" + }, + "securePasswordGenerated": { + "message": "Senha segura gerada! Não se esqueça de atualizar também sua senha no site." + }, + "useGeneratorHelpTextPartOne": { + "message": "Usar 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": { + "message": "para criar uma senha única e forte", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Serviço" }, @@ -7678,7 +7699,7 @@ "message": "Aprovação do dispositivo solicitada." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Usuário definiu uma senha mestra durante o offboard TDE." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Comece o seu período de teste gratuito de 7 dias do Bitwarden para $ORG$", @@ -8049,10 +8070,10 @@ "message": "Atribuir a estas coleções" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "Apenas membros da organização com acesso a essas coleções poderão ver o item." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "Apenas membros da organização com acesso à essas coleções poderão ver os itens." }, "selectCollectionsToAssign": { "message": "Selecione as coleções para atribuir" @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Adicionar campo" }, "items": { "message": "Itens" @@ -8470,49 +8491,49 @@ "message": "Gerenciar faturamento a partir do Portal do Provedor" }, "startYour7DayFreeTrialOfBitwarden": { - "message": "Start your 7-Day free trial of Bitwarden" + "message": "Comece o seu teste grátis de 7 dias do Bitwarden" }, "startYour7DayFreeTrialOfBitwardenForTeams": { - "message": "Start your 7-Day free trial of Bitwarden for Teams" + "message": "Comece o seu teste gratuito de 7 dias do Bitwarden para Equipes" }, "startYour7DayFreeTrialOfBitwardenForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden for Families" + "message": "Comece o seu teste gratuito de 7 dias do Bitwarden para as Famílias" }, "startYour7DayFreeTrialOfBitwardenForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden for Enterprise" + "message": "Comece o seu teste gratuito de 7 dias do Bitwarden para Empresas" }, "startYour7DayFreeTrialOfBitwardenSecretsManager": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager" + "message": "Inicie o seu teste gratuito de 7 dias do Gerenciador de Segredos do Bitwarden" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForTeams": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Teams" + "message": "Comece o seu teste gratuito de 7 dias do Gerenciador de Segredos do Bitwarden para equipes" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Families" + "message": "Comece o seu teste gratuito de 7 dias do Bitwarden Secrets Manager para famílias" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Enterprise" + "message": "Comece o seu teste gratuito de 7 dias do Bitwarden Secrets Manager para a Empresa" }, "startYour7DayFreeTrialOfBitwardenPasswordManager": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager" + "message": "Comece o seu teste gratuito de 7 dias do Bitwarden Password Manager" }, "startYour7DayFreeTrialOfBitwardenPasswordManagerForTeams": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager for Teams" + "message": "Comece o seu teste gratuito de 7 dias do Gerenciador de Senhas do Bitwarden para Equipes" }, "startYour7DayFreeTrialOfBitwardenPasswordManagerForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager for Families" + "message": "Comece o seu teste gratuito de 7 dias do Bitwarden Password Manager para Famílias" }, "startYour7DayFreeTrialOfBitwardenPasswordManagerForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager for Enterprise" + "message": "Comece o seu teste gratuito de 7 dias do Bitwarden Password Manager para a Empresa" }, "enterTeamsOrgInfo": { - "message": "Enter your Teams organization information" + "message": "Insira as informações da organização de suas equipes" }, "enterFamiliesOrgInfo": { - "message": "Enter your Families organization information" + "message": "Digite as informações da organização das suas famílias" }, "enterEnterpriseOrgInfo": { - "message": "Enter your Enterprise organization information" + "message": "Insira suas informações da organização empresarial" }, "viewItemsIn": { "message": "Visualizar itens em $NAME$", @@ -8721,44 +8742,44 @@ "message": "Baixar CSV" }, "monthlySubscriptionUserSeatsMessage": { - "message": "Adjustments to your subscription will result in prorated charges to your billing totals on your next billing period. " + "message": "Os ajustes à sua assinatura resultarão em cobranças rateadas no total de seu próximo período de faturamento. " }, "annualSubscriptionUserSeatsMessage": { - "message": "Adjustments to your subscription will result in prorated charges on a monthly billing cycle. " + "message": "Os ajustes à sua assinatura resultarão em cobranças rateadas em um ciclo de faturamento mensal. " }, "billingHistoryDescription": { - "message": "Download a CSV to obtain client details for each billing date. Prorated charges are not included in the CSV and may vary from the linked invoice. For the most accurate billing details, refer to your monthly invoices.", + "message": "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.", "description": "A paragraph on the Billing History page of the Provider Portal letting users know they can download a CSV report for their invoices that does not include prorations." }, "noInvoicesToList": { - "message": "There are no invoices to list", + "message": "Não há itens 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": "Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions,", + "message": "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,", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'." }, "contactBitwardenSupport": { - "message": "contact Bitwarden support.", + "message": "entre em contato com o suporte do Bitwarden.", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'. 'Bitwarden' should not be translated" }, "sponsored": { - "message": "Sponsored" + "message": "Patrocinado" }, "licenseAndBillingManagementDesc": { - "message": "After making updates in the Bitwarden cloud server, upload your license file to apply the most recent changes." + "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." }, "addToFolder": { - "message": "Add to folder" + "message": "Adicionar à pasta" }, "selectFolder": { - "message": "Select folder" + "message": "Selecionar pastas" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "1 item será transferido permanentemente para a organização selecionada. Você não irá mais possuir este item." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "Itens $PERSONAL_ITEMS_COUNT$ serão transferidos permanentemente para a organização selecionada. Você não irá mais possuir esses itens.", "placeholders": { "personal_items_count": { "content": "$1", @@ -8767,7 +8788,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "1 item será transferido permanentemente para $ORG$. Você não irá mais possuir este item.", "placeholders": { "org": { "content": "$1", @@ -8776,7 +8797,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "Os itens $PERSONAL_ITEMS_COUNT$ serão transferidos permanentemente para $ORG$. Você não irá mais possuir esses itens.", "placeholders": { "personal_items_count": { "content": "$1", @@ -8789,13 +8810,19 @@ } }, "data": { - "message": "Data" + "message": "Dado" }, "purchasedSeatsRemoved": { - "message": "purchased seats removed" + "message": "assentos comprados removidos" + }, + "fileSends": { + "message": "Arquivos enviados" + }, + "textSends": { + "message": "Texto enviado" }, "includesXMembers": { - "message": "for $COUNT$ member", + "message": "para $COUNT$ membro", "placeholders": { "count": { "content": "$1", @@ -8813,10 +8840,10 @@ } }, "optionalOnPremHosting": { - "message": "Optional on-premises hosting" + "message": "Hospedagem local opcional" }, "upgradeFreeOrganization": { - "message": "Upgrade your $NAME$ organization ", + "message": "Atualize sua organização $NAME$ ", "placeholders": { "name": { "content": "$1", @@ -8825,10 +8852,10 @@ } }, "includeSsoAuthenticationMessage": { - "message": "SSO Authentication" + "message": "Autenticação SSO" }, "familiesPlanInvLimitReachedManageBilling": { - "message": "Families organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.", + "message": "Organizações familiares podem ter até membros da $SEATCOUNT$. Faça o upgrade para um plano pago para convidar mais membros.", "placeholders": { "seatcount": { "content": "$1", @@ -8837,7 +8864,7 @@ } }, "familiesPlanInvLimitReachedNoManageBilling": { - "message": "Families organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.", + "message": "Organizações familiares podem ter até membros $SEATCOUNT$. Entre em contato com o proprietário da sua organização para atualizar.", "placeholders": { "seatcount": { "content": "$1", @@ -8846,10 +8873,10 @@ } }, "upgradePlan": { - "message": "Upgrade your plan to invite more members and gain access to additional Bitwarden features" + "message": "Atualize o seu plano para convidar mais membros e ganhar acesso a recursos adicionais do Bitwarden" }, "upgradeDiscount": { - "message": "Save $AMOUNT$%", + "message": "Economize $AMOUNT$%", "placeholders": { "amount": { "content": "$1", @@ -8858,27 +8885,27 @@ } }, "upgradeEnterpriseMessage": { - "message": "Advanced capabilities for larger businesses" + "message": "Recursos avançados para empresas maiores" }, "upgradeTeamsMessage": { - "message": "Businesses looking for powerful security" + "message": "Empresas que procuram segurança poderosa" }, "teamsInviteMessage": { - "message": "Invite unlimited members" + "message": "Convide membros ilimitados" }, "accessToCreateGroups": { - "message": "Access to create groups" + "message": "Acesso para criar grupos" }, "syncGroupsAndUsersFromDirectory": { - "message": "Sync groups and users from a directory" + "message": "Sincronizar grupos e usuários de um diretório" }, "upgradeFamilyMessage": { - "message": "Share with families and friends" + "message": "Compartilhar com famílias e amigos" }, "accessToPremiumFeatures": { - "message": "Access to Premium features" + "message": "Acesso às funcionalidades Premium" }, "additionalStorageGbMessage": { - "message": "GB additional storage" + "message": "GB de armazenamento adicional" } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 35c108d2f2f..798acf3b535 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Número mínimo de palavras" }, - "defaultType": { - "message": "Tipo predefinido" + "overridePasswordTypePolicy": { + "message": "Tipo de palavra-passe", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Preferência do utilizador" @@ -5323,7 +5324,7 @@ "message": "A sua palavra-passe mestra não cumpre uma ou mais políticas da sua organização. Para aceder ao cofre, tem de atualizar a sua palavra-passe mestra agora. Ao prosseguir, terminará a sua sessão atual e terá de iniciar sessão novamente. As sessões ativas noutros dispositivos poderão continuar ativas até uma hora." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "A sua organização atualizou as opções de desencriptação. Por favor, defina uma palavra-passe mestra para aceder ao seu cofre." }, "maximumVaultTimeout": { "message": "Tempo limite do cofre" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Palavra aleatória" }, + "usernameGenerator": { + "message": "Gerador de nomes de utilizador" + }, + "useThisPassword": { + "message": "Utilizar esta palavra-passe" + }, + "useThisUsername": { + "message": "Utilizar este nome de utilizador" + }, + "securePasswordGenerated": { + "message": "Palavra-passe segura gerada! Não se esqueça de atualizar também a sua palavra-passe no site." + }, + "useGeneratorHelpTextPartOne": { + "message": "Utilize 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": { + "message": "para criar uma palavra-passe forte e única", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Serviço" }, @@ -7678,7 +7699,7 @@ "message": "Aprovação de dispositivo pedida." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "O utilizador definiu uma palavra-passe mestra durante o TDE offboarding." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Inicie o seu período experimental gratuito de 7 dias do Bitwarden para $ORG$", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "lugares adquiridos removidos" }, + "fileSends": { + "message": "Sends de ficheiros" + }, + "textSends": { + "message": "Sends de texto" + }, "includesXMembers": { "message": "para $COUNT$ membro(s)", "placeholders": { diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 6264625107f..ff98c7b2547 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Număr minim de cuvinte" }, - "defaultType": { - "message": "Tip implicit" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Preferințe utilizator" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Cuvânt aleatoriu" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Serviciu" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index f0f6f9fd260..1ee49306479 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Минимальное количество слов" }, - "defaultType": { - "message": "Тип по умолчанию" + "overridePasswordTypePolicy": { + "message": "Тип пароля", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Предпочтения пользователя" @@ -5323,7 +5324,7 @@ "message": "Ваш мастер-пароль не соответствует требованиям политики вашей организации. Для доступа к хранилищу вы должны обновить свой мастер-пароль прямо сейчас. При этом текущий сеанс будет завершен и потребуется повторная авторизация. Сеансы на других устройствах могут оставаться активными в течение часа." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Ваша организация обновила параметры расшифровки. Пожалуйста, установите мастер-пароль для доступа к вашему хранилищу." }, "maximumVaultTimeout": { "message": "Тайм-аут хранилища" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Случайное слово" }, + "usernameGenerator": { + "message": "Генератор имени пользователя" + }, + "useThisPassword": { + "message": "Использовать этот пароль" + }, + "useThisUsername": { + "message": "Использовать это имя пользователя" + }, + "securePasswordGenerated": { + "message": "Безопасный пароль сгенерирован! Не забудьте также обновить свой пароль на сайте." + }, + "useGeneratorHelpTextPartOne": { + "message": "Использовать генератор", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "для создания надежного уникального пароля", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Служба" }, @@ -7678,7 +7699,7 @@ "message": "Запрошено одобрение устройства." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Пользователь установил мастер-пароль во время регистрации в TDE." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Начните 7-дневную бесплатную пробную версию Bitwarden для $ORG$", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "купленные места удалены" }, + "fileSends": { + "message": "Файловая Send" + }, + "textSends": { + "message": "Текстовая Send" + }, "includesXMembers": { "message": "для участников: $COUNT$", "placeholders": { diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 032bcc59055..bc214c47971 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index f00051e2dc1..273a99fb567 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -49,22 +49,22 @@ "message": "Kľúč overovacej aplikácie" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Možnosti automatického vypĺňania" }, "websiteUri": { - "message": "Website (URI)" + "message": "Webová stránka (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Webová stránka pridaná" }, "addWebsite": { - "message": "Add website" + "message": "Pridať webovú stránku" }, "deleteWebsite": { - "message": "Delete website" + "message": "Odstrániť webovú stránku" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Predvolené ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Zobraziť spôsob mapovania $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Skryť spôsob mapovania $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Automaticky vyplniť pri načítaní stránky?" }, "number": { "message": "Číslo" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimálny počet slov" }, - "defaultType": { - "message": "Východzí typ" + "overridePasswordTypePolicy": { + "message": "Typ hesla", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Nastaví používateľ" @@ -5323,7 +5324,7 @@ "message": "Vaše hlavné heslo nespĺňa jednu alebo viacero podmienok vašej organizácie. Ak chcete získať prístup k trezoru, musíte teraz aktualizovať svoje hlavné heslo. Pokračovaním sa odhlásite z aktuálnej relácie a budete sa musieť znova prihlásiť. Aktívne relácie na iných zariadeniach môžu zostať aktívne až jednu hodinu." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Vaša organizácia aktualizovala možnosti dešifrovania. Na prístup k trezoru nastavte hlavné heslo." }, "maximumVaultTimeout": { "message": "Časový limit pre trezor" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Náhodné slovo" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Služba" }, @@ -7678,7 +7699,7 @@ "message": "Požaduje sa schválenie zariadenia." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Používateľ nastavil hlavné heslo počas odhlasovania z TDE." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Začnite 7-dňovú skúšobnú dobu Bitwardenu zadarmo pre $ORG$", @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Pridať pole" }, "items": { "message": "Položky" @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "zakúpené sedenia boli odstránené" }, + "fileSends": { + "message": "Sendy so súborom" + }, + "textSends": { + "message": "Textové Sendy" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 1b883f6da49..7d817afbb73 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Vsaj toliko besed" }, - "defaultType": { - "message": "Privzeta vrsta" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Naključna beseda" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Storitev" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index db682c3d9e4..437ecb40791 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -49,22 +49,22 @@ "message": "Кључ аутентификатора" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Опције Ауто-пуњења" }, "websiteUri": { - "message": "Website (URI)" + "message": "Вебсајт (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Вебсајт додат" }, "addWebsite": { - "message": "Add website" + "message": "Додај вебсајт" }, "deleteWebsite": { - "message": "Delete website" + "message": "Обриши вебсајт" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Подразумевано ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Прикажи откривање подударања $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Сакриј откривање подударања $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Ауто-попуњавање при учитавању странице?" }, "number": { "message": "Број" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Минимални број речи" }, - "defaultType": { - "message": "Подразумевани тип" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Подешавање Корисника" @@ -5323,7 +5324,7 @@ "message": "Ваша главна лозинка не испуњава једну или више смерница ваше организације. Да бисте приступили сефу, морате одмах да ажурирате главну лозинку. Ако наставите, одјавићете се са ваше тренутне сесије, што захтева да се поново пријавите. Активне сесије на другим уређајима могу да остану активне до један сат." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Ваша организација је ажурирала опције дешифровања. Поставите главну лозинку за приступ вашем сефу." }, "maximumVaultTimeout": { "message": "Тајмаут сефа" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Случајна реч" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Сервис" }, @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Додај поље" }, "items": { "message": "Ставке" @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "купљена места уклоњена" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index fc81cfdd45f..f6c45165a6b 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimalan broj reči" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index bab5fbecb5c..415cabbed63 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -52,19 +52,19 @@ "message": "Auto-fill options" }, "websiteUri": { - "message": "Website (URI)" + "message": "Webbplats (URI)" }, "websiteAdded": { "message": "Website added" }, "addWebsite": { - "message": "Add website" + "message": "Lägg till webbplats" }, "deleteWebsite": { - "message": "Delete website" + "message": "Radera webbplats" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Standard ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minsta antal ord" }, - "defaultType": { - "message": "Standardtyp" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Användarpreferens" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Slumpmässigt ord" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Tjänst" }, @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Lägg till fält" }, "items": { "message": "Objekt" @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "för $COUNT$ medlem", "placeholders": { diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index e2a3a42e575..3b3ab45fbd5 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 7392d1108d5..8a8d2102b1a 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 271e8dc2317..443ef8e2419 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -2927,7 +2927,7 @@ "message": "Tümü" }, "addAccess": { - "message": "Add Access" + "message": "Erişim ekle" }, "addAccessFilter": { "message": "Add Access Filter" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum kelime sayısı" }, - "defaultType": { - "message": "Varsayılan tür" + "overridePasswordTypePolicy": { + "message": "Parola türü", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Kullanıcı tercihi" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Rastgele kelime" }, + "usernameGenerator": { + "message": "Kullanıcı adı üreteci" + }, + "useThisPassword": { + "message": "Bu parolayı kullan" + }, + "useThisUsername": { + "message": "Bu kullanıcı adını kullan" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Servis" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "Dosya Send'leri" + }, + "textSends": { + "message": "Metin Send'leri" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index c97b4bafb0e..e4a4ebc5bbb 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -49,22 +49,22 @@ "message": "Ключ автентифікації" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Параметри автозаповнення" }, "websiteUri": { - "message": "Website (URI)" + "message": "Вебсайт (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Вебсайт додано" }, "addWebsite": { - "message": "Add website" + "message": "Додати вебсайт" }, "deleteWebsite": { - "message": "Delete website" + "message": "Видалити вебсайт" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Типово ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Показати виявлення збігів $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Приховати виявлення збігів $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Автоматично заповнювати під час завантаження сторінки?" }, "number": { "message": "Номер" @@ -107,7 +107,7 @@ "message": "Код безпеки (CVV)" }, "identityName": { - "message": "Назва" + "message": "Назва посвідчення" }, "company": { "message": "Компанія" @@ -336,7 +336,7 @@ "message": "Шукати в обраному" }, "searchLogin": { - "message": "Шукати записи входу", + "message": "Шукати записи", "description": "Search Login type" }, "searchCard": { @@ -344,7 +344,7 @@ "description": "Search Card type" }, "searchIdentity": { - "message": "Шукати особисті дані", + "message": "Шукати посвідчення", "description": "Search Identity type" }, "searchSecureNote": { @@ -367,7 +367,7 @@ "message": "Пошук в групах" }, "allItems": { - "message": "Всі елементи" + "message": "Усі записи" }, "favorites": { "message": "Обране" @@ -382,7 +382,7 @@ "message": "Картка" }, "typeIdentity": { - "message": "Особисті дані" + "message": "Посвідчення" }, "typeSecureNote": { "message": "Захищена нотатка" @@ -394,7 +394,7 @@ "message": "Картки" }, "typeIdentityPlural": { - "message": "Особисті дані" + "message": "Посвідчення" }, "typeSecureNotePlural": { "message": "Захищені нотатки" @@ -544,7 +544,7 @@ "message": "Сховища" }, "vaultItems": { - "message": "Елементи сховища" + "message": "Записи сховища" }, "filter": { "message": "Фільтр" @@ -617,7 +617,7 @@ } }, "itemsMovedToOrg": { - "message": "Елементи переміщено до $ORGNAME$", + "message": "Записи переміщено до $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -626,7 +626,7 @@ } }, "itemMovedToOrg": { - "message": "Елемент переміщено до $ORGNAME$", + "message": "Запис переміщено до $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -1001,7 +1001,7 @@ "message": "Нова організація" }, "noOrganizationsList": { - "message": "Ви не входите до жодної організації. Організації дозволяють безпечно обмінюватися елементами з іншими користувачами." + "message": "Ви не входите до жодної організації. Організації дають змогу безпечно обмінюватися записами з іншими користувачами." }, "notificationSentDevice": { "message": "Сповіщення було надіслано на ваш пристрій." @@ -1502,7 +1502,7 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { - "message": "новий елемент", + "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.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { @@ -1523,7 +1523,7 @@ "message": "Дані успішно імпортовано" }, "importSuccessNumberOfItems": { - "message": "Всього імпортовано $AMOUNT$ елементів.", + "message": "Всього імпортовано $AMOUNT$ записів.", "placeholders": { "amount": { "content": "$1", @@ -1575,7 +1575,7 @@ } }, "importUnassignedItemsError": { - "message": "Файл містить непризначені елементи." + "message": "Файл містить непризначені записи." }, "selectFormat": { "message": "Оберіть формат імпортованого файлу" @@ -2921,7 +2921,7 @@ "message": "Користувач" }, "userDesc": { - "message": "Доступ та додавання елементів до призначених збірок" + "message": "Доступ та додавання записів до призначених збірок" }, "all": { "message": "Усі" @@ -3020,7 +3020,7 @@ } }, "editedItemId": { - "message": "Змінений елемент $ID$.", + "message": "Змінений запис $ID$.", "placeholders": { "id": { "content": "$1", @@ -3077,7 +3077,7 @@ } }, "viewedCardNumberItemId": { - "message": "Переглянуто номер картки для елемента $ID$.", + "message": "Переглянуто номер картки для запису $ID$.", "placeholders": { "id": { "content": "$1", @@ -3104,7 +3104,7 @@ } }, "editItemWithName": { - "message": "Редагувати елемент - $NAME$", + "message": "Редагувати запис - $NAME$", "placeholders": { "name": { "content": "$1", @@ -3269,7 +3269,7 @@ } }, "createdAttachmentForItem": { - "message": "Створено вкладення для елемента $ID$.", + "message": "Створено вкладення для запису $ID$.", "placeholders": { "id": { "content": "$1", @@ -3278,7 +3278,7 @@ } }, "deletedAttachmentForItem": { - "message": "Видалено вкладення для елемента $ID$.", + "message": "Видалено вкладення для запису $ID$.", "placeholders": { "id": { "content": "$1", @@ -3287,7 +3287,7 @@ } }, "editedCollectionsForItem": { - "message": "Змінена збірка для елемента $ID$.", + "message": "Змінено збірку для запису $ID$.", "placeholders": { "id": { "content": "$1", @@ -3968,7 +3968,7 @@ "message": "Власник" }, "whoOwnsThisItem": { - "message": "Хто є власником цього елемента?" + "message": "Хто є власником цього запису?" }, "strong": { "message": "Надійний", @@ -4002,7 +4002,7 @@ "message": "Ви справді хочете оновити ключ шифрування облікового запису?" }, "attachmentsNeedFix": { - "message": "Цей елемент має старі вкладені файли, які необхідно виправити." + "message": "Цей запис має старі вкладені файли, які необхідно виправити." }, "attachmentFixDescription": { "message": "Це вкладення використовує застаріле шифрування. Виберіть \"Виправити\", щоб завантажити, повторно зашифрувати та вивантажити вкладення." @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Мінімальна кількість слів" }, - "defaultType": { - "message": "Стандартний тип" + "overridePasswordTypePolicy": { + "message": "Тип пароля", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Налаштовано користувачем" @@ -4793,7 +4794,7 @@ "message": "Політика організації впливає на ваші параметри власності." }, "personalOwnershipPolicyInEffectImports": { - "message": "Політика організації заблокувала імпортування елементів до вашого особистого сховища." + "message": "Політика організації заблокувала імпортування записів до вашого особистого сховища." }, "personalOwnershipCheckboxDesc": { "message": "Вилучити особисту власність для учасників організації" @@ -5090,10 +5091,10 @@ "message": "Порожня відповідь на скидання пароля" }, "trashCleanupWarning": { - "message": "Елементи, що знаходяться в смітнику понад 30 днів, автоматично видалятимуться." + "message": "Записи, що знаходяться в смітнику понад 30 днів, автоматично видалятимуться." }, "trashCleanupWarningSelfHosted": { - "message": "Елементи, що знаходяться в смітнику деякий час, автоматично видалятимуться." + "message": "Записи, що знаходяться в смітнику деякий час, автоматично видалятимуться." }, "passwordPrompt": { "message": "Повторний запит головного пароля" @@ -5323,7 +5324,7 @@ "message": "Ваш головний пароль не відповідає одній або більше політикам вашої організації. Щоб отримати доступ до сховища, вам необхідно оновити свій головний пароль зараз. Продовживши, ви вийдете з поточного сеансу, після чого потрібно буде повторно виконати вхід. Сеанси на інших пристроях можуть залишатися активними протягом однієї години." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "Ваша організація оновила параметри дешифрування. Встановіть головний пароль для доступу до сховища." }, "maximumVaultTimeout": { "message": "Час очікування сховища" @@ -5431,7 +5432,7 @@ "message": "Конфігурація провайдера послуг SAML" }, "samlIdpConfig": { - "message": "Конфігурація провайдера облікових даних SAML" + "message": "Конфігурація провайдера ідентифікації SAML" }, "callbackPath": { "message": "Шлях до зворотного виклику" @@ -5936,7 +5937,7 @@ } }, "idpSingleSignOnServiceUrlRequired": { - "message": "Обов'язково, якщо ID елемента не є URL." + "message": "Обов'язково, якщо ID об'єкта не є URL." }, "openIdOptionalCustomizations": { "message": "Додаткові налаштування" @@ -5966,7 +5967,7 @@ } }, "exportingOrganizationVaultDesc": { - "message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Елементи особистих сховищ або інших організацій не будуть включені.", + "message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Записи особистих сховищ або інших організацій не будуть включені.", "placeholders": { "organization": { "content": "$1", @@ -6049,11 +6050,31 @@ "randomWord": { "message": "Випадкове слово" }, + "usernameGenerator": { + "message": "Генератор імені користувача" + }, + "useThisPassword": { + "message": "Використати цей пароль" + }, + "useThisUsername": { + "message": "Використати це ім'я користувача" + }, + "securePasswordGenerated": { + "message": "Надійний пароль згенеровано! Обов'язково оновіть свій пароль на вебсайті." + }, + "useGeneratorHelpTextPartOne": { + "message": "Скористатися генератором", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "для створення надійного, унікального пароля", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Послуга" }, "unknownCipher": { - "message": "Невідомий елемент. Можливо, вам необхідно запитати дозвіл для доступу до нього." + "message": "Невідомий запис. Можливо, вам необхідно запитати дозвіл для доступу до нього." }, "cannotSponsorSelf": { "message": "Ви не можете отримати для активного облікового запису. Введіть іншу адресу е-пошти." @@ -6249,7 +6270,7 @@ "description": "The text, 'SCIM', is an acronymn and should not be translated." }, "scimDescription": { - "message": "Автоматично забезпечувати користувачів та групи бажаним провайдером облікових даних через SCIM", + "message": "Автоматично забезпечувати користувачів та групи бажаним провайдером ідентифікації через SCIM", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDesc": { @@ -6257,7 +6278,7 @@ "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDescHelpText": { - "message": "Налаштуйте бажаного провайдера облікових даних, вказавши URL-адресу та ключ API для SCIM", + "message": "Налаштуйте бажаного провайдера ідентифікації, вказавши URL-адресу та ключ API для SCIM", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimApiKeyHelperText": { @@ -7207,7 +7228,7 @@ "message": "Експорт секретних даних організації" }, "exportingOrganizationSecretDataDescription": { - "message": "Будуть експортовані лише дані менеджера секретів, пов'язані з $ORGANIZATION$. Елементи інших продуктів чи інших організацій не будуть включені.", + "message": "Будуть експортовані лише дані менеджера секретів, пов'язані з $ORGANIZATION$. Записи інших продуктів чи інших організацій не будуть включені.", "placeholders": { "ORGANIZATION": { "content": "$1", @@ -7678,7 +7699,7 @@ "message": "Запит підтвердження пристрою." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Користувач встановив головний пароль під час деактивації TDE." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Розпочніть 7-денний пробний період Bitwarden для $ORG$", @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "Додати поле" }, "items": { "message": "Записи" @@ -8461,10 +8482,10 @@ "message": "Перемкнути меню організації" }, "vaultItemSelect": { - "message": "Вибрати елемент сховища" + "message": "Вибрати запис сховища" }, "collectionItemSelect": { - "message": "Вибрати елемент збірки" + "message": "Вибрати запис збірки" }, "manageBillingFromProviderPortalMessage": { "message": "Керування рахунками на порталі провайдера" @@ -8646,7 +8667,7 @@ "message": "Переконайтеся, що учасники мають доступ до належних облікових даних і їхні облікові записи надійні. Використовуйте цей звіт, щоб отримати файл CSV з даними про доступ учасників та конфігурації облікових записів." }, "memberAccessReportPageDesc": { - "message": "Аудит доступу учасника організації до груп, збірок та елементів збірок. Експорт CSV надає детальну розбивку для кожного учасника, зокрема інформацію про дозволи для збірок та конфігурації облікового запису." + "message": "Аудит доступу учасника організації до груп, збірок та записів збірок. Експорт CSV надає детальну розбивку для кожного учасника, зокрема інформацію про дозволи для збірок та конфігурації облікового запису." }, "higherKDFIterations": { "message": "Вищі значення KDF-ітерацій можуть допомогти захистити ваш головний пароль від грубого зламу зловмисником." @@ -8735,7 +8756,7 @@ "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": "Повідомлення: пізніше цього місяця приватність клієнтського сховища буде поліпшено й учасники провайдера більше не матимуть прямого доступу до елементів клієнтських сховищ. Для отримання відповідей на запитання,", + "message": "Повідомлення: пізніше цього місяця приватність клієнтського сховища буде поліпшено й учасники провайдера більше не матимуть прямого доступу до записів клієнтських сховищ. Для отримання відповідей на запитання,", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'." }, "contactBitwardenSupport": { @@ -8755,10 +8776,10 @@ "message": "Вибрати теку" }, "personalItemTransferWarningSingular": { - "message": "1 елемент буде остаточно перенесено до вибраної організації. Ви більше не будете власником цього елемента." + "message": "1 запис буде остаточно перенесено до вибраної організації. Ви більше не будете власником цього запису." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ елементи буде остаточно перенесено до вибраної організації. Ви більше не будете власником цих елементів.", + "message": "$PERSONAL_ITEMS_COUNT$ записів будуть остаточно перенесені до вибраної організації. Ви більше не будете власником цих записів.", "placeholders": { "personal_items_count": { "content": "$1", @@ -8767,7 +8788,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 елемент буде остаточно перенесено до $ORG$. Ви більше не будете власником цього елемента.", + "message": "1 запис буде остаточно перенесено до $ORG$. Ви більше не будете власником цього запису.", "placeholders": { "org": { "content": "$1", @@ -8776,7 +8797,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ елементи буде остаточно перенесено до $ORG$. Ви більше не будете власником цих елементів.", + "message": "$PERSONAL_ITEMS_COUNT$ записів будуть остаточно перенесені до $ORG$. Ви більше не будете власником цих записів.", "placeholders": { "personal_items_count": { "content": "$1", @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "куплених місць вилучено" }, + "fileSends": { + "message": "Відправлення файлів" + }, + "textSends": { + "message": "Відправлення тексту" + }, "includesXMembers": { "message": "за $COUNT$ учасника", "placeholders": { diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 458855f71a9..3dd422fcfb0 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "Random word" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "Service" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index f8e255ecd4c..b4bc9bce069 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -49,22 +49,22 @@ "message": "验证器密钥" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "自动填充选项" }, "websiteUri": { - "message": "Website (URI)" + "message": "网站 (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "网址已添加" }, "addWebsite": { - "message": "Add website" + "message": "添加网站" }, "deleteWebsite": { - "message": "Delete website" + "message": "删除网站" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "默认 ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -74,7 +74,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "显示匹配检测 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -83,7 +83,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "隐藏匹配检测 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -92,7 +92,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "页面加载时自动填充吗?" }, "number": { "message": "号码" @@ -113,7 +113,7 @@ "message": "公司" }, "ssn": { - "message": "社会保险号码" + "message": "社会保障号码" }, "passportNumber": { "message": "护照号码" @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "单词最少个数" }, - "defaultType": { - "message": "默认类型" + "overridePasswordTypePolicy": { + "message": "密码类型", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "用户偏好设置" @@ -5323,7 +5324,7 @@ "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "您的组织更新了您的解密选项。要访问您的密码库,请设置一个主密码。" }, "maximumVaultTimeout": { "message": "密码库超时时间" @@ -5413,7 +5414,7 @@ "message": "为所有现有成员和新成员激活浏览器扩展上的页面加载时的自动填充设置。" }, "experimentalFeature": { - "message": "不完整或不信任的网站可以在页面加载时自动填充。" + "message": "不完整或不信任的网站可以使用页面加载时自动填充。" }, "learnMoreAboutAutofill": { "message": "了解更多关于自动填充的信息" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "随机单词" }, + "usernameGenerator": { + "message": "用户名生成器" + }, + "useThisPassword": { + "message": "使用此密码" + }, + "useThisUsername": { + "message": "使用此用户名" + }, + "securePasswordGenerated": { + "message": "安全密码生成好了!别忘了也在网站上更新一下您的密码。" + }, + "useGeneratorHelpTextPartOne": { + "message": "使用此生成器", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "创建一个强大且唯一的密码", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "服务" }, @@ -7144,7 +7165,7 @@ "message": "切换产品" }, "freeOrgInvLimitReachedManageBilling": { - "message": "免费组织最多拥有 $SEATCOUNT$ 位成员。升级到付费计划以邀请更多成员。", + "message": "免费组织最多拥有 $SEATCOUNT$ 位成员。要邀请更多成员,请升级到付费计划。", "placeholders": { "seatcount": { "content": "$1", @@ -7162,7 +7183,7 @@ } }, "teamsStarterPlanInvLimitReachedManageBilling": { - "message": "团队入门版计划最多拥有 $SEATCOUNT$ 位成员。升级您的计划并以邀请更多成员。", + "message": "团队入门版计划最多拥有 $SEATCOUNT$ 位成员。要邀请更多成员,请升级您的计划。", "placeholders": { "seatcount": { "content": "$1", @@ -7171,7 +7192,7 @@ } }, "teamsStarterPlanInvLimitReachedNoManageBilling": { - "message": "团队入门版计划最多拥有 $SEATCOUNT$ 位成员。请联系您的组织所有者升级您的计划以邀请更多成员。", + "message": "团队入门版计划最多拥有 $SEATCOUNT$ 位成员。要升级您的计划及邀请更多成员,请联系您的组织所有者。", "placeholders": { "seatcount": { "content": "$1", @@ -7678,7 +7699,7 @@ "message": "请求了设备批准。" }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "用户在 TDE 卸载期间设置了主密码。" }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "开始 $ORG$ 的 Bitwarden 7 天免费试用", @@ -8077,7 +8098,7 @@ } }, "addField": { - "message": "Add field" + "message": "添加字段" }, "items": { "message": "项目" @@ -8794,8 +8815,14 @@ "purchasedSeatsRemoved": { "message": "购买的席位已移除" }, + "fileSends": { + "message": "文件 Send" + }, + "textSends": { + "message": "文本 Send" + }, "includesXMembers": { - "message": "for $COUNT$ member", + "message": "适用于 $COUNT$ 位成员", "placeholders": { "count": { "content": "$1", @@ -8813,7 +8840,7 @@ } }, "optionalOnPremHosting": { - "message": "可选本地托管" + "message": "可选的本地托管" }, "upgradeFreeOrganization": { "message": "升级您的 $NAME$ 组织 ", @@ -8828,7 +8855,7 @@ "message": "SSO 身份验证" }, "familiesPlanInvLimitReachedManageBilling": { - "message": "家庭组织最多拥有 $SEATCOUNT$ 位成员。升级到付费计划以邀请更多成员。", + "message": "家庭组织最多拥有 $SEATCOUNT$ 位成员。要邀请更多成员,请升级到付费计划。", "placeholders": { "seatcount": { "content": "$1", @@ -8837,7 +8864,7 @@ } }, "familiesPlanInvLimitReachedNoManageBilling": { - "message": "家庭组织最多拥有 $SEATCOUNT$ 位成员。联系您的组织所有者以升级。", + "message": "家庭组织最多拥有 $SEATCOUNT$ 位成员。要升级,请联系您的组织所有者。", "placeholders": { "seatcount": { "content": "$1", @@ -8846,7 +8873,7 @@ } }, "upgradePlan": { - "message": "升级您的计划以邀请更多成员并获得附加的 Bitwarden 功能" + "message": "要邀请更多成员并获得附加的 Bitwarden 功能,请升级您的计划" }, "upgradeDiscount": { "message": "节省 $AMOUNT$%", @@ -8861,7 +8888,7 @@ "message": "适用于大型企业的高级功能" }, "upgradeTeamsMessage": { - "message": "企业寻求的强大安全保障" + "message": "企业寻求的强大安全性" }, "teamsInviteMessage": { "message": "邀请不限数量的成员" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index a7de3b01780..e474e013265 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "最少單字個數" }, - "defaultType": { - "message": "預設類型" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "使用者偏好設定" @@ -6049,6 +6050,26 @@ "randomWord": { "message": "隨機單字" }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "service": { "message": "服務" }, @@ -8794,6 +8815,12 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "includesXMembers": { "message": "for $COUNT$ member", "placeholders": { From b3fb3f70945ce11d45c628fbbacd0656e2d562ae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:24:27 +0000 Subject: [PATCH 25/59] Autosync the updated translations (#10461) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 71 ++++++ apps/browser/src/_locales/az/messages.json | 107 +++++++-- apps/browser/src/_locales/be/messages.json | 71 ++++++ apps/browser/src/_locales/bg/messages.json | 87 ++++++- apps/browser/src/_locales/bn/messages.json | 71 ++++++ apps/browser/src/_locales/bs/messages.json | 71 ++++++ apps/browser/src/_locales/ca/messages.json | 165 +++++++++---- apps/browser/src/_locales/cs/messages.json | 95 +++++++- apps/browser/src/_locales/cy/messages.json | 71 ++++++ apps/browser/src/_locales/da/messages.json | 73 +++++- apps/browser/src/_locales/de/messages.json | 113 +++++++-- apps/browser/src/_locales/el/messages.json | 73 +++++- apps/browser/src/_locales/en_GB/messages.json | 73 +++++- apps/browser/src/_locales/en_IN/messages.json | 73 +++++- apps/browser/src/_locales/es/messages.json | 71 ++++++ apps/browser/src/_locales/et/messages.json | 71 ++++++ apps/browser/src/_locales/eu/messages.json | 71 ++++++ apps/browser/src/_locales/fa/messages.json | 71 ++++++ apps/browser/src/_locales/fi/messages.json | 107 +++++++-- apps/browser/src/_locales/fil/messages.json | 71 ++++++ apps/browser/src/_locales/fr/messages.json | 71 ++++++ apps/browser/src/_locales/gl/messages.json | 71 ++++++ apps/browser/src/_locales/he/messages.json | 71 ++++++ apps/browser/src/_locales/hi/messages.json | 73 +++++- apps/browser/src/_locales/hr/messages.json | 115 +++++++-- apps/browser/src/_locales/hu/messages.json | 95 +++++++- apps/browser/src/_locales/id/messages.json | 71 ++++++ apps/browser/src/_locales/it/messages.json | 71 ++++++ apps/browser/src/_locales/ja/messages.json | 95 +++++++- apps/browser/src/_locales/ka/messages.json | 71 ++++++ apps/browser/src/_locales/km/messages.json | 71 ++++++ apps/browser/src/_locales/kn/messages.json | 71 ++++++ apps/browser/src/_locales/ko/messages.json | 89 ++++++- apps/browser/src/_locales/lt/messages.json | 71 ++++++ apps/browser/src/_locales/lv/messages.json | 73 +++++- apps/browser/src/_locales/ml/messages.json | 71 ++++++ apps/browser/src/_locales/mr/messages.json | 71 ++++++ apps/browser/src/_locales/my/messages.json | 71 ++++++ apps/browser/src/_locales/nb/messages.json | 71 ++++++ apps/browser/src/_locales/ne/messages.json | 71 ++++++ apps/browser/src/_locales/nl/messages.json | 79 +++++- apps/browser/src/_locales/nn/messages.json | 71 ++++++ apps/browser/src/_locales/or/messages.json | 71 ++++++ apps/browser/src/_locales/pl/messages.json | 71 ++++++ apps/browser/src/_locales/pt_BR/messages.json | 225 ++++++++++++------ apps/browser/src/_locales/pt_PT/messages.json | 73 +++++- apps/browser/src/_locales/ro/messages.json | 217 +++++++++++------ apps/browser/src/_locales/ru/messages.json | 77 +++++- apps/browser/src/_locales/si/messages.json | 71 ++++++ apps/browser/src/_locales/sk/messages.json | 95 +++++++- apps/browser/src/_locales/sl/messages.json | 71 ++++++ apps/browser/src/_locales/sr/messages.json | 107 +++++++-- apps/browser/src/_locales/sv/messages.json | 85 ++++++- apps/browser/src/_locales/te/messages.json | 71 ++++++ apps/browser/src/_locales/th/messages.json | 71 ++++++ apps/browser/src/_locales/tr/messages.json | 71 ++++++ apps/browser/src/_locales/uk/messages.json | 133 ++++++++--- apps/browser/src/_locales/vi/messages.json | 71 ++++++ apps/browser/src/_locales/zh_CN/messages.json | 129 +++++++--- apps/browser/src/_locales/zh_TW/messages.json | 71 ++++++ apps/browser/store/locales/zh_CN/copy.resx | 2 +- 61 files changed, 4701 insertions(+), 441 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 48e7de085fe..0373055d1bf 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "تحرير المجلّد" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "حذف المجلّد" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "واحدة أو أكثر من سياسات المؤسسة تؤثر على إعدادات المولدات الخاصة بك." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "إجراء مهلة المخزن" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "عدم تطابق الحساب" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "لم يتم إعداد القياسات الحيوية" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 09e43ab2dc5..7f62c82ae24 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Qovluğa düzəliş et" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Qovluğu sil" }, @@ -1343,13 +1361,13 @@ "message": "Anbarı yan çubuqda aç" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "Hazırkı veb sayt üçün son istifadə edilən girişi avto-doldur" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "Hazırkı veb sayt üçün son istifadə edilən kartı avto-doldur" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "Hazırkı veb sayt üçün son istifadə edilən kimliyi avto-doldur" }, "commandGeneratePasswordDesc": { "message": "Təsadüfi yeni bir parol yarat və lövhəyə kopyala" @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Baza domeni (tövsiyə edilən)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Bir və ya daha çox təşkilat siyasəti yaradıcı ayarlarınıza təsir edir." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Anbar vaxtının bitmə əməliyyatı" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Hesablar uyuşmur" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometriklər qurulmayıb" }, @@ -2328,7 +2375,7 @@ "message": "Ana parolunuz təşkilatınızdakı siyasətlərdən birinə və ya bir neçəsinə uyğun gəlmir. Anbara müraciət üçün ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış edəcəksiniz və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Təşkilatınız, güvənli cihaz şifrələməsini sıradan çıxartdı. Anbarınıza müraciət etmək üçün lütfən ana parol təyin edin." }, "resetPasswordPolicyAutoEnroll": { "message": "Avtomatik yazılma" @@ -2787,16 +2834,16 @@ "message": "Qısayolu dəyişdir" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "Qısayolları idarə et" }, "autofillShortcut": { "message": "Avto-doldurma klaviatura qısayolu" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "Avto-doldurma giriş qısayolu ayarlanmayıb. Bunu brauzerin ayarlarında dəyişdirin." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "Girişin avto-doldurma qısayolu: $COMMAND$. Bütün qısayolları brauzerin ayarlarında idarə edin.", "placeholders": { "command": { "content": "$1", @@ -3831,22 +3878,22 @@ "message": "Kimlik doğrulayıcı açarı" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Avto-doldurma seçimləri" }, "websiteUri": { - "message": "Website (URI)" + "message": "Veb sayt (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Veb sayt əlavə edildi" }, "addWebsite": { - "message": "Add website" + "message": "Veb sayt əlavə et" }, "deleteWebsite": { - "message": "Delete website" + "message": "Veb saytı sil" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "İlkin ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "$WEBSITE$ ilə uyuşma aşkarlamasını göstər", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "$WEBSITE$ ilə uyuşma aşkarlamasını gizlət", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Səhifə yüklənəndə avto-doldurulsun?" }, "cardDetails": { "message": "Kart detalları" @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Təyin et" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Element yeri" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden-in yeni bir görünüşü var!" }, @@ -4098,6 +4163,12 @@ "message": "Anbar vərəqindən avto-doldurma və axtarış etmə artıq daha asan və intuitivdir. Nəzər salın!" }, "accountActions": { - "message": "Account actions" + "message": "Hesab fəaliyyətləri" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 2dc68605199..4e78a92e8e1 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Рэдагаваць папку" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Выдаліць папку" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Адна або больш палітык арганізацыі ўплывае на налады генератара." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Дзеянне пасля заканчэння часу чакання сховішча" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Неадпаведнасць уліковых запісаў" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Біяметрыя не ўключана" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index b1fc24ac0f3..454988dda71 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Редактиране на папка" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Изтриване на папка" }, @@ -823,7 +841,7 @@ "message": "Показване на картите в страницата с разделите, за лесно автоматично попълване." }, "showIdentitiesInVaultView": { - "message": "Show identifies as Autofill suggestions on Vault view" + "message": "Показване на самоличности като предложения за самопопълване в изгледа на трезора" }, "showIdentitiesCurrentTab": { "message": "Показване на самоличности в страницата с разделите" @@ -1489,7 +1507,7 @@ "message": "Д-р" }, "mx": { - "message": "Mx" + "message": "Госпоуи" }, "firstName": { "message": "Собствено име" @@ -1567,7 +1585,7 @@ "message": "Самоличност" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Ново $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Поне една политика на организация влияе на настройките на генерирането на паролите." }, + "passwordGenerator": { + "message": "Генератор на пароли" + }, + "usernameGenerator": { + "message": "Генератор на потребителски имена" + }, + "useThisPassword": { + "message": "Използване на тази парола" + }, + "useThisUsername": { + "message": "Използване на това потребителско име" + }, + "securePasswordGenerated": { + "message": "Създадена е сигурна парола! Не забравяйте и да промените паролата си в уеб сайта." + }, + "useGeneratorHelpTextPartOne": { + "message": "Използвайте генератора", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "за да създадете сигурна и уникална парола", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Действие при изтичане на времето" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Регистрациите са различни" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Отключването чрез биометрични данни не беше успешно. Биометричният таен ключ не успя да отключи трезора. Опитайте да направите настройката на биометричните данни отново." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Несъответстващ биометричен ключ" + }, "biometricsNotEnabledTitle": { "message": "Потвърждаването с биометрични данни не е включено" }, @@ -2328,7 +2375,7 @@ "message": "Вашата главна парола не отговаря на една или повече политики на организацията Ви. За да получите достъп до трезора, трябва да промените главната си парола сега. Това означава, че ще бъдете отписан(а) от текущата си сесия и ще трябва да се впишете отново. Активните сесии на други устройства може да продължат да бъдат активни още един час." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Вашата организация е деактивирала шифроването чрез доверени устройства. Задайте главна парола, за да получите достъп до трезора си." }, "resetPasswordPolicyAutoEnroll": { "message": "Автоматично включване" @@ -3264,7 +3311,7 @@ "message": "Неправилна парола за файла. Използвайте паролата, която сте въвели при създаването на изнесения файл." }, "destination": { - "message": "Destination" + "message": "Местоназначение" }, "learnAboutImportOptions": { "message": "Научете повече относно възможностите за внасяне" @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Показване на откритото съвпадение $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Скриване на откритото съвпадение $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3897,6 +3944,18 @@ "data": { "message": "Данни" }, + "passkeys": { + "message": "Секретни ключове", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Пароли", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Вписване със секретен ключ", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Свързване" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Местоположение на елемента" }, + "fileSends": { + "message": "Файлови изпращания" + }, + "textSends": { + "message": "Текстови изпращания" + }, "bitwardenNewLook": { "message": "Биуорден има нов облик!" }, @@ -4098,6 +4163,12 @@ "message": "Сега е по-лесно и интуитивно от всякога да използвате автоматичното попълване и да търсите в раздела на трезора. Разгледайте!" }, "accountActions": { - "message": "Account actions" + "message": "Действия по регистрацията" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index efba4adaaa1..d6bc891a172 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "ফোল্ডার সম্পাদনা" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "ফোল্ডার মুছুন" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "এক বা একাধিক সংস্থার নীতিগুলি আপনার উৎপাদকের সেটিংসকে প্রভাবিত করছে।" }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "ভল্টের সময়সীমা কর্ম" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "অ্যাকাউন্ট মেলেনি" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "বায়োমেট্রিকস সক্ষম নেই" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 60a0fced110..198874339e9 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 66a089e08ee..fe0ec495b8a 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edita la carpeta" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Suprimeix carpeta" }, @@ -636,7 +654,7 @@ "message": "El codi de verificació és obligatori." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "L'autenticació s'ha cancel·lat o ha tardat massa. Torna-ho a provar." }, "invalidVerificationCode": { "message": "Codi de verificació no vàlid" @@ -664,10 +682,10 @@ "message": "Escaneja el codi QR de l'autenticador des de la pàgina web actual" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Feu que la verificació en dos passos siga perfecta" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden pot emmagatzemar i omplir codis de verificació en dos passos. Copieu i enganxeu la clau en aquest camp." }, "totpHelperWithCapture": { "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." @@ -685,7 +703,7 @@ "message": "La vostra sessió ha caducat." }, "logIn": { - "message": "Log in" + "message": "Inicia sessió" }, "restartRegistration": { "message": "Restart registration" @@ -761,7 +779,7 @@ "message": "Nova URI" }, "addDomain": { - "message": "Add domain", + "message": "Afig domini", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { @@ -873,7 +891,7 @@ "message": "Desbloqueja" }, "additionalOptions": { - "message": "Additional options" + "message": "Opcions addicionals" }, "enableContextMenuItem": { "message": "Mostra les opcions del menú contextual" @@ -913,7 +931,7 @@ "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportFrom": { - "message": "Export from" + "message": "Exporta des de" }, "exportVault": { "message": "Exporta caixa forta" @@ -925,25 +943,25 @@ "message": "This file export will be password protected and require the file password to decrypt." }, "filePassword": { - "message": "File password" + "message": "Contrasenya del fitxer" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "Aquesta contrasenya s'utilitzarà per exportar i importar aquest fitxer" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "Utilitzeu la clau de xifratge del vostre compte, derivada del nom d'usuari i la contrasenya mestra, per xifrar l'exportació i restringir la importació només al compte de Bitwarden actual." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "Establiu una contrasenya per xifrar l'exportació i importeu-la a qualsevol compte de Bitwarden mitjançant aquesta contrasenya." }, "exportTypeHeading": { - "message": "Export type" + "message": "Tipus d'exportació" }, "accountRestricted": { - "message": "Account restricted" + "message": "Hi ha una restricció de compte" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "\"Contrasenya del fitxer\" i \"Confirma contrasenya del fitxer\" no coincideixen." }, "warning": { "message": "ADVERTIMENT", @@ -1382,7 +1400,7 @@ "message": "Booleà" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Casella de selecció" }, "cfTypeLinked": { "message": "Enllaçat", @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Una o més polítiques d’organització afecten la configuració del generador." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Acció quan acabe el temps d'espera de la caixa forta" }, @@ -1938,7 +1979,7 @@ "message": "By continuing, you agree to the" }, "and": { - "message": "and" + "message": "i" }, "acceptPolicies": { "message": "Si activeu aquesta casella, indiqueu que esteu d’acord amb el següent:" @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "El compte no coincideix" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "La biomètrica no està habilitada" }, @@ -3702,7 +3749,7 @@ } }, "new": { - "message": "New" + "message": "Nou" }, "removeItem": { "message": "Remove $NAME$", @@ -3718,10 +3765,10 @@ "message": "Items with no folder" }, "itemDetails": { - "message": "Item details" + "message": "Detalls de l'element" }, "itemName": { - "message": "Item name" + "message": "Nom d'element" }, "cannotRemoveViewOnlyCollections": { "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", @@ -3736,29 +3783,29 @@ "message": "Organization is deactivated" }, "owner": { - "message": "Owner" + "message": "Propietari" }, "selfOwnershipLabel": { - "message": "You", + "message": "Tú", "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." + "message": "No es pot accedir als elements de les organitzacions inhabilitades. Poseu-vos en contacte amb el propietari de la vostra organització per obtenir ajuda." }, "additionalInformation": { - "message": "Additional information" + "message": "Informació addicional" }, "itemHistory": { "message": "Item history" }, "lastEdited": { - "message": "Last edited" + "message": "Última edició" }, "ownerYou": { "message": "Owner: You" }, "linked": { - "message": "Linked" + "message": "Enllaçat" }, "copySuccessful": { "message": "Copy Successful" @@ -3800,16 +3847,16 @@ "message": "Free organizations cannot use attachments" }, "filters": { - "message": "Filters" + "message": "Filtres" }, "personalDetails": { - "message": "Personal details" + "message": "Detalls personals" }, "identification": { - "message": "Identification" + "message": "Identificació" }, "contactInfo": { - "message": "Contact info" + "message": "Informació de contacte" }, "downloadAttachment": { "message": "Download - $ITEMNAME$", @@ -3825,28 +3872,28 @@ "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login credentials" + "message": "Credencials d'inici de sessió" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Clau autenticadora" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Opcions d'emplenament automàtic" }, "websiteUri": { - "message": "Website (URI)" + "message": "Lloc web (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Lloc web afegit" }, "addWebsite": { - "message": "Add website" + "message": "Afig un lloc web" }, "deleteWebsite": { - "message": "Delete website" + "message": "Suprimeix lloc web" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Per defecte ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3889,16 +3936,28 @@ } }, "addAccount": { - "message": "Add account" + "message": "Afig compte" }, "loading": { - "message": "Loading" + "message": "S'està carregant" }, "data": { - "message": "Data" + "message": "Dades" + }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "Assigna" }, "bulkCollectionAssignmentDialogDescriptionSingular": { "message": "Only organization members with access to these collections will be able to see the item." @@ -3919,16 +3978,16 @@ } }, "addField": { - "message": "Add field" + "message": "Afig un camp" }, "add": { - "message": "Add" + "message": "Afig" }, "fieldType": { - "message": "Field type" + "message": "Tipus de camp" }, "fieldLabel": { - "message": "Field label" + "message": "Etiqueta del camp" }, "textHelpText": { "message": "Use text fields for data like security questions" @@ -3946,7 +4005,7 @@ "message": "Enter the the field's html id, name, aria-label, or placeholder." }, "editField": { - "message": "Edit field" + "message": "Edita el camp" }, "editFieldLabel": { "message": "Edit $LABEL$", @@ -4002,7 +4061,7 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "Seleccioneu les col·leccions per assignar" }, "personalItemTransferWarningSingular": { "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 93f2f991a8d..c7afbeeac61 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Upravit složku" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Smazat složku" }, @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Základní doména (doporučeno)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Jedna nebo více zásad organizace ovlivňují nastavení generátoru." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Akce při vypršení časového limitu" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Neshoda účtů" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrie není nastavena" }, @@ -2328,7 +2375,7 @@ "message": "Vaše hlavní heslo nesplňuje jednu nebo více zásad Vaší organizace. Pro přístup k trezoru musíte nyní aktualizovat své hlavní heslo. Pokračování Vás odhlásí z Vaší aktuální relace a bude nutné se přihlásit. Aktivní relace na jiných zařízeních mohou zůstat aktivní až po dobu jedné hodiny." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Vaše organizace zakázala šifrování pomocí důvěryhodného zařízení. Nastavte hlavní heslo pro přístup k trezoru." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatická registrace" @@ -3831,22 +3878,22 @@ "message": "Ověřovací klíč" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Nastavení automatického vyplňování" }, "websiteUri": { - "message": "Website (URI)" + "message": "Webová stránka (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Webová stránka přidána" }, "addWebsite": { - "message": "Add website" + "message": "Přidat webovou stránku" }, "deleteWebsite": { - "message": "Delete website" + "message": "Vymazat webovou stránku" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Výchozí ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Zobrazit detekci shody $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Skrýt detekci shody $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Automaticky vyplnit při načtení stránky?" }, "cardDetails": { "message": "Podrobnosti karty" @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Přiřadit" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Umístění položky" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden má nový vzhled!" }, @@ -4098,6 +4163,12 @@ "message": "Je snazší a intuitivnější než kdy jindy automaticky vyplňovat a vyhledávat z karty trezor. Mrkněte se!" }, "accountActions": { - "message": "Account actions" + "message": "Činnosti účtu" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 12df0cb8fa4..857dcc13642 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Golygu ffolder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Dileu'r ffolder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 2fc1a8c8013..22032050711 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Redigér mappe" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Slet mappe" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Én eller flere organisationspolitikker påvirker dine generatorindstillinger." }, + "passwordGenerator": { + "message": "Adgangskodegenerator" + }, + "usernameGenerator": { + "message": "Brugernavngenerator" + }, + "useThisPassword": { + "message": "Anvend denne adgangskode" + }, + "useThisUsername": { + "message": "Anvend dette brugernavn" + }, + "securePasswordGenerated": { + "message": "Sikker adgangskode genereret! Glem ikke at opdatere adgangskoden på webstedet også." + }, + "useGeneratorHelpTextPartOne": { + "message": "Anvend generatoren", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "til at oprette en stærk, unik adgangskode", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Boks timeout-handling" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Konto mismatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometrisk oplåsning mislykkedes. Den hemmelige biometriske nøgle kunne ikke oplåse boksen. Prøv at opsætte biometri igen." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometrisk nøgle matcher ikke" + }, "biometricsNotEnabledTitle": { "message": "Biometri ikke aktiveret" }, @@ -2328,7 +2375,7 @@ "message": "Din hovedadgangskode overholder ikke en eller flere organisationspolitikker. For at få adgang til boksen skal hovedadgangskode opdateres nu. Fortsættes, logges du ud af den nuværende session og vil skulle logger ind igen. Aktive sessioner på andre enheder kan forblive aktive i op til én time." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Organisationen har deaktiveret betroet enhedskryptering. Opsæt en hovedadgangskode for at tilgå boksen." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatisk tilmelding" @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Adgangsnøgler", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Adgangskoder", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log ind med adgangsnøgle", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Tildel" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Emneplacering" }, + "fileSends": { + "message": "Fil-Sends" + }, + "textSends": { + "message": "Tekst-Sends" + }, "bitwardenNewLook": { "message": "Bitwarden har fået et nyt look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Kontohandlinger" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 9fe95936316..dc2e2d34bdd 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Ordner bearbeiten" }, + "newFolder": { + "message": "Neuer Ordner" + }, + "folderName": { + "message": "Ordnername" + }, + "folderHintText": { + "message": "Verschachtel einen Ordner, indem du den Namen des übergeordneten Ordners hinzufügst, gefolgt von einem „/“. Beispiel: Social/Foren" + }, + "noFoldersAdded": { + "message": "Keine Ordner hinzugefügt" + }, + "createFoldersToOrganize": { + "message": "Erstelle Ordner, um deine Tresor-Einträge zu organisieren" + }, + "deleteFolderPermanently": { + "message": "Bist du sicher, dass du diesen Ordner dauerhaft löschen willst?" + }, "deleteFolder": { "message": "Ordner löschen" }, @@ -1343,13 +1361,13 @@ "message": "Tresor in der Seitenleiste öffnen" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "Die zuletzt verwendeten Zugangsdaten für die aktuelle Website automatisch ausfüllen" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "Die zuletzt verwendete Karte für die aktuelle Website automatisch ausfüllen" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "Die zuletzt verwendete Identität für die aktuelle Website automatisch ausfüllen" }, "commandGeneratePasswordDesc": { "message": "Ein neues zufälliges Passwort generieren und in die Zwischenablage kopieren" @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Basis-Domain (empfohlen)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Eine oder mehrere Organisationsrichtlinien beeinflussen deine Generator-Einstellungen." }, + "passwordGenerator": { + "message": "Passwort-Generator" + }, + "usernameGenerator": { + "message": "Benutzernamen-Generator" + }, + "useThisPassword": { + "message": "Dieses Passwort verwenden" + }, + "useThisUsername": { + "message": "Diesen Benutzernamen verwenden" + }, + "securePasswordGenerated": { + "message": "Sicheres Passwort generiert! Vergiss nicht, auch dein Passwort auf der Website zu aktualisieren." + }, + "useGeneratorHelpTextPartOne": { + "message": "Verwende den Generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": ", um ein starkes einzigartiges Passwort zu erstellen", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Aktion bei Tresor-Timeout" }, @@ -1842,7 +1883,7 @@ "message": "Bestätigung der Timeout-Aktion" }, "autoFillAndSave": { - "message": "Auto-Ausfüllen und speichern" + "message": "Automatisch ausfüllen und speichern" }, "fillAndSave": { "message": "Ausfüllen und speichern" @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Konten stimmen nicht überein" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometrische Entsperrung fehlgeschlagen. Der biometrische Geheimschlüssel konnte den Tresor nicht entsperren. Bitte versuche Biometrie erneut einzurichten." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometrische Schlüssel stimmen nicht überein" + }, "biometricsNotEnabledTitle": { "message": "Biometrie ist nicht eingerichtet" }, @@ -2328,7 +2375,7 @@ "message": "Dein Master-Passwort entspricht nicht einer oder mehreren Richtlinien deiner Organisation. Um auf den Tresor zugreifen zu können, musst du dein Master-Passwort jetzt aktualisieren. Wenn du fortfährst, wirst du von deiner aktuellen Sitzung abgemeldet und musst dich erneut anmelden. Aktive Sitzungen auf anderen Geräten können noch bis zu einer Stunde lang aktiv bleiben." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Deine Organisation hat die vertrauenswürdige Geräteverschlüsselung deaktiviert. Bitte lege ein Master-Passwort fest, um auf deinen Tresor zuzugreifen." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatische Registrierung" @@ -2757,10 +2804,10 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Deine Organisationsrichtlinien haben Auto-Ausfüllen beim Laden von Seiten aktiviert." + "message": "Deine Organisationsrichtlinien haben das automatische Ausfüllen beim Laden der Seite aktiviert." }, "howToAutofill": { - "message": "So funktioniert Auto-Ausfüllen" + "message": "So funktioniert automatisches Ausfüllen" }, "autofillSelectInfoWithCommand": { "message": "Wähle einen Eintrag von dieser Bildschirmseite, verwende das Tastaturkürzel $COMMAND$ oder entdecke andere Optionen in den Einstellungen.", @@ -2787,16 +2834,16 @@ "message": "Tastenkombination ändern" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "Tastaturkürzel verwalten" }, "autofillShortcut": { "message": "Auto-Ausfüllen Tastaturkürzel" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "Das Tastaturkürzel zum automatischen Ausfüllen von Zugangsdaten ist nicht festgelegt. Du kannst es in den Browser-Einstellungen ändern." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "Das Tastaturkürzel zum automatischen Ausfüllen von Zugangsdaten ist $COMMAND$. Verwalte alle Tastaturkürzel in den Browser-Einstellungen.", "placeholders": { "command": { "content": "$1", @@ -2805,7 +2852,7 @@ } }, "autofillShortcutTextSafari": { - "message": "Standard-Auto-Ausfüllen Tastaturkürzel: $COMMAND$.", + "message": "Standard-Auto-Ausfüllen Tastenkombination: $COMMAND$.", "placeholders": { "command": { "content": "$1", @@ -3831,22 +3878,22 @@ "message": "Authenticator-Schlüssel" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Auto-Ausfüllen Optionen" }, "websiteUri": { "message": "Website (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Website hinzugefügt" }, "addWebsite": { - "message": "Add website" + "message": "Website hinzufügen" }, "deleteWebsite": { - "message": "Delete website" + "message": "Website löschen" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Standard ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Übereinstimmungs-Erkennung anzeigen $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Übereinstimmungs-Erkennung verstecken $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Auto-Ausfüllen beim Laden einer Seite?" }, "cardDetails": { "message": "Kartendetails" @@ -3897,6 +3944,18 @@ "data": { "message": "Daten" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwörter", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Mit Passkey anmelden", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Zuweisen" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Eintrags-Standort" }, + "fileSends": { + "message": "Datei-Sends" + }, + "textSends": { + "message": "Text-Sends" + }, "bitwardenNewLook": { "message": "Bitwarden hat einen neuen Look!" }, @@ -4098,6 +4163,12 @@ "message": "Auto-Ausfüllen und Suchen vom Tresor-Tab ist einfacher und intuitiver als je zuvor. Schau dich um!" }, "accountActions": { - "message": "Account actions" + "message": "Konto-Aktionen" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index b7393c6ec22..1e898f7e0bf 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Επεξεργασία φακέλου" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Διαγραφή φακέλου" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Μία ή περισσότερες πολιτικές του οργανισμού επηρεάζουν τις ρυθμίσεις της γεννήτριας." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Ενέργεια Χρόνου Λήξης Vault" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Απόρριψη λογαριασμού" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Το βιομετρικό ξεκλείδωμα απέτυχε. Το βιομετρικό μυστικό κλειδί απέτυχε να ξεκλειδώσει το θησαυ/κιο. Προσπαθήστε να ρυθμίσετε ξανά τα βιομετρικά." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Αναντιστοιχία βιομετρικού κλειδιού" + }, "biometricsNotEnabledTitle": { "message": "Δεν έχουν οριστεί βιομετρικά" }, @@ -2328,7 +2375,7 @@ "message": "Ο Κύριος κωδικός πρόσβασης δεν πληροί τις απαιτήσεις πολιτικής αυτού του οργανισμού. Για να έχετε πρόσβαση στο vault, πρέπει να ενημερώσετε τον Κύριο σας κωδικό άμεσα. Η διαδικασία θα σας αποσυνδέσει από την τρέχουσα συνεδρία σας, απαιτώντας από εσάς να συνδεθείτε ξανά. Οι ενεργές συνεδρίες σε άλλες συσκευές ενδέχεται να συνεχίσουν να είναι ενεργές για μία ώρα." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Ο οργανισμός σας έχει απενεργοποιήσει την κρυπτογράφηση αξιόπιστης συσκευής. Παρακαλώ ορίστε έναν κύριο κωδικό πρόσβασης για να αποκτήσετε πρόσβαση στο θησαυροφυλάκιο σας." }, "resetPasswordPolicyAutoEnroll": { "message": "Αυτόματη εγγραφή" @@ -3897,6 +3944,18 @@ "data": { "message": "Δεδομένα" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Ανάθεση" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Τοποθεσία Αντικειμένου" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Το Bitwarden έχει μια νέα εμφάνιση!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Ενέργειες λογαριασμού" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 4b875de6efb..624e715926f 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organise your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organisation policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account mismatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key mismatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -2328,7 +2375,7 @@ "message": "Your master password does not meet one or more of your organisation policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Your organisation has disabled trusted device encryption. Please set a master password to access your vault." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatic enrolment" @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index a0b5ddaa57e..65b347eacbc 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organisation policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key mismatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not enabled" }, @@ -2328,7 +2375,7 @@ "message": "Your master password does not meet one or more of your organisation policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Your organisation has disabled trusted device encryption. Please set a master password to access your vault." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatic Enrollment" @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 40bdc71f15a..fc501882ca0 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Editar carpeta" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Eliminar carpeta" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Una o más políticas de la organización están afectando la configuración del generador" }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Acción de tiempo de espera de la caja fuerte" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Las cuentas son distintas" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometría deshabilitada" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 81fec065db2..bdceb79217e 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Muuda kausta" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Kustuta Kaust" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Organisatsiooni seaded mõjutavad parooli genereerija sätteid." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Hoidla ajalõpu tegevus" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Kontod ei ühti" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biomeetria ei ole sisse lülitatud" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 87eb73b11a3..6dc87cff44c 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Editatu Karpeta" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Ezabatu karpeta" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Erakundeko politika batek edo gehiagok sortzailearen konfigurazioari eragiten diote." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Kutxa gotorraren itxaronaldiaren ekintza" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Kontu ezberdinak dira" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometria desgaitua" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 7b56e6c09cb..cbbb6cff3e4 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "ويرايش پوشه" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "حذف پوشه" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "یک یا چند سیاست سازمان بر تنظیمات تولید کننده شما تأثیر می‌گذارد." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "عمل متوقف شدن گاو‌صندوق" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "عدم مطابقت حساب کاربری" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "بیومتریک برپا نشده" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index c4944c3ce7a..0f5d9ff1623 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Muokkaa kansiota" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Poista kansio" }, @@ -1343,13 +1361,13 @@ "message": "Avaa holvi sivupalkissa" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "Automaattitäytä tällä sivustolla viimeksi käytetty kirjautumistieto." }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "Automaattitäytä tällä sivustolla viimeksi käytetty kortti." }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "Automaattitäytä tällä sivustolla viimeksi käytetty henkilöllisyys." }, "commandGeneratePasswordDesc": { "message": "Luo uusi satunnainen salasana ja kopioi se leikepöydälle." @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Juuriverkkotunnus (suositus)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Yksi tai useampi organisaatiokäytäntö vaikuttaa generaattorisi asetuksiin." }, + "passwordGenerator": { + "message": "Salasanageneraattori" + }, + "usernameGenerator": { + "message": "Käyttäjätunnusgeneraattori" + }, + "useThisPassword": { + "message": "Käytä tätä salasanaa" + }, + "useThisUsername": { + "message": "Käytä tätä käyttäjätunnusta" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Käytä generaattoria", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Holvin aikakatkaisutoiminto" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Tili ei täsmää" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometrinen avaus epäonnistui. Biometrinen salainen avain ei voinut avata holvia. Yritä määrittää biometria uudelleen." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometrinen avain ei täsmää" + }, "biometricsNotEnabledTitle": { "message": "Biometriaa ei ole määritetty" }, @@ -2328,7 +2375,7 @@ "message": "Pääsalasanasi ei täytä yhden tai useamman organisaatiokäytännön vaatimuksia ja holvin käyttämiseksi sinun on vaihdettava se nyt. Tämä uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Organisaatiosi on estänyt luotettavan laitesalauksen. Käytä holviasi asettamalla pääsalasana." }, "resetPasswordPolicyAutoEnroll": { "message": "Automaattinen liitos" @@ -2787,16 +2834,16 @@ "message": "Muuta pikanäppäintä" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "Hallitse pikanäppäimiä" }, "autofillShortcut": { "message": "Automaattitäytön pikanäppäin" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "Automaattitäytön pikanäppäintä ei ole määritetty. Määritä se selaimen asetuksista." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "Kirjautumistiedon automaatttitäytön pikanäppäin on $COMMAND$. Hallitse pikanäppäimiä selaimen asetuksista.", "placeholders": { "command": { "content": "$1", @@ -3831,22 +3878,22 @@ "message": "Todennusavain" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Automaattitäytön asetukset" }, "websiteUri": { - "message": "Website (URI)" + "message": "Verkkosivusto (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Verkkosivusto lisättiin" }, "addWebsite": { - "message": "Add website" + "message": "Lisää verkkosivusto" }, "deleteWebsite": { - "message": "Delete website" + "message": "Poista verkkosivusto" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Oletus ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Näytä vastaavuuden tunnistus $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Piilota vastaavuuden tunnistus $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Automaattitäytetäänkö sivun avautuessa?" }, "cardDetails": { "message": "Kortin tiedot" @@ -3897,6 +3944,18 @@ "data": { "message": "Tiedot" }, + "passkeys": { + "message": "Avainkoodit", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Salasanat", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Kirjaudu avainkoodilla", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Määritä" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Kohteen sijainti" }, + "fileSends": { + "message": "Tiedosto-Sendit" + }, + "textSends": { + "message": "Teksti-Sendit" + }, "bitwardenNewLook": { "message": "Bitwardenilla on uusi ulkoasu!" }, @@ -4098,6 +4163,12 @@ "message": "Automaattitäyttäminen ja hakujen tekeminen Holvi-välilehdeltä on entistä helpompaa ja intuitiivisempaa. Kokeile nyt!" }, "accountActions": { - "message": "Account actions" + "message": "Tilitoiminnot" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 9ed5ae7f85f..ee909343087 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "I-edit ang folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Burahin ang folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Isang o higit pang patakaran ng organisasyon ay nakakaapekto sa iyong mga setting ng generator." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Aksyon sa Vault timeout" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Mismatch sa Account" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Hindi naka-setup ang biometrics" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 47aec7928d3..ace9915f1d7 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Modifier le dossier" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Supprimer le dossier" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Une ou plusieurs politiques de sécurité de l'organisation affectent les paramètres de votre générateur." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Action après délai d'expiration du coffre" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Erreur de correspondance entre les comptes" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Le déverrouillage biométrique n'est pas activé" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 1ab856e8247..59e10828b19 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Editar cartafol" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Eliminar cartafol" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 0a0fab1e1c9..498f46447da 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "ערוך תיקייה" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "מחק תיקייה" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "מדיניות ארגונית אחת או יותר משפיעה על הגדרות המחולל שלך." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "פעולה לביצוע בכספת בתום זמן החיבור" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "חוסר התאמה בין חשבונות" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "אמצעי זיהוי ביומטרים לא מאופשרים" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 281e3bc011c..5dce5f7ff82 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit Folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete Folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "एक या एक से अधिक संगठन नीतियां आपकी जनरेटर सेटिंग को प्रभावित कर रही हैं।" }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "वॉल्ट मध्यांतर कार्रवाई" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "खाता गलत मैच" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "बायोमेट्रिक अनलॉक विफल रहा. बायोमेट्रिक गुप्त कुंजी वॉल्ट को अनलॉक करने में विफल रही. कृपया बायोमेट्रिक्स को फिर से सेट करने का प्रयास करें." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "बेमेल बायोमेट्रिक कुंजी" + }, "biometricsNotEnabledTitle": { "message": "बॉयोमीट्रिक्स सक्षम नहीं है" }, @@ -2328,7 +2375,7 @@ "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "आपके संगठन ने विश्वसनीय डिवाइस एन्क्रिप्शन अक्षम कर दिया है. कृपया अपने वॉल्ट तक पहुँचने के लिए मास्टर पासवर्ड सेट करें." }, "resetPasswordPolicyAutoEnroll": { "message": "स्वचालित नामांकन" @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "बिटवार्डन का नया रूप!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index b2f2366fab7..fb61827321c 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -135,13 +135,13 @@ "message": "Auto-ispuna" }, "autoFillLogin": { - "message": "Automatsko popunjavanje prijave" + "message": "Auto-ispuna prijave" }, "autoFillCard": { - "message": "Automatsko popunjavanje kartice" + "message": "Auto-ispuna kartice" }, "autoFillIdentity": { - "message": "Automatsko popunjavanje identiteta" + "message": "Auto-ispuna identiteta" }, "generatePasswordCopied": { "message": "Generiraj lozinku (i kopiraj)" @@ -304,6 +304,24 @@ "editFolder": { "message": "Uredi mapu" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Izbriši mapu" }, @@ -652,7 +670,7 @@ } }, "autofillError": { - "message": "Nije moguće automatski ispuniti odabranu stavku na ovoj stranici. Umjesto toga kopirajte i zalijepite podatke." + "message": "Nije moguće auto-ispuniti odabranu prijavu na ovoj stranici. Umjesto toga kopiraj/zalijepi podatke." }, "totpCaptureError": { "message": "Nije moguće skenirati QR kod s trenutne web stranice" @@ -867,7 +885,7 @@ "message": "Ažuriraj" }, "notificationUnlockDesc": { - "message": "Za dovršetak auto-ispune, otključaj svoj trezor." + "message": "Za dovršetak auto-ispune, otključaj svoj Bitwarden trezor." }, "notificationUnlock": { "message": "Otključaj" @@ -1104,7 +1122,7 @@ "message": "Automatski kopiraj TOTP" }, "disableAutoTotpCopyDesc": { - "message": "Ako se za prijavu koristi dvostruka autentifikacija, TOTP kontrolni kôd se automatski kopira u međuspremnik nakon auto-ispune korisničkog imena i lozinke." + "message": "Ako za prijavu postoji autentifikatorski ključ, kopiraj TOTP kontrolni kôd u međuspremnik nakon auto-ispune prijave." }, "enableAutoBiometricsPrompt": { "message": "Traži biometrijsku autentifikaciju pri pokretanju" @@ -1489,7 +1507,7 @@ "message": "dr." }, "mx": { - "message": "Mx" + "message": "gx." }, "firstName": { "message": "Ime" @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Osnovna domena (preporučeno)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Jedno ili više pravila organizacije utječe na postavke generatora." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Nakon isteka trezora" }, @@ -1848,10 +1889,10 @@ "message": "Ispuni i spremi" }, "autoFillSuccessAndSavedUri": { - "message": "Auto-ispunjena stavka i spremanje URI" + "message": "Stavka auto-ispunjena i spremljen URI" }, "autoFillSuccess": { - "message": "Stavka je automatski popunjena" + "message": "Stavka je auto-ispunjena " }, "insecurePageWarning": { "message": "Upozorenje: Ovo je nezaštićena HTTP stranica i svi podaci koje preko nje pošalješ drugi mogu vidjeti i izmijeniti. Ova prijava je prvotno bila spremljena za sigurnu (HTTPS) stranicu." @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Pogrešan korisnički račun" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometrijsko otključavanje nije uspjelo. Biometrijski tajni ključ nije uspio otključati trezor. Pokušaj ponovo postaviti biometriju." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Neusklađenost biometrijskog ključa" + }, "biometricsNotEnabledTitle": { "message": "Biometrija nije omogućena" }, @@ -2328,7 +2375,7 @@ "message": "Tvoja glavna lozinka ne zadovoljava pravila ove organizacije. Za pristup trezoru moraš odmah ažurirati svoju glavnu lozinku. Ako nastaviš, odjaviti ćeš se iz trenutne sesije te ćeš se morati ponovno prijaviti. Aktivne sesije na drugim uređajima mogu ostati aktivne do jedan sat." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Tvoja je organizacija onemogućila šifriranje pouzdanog uređaja. Postavi glavnu lozinku za pristup svom trezoru." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatsko učlanjenje" @@ -3831,22 +3878,22 @@ "message": "Kôd za provjeru" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Postavke auto-ispune" }, "websiteUri": { - "message": "Website (URI)" + "message": "Web stranica (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Web stranica dodana" }, "addWebsite": { - "message": "Add website" + "message": "Dodaj web stranicu" }, "deleteWebsite": { - "message": "Delete website" + "message": "Izbriši web stranicu" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Zadano ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Prikaži otkrivanje podudaranja $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Sakrij otkrivanje podudaranja $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Auto-ispuna kod učitavanja?" }, "cardDetails": { "message": "Detalji kartice" @@ -3897,6 +3944,18 @@ "data": { "message": "Podaci" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Dodijeli" }, @@ -3937,7 +3996,7 @@ "message": "Koristi skrivena polja za osjetljive podatke poput lozinke" }, "checkBoxHelpText": { - "message": "Koristi potvrdne okvire ako ih želiš automatski uključiti u obrascu, npr. zapamti adresu e-pošte" + "message": "Koristi potvrdne okvire ako ih želiš auto-ispuniti u obrascu, npr. zapamti adresu e-pošte" }, "linkedHelpText": { "message": "Koristi povezano polje kada imaš problema s auto-ispunom za određenu web stranicu." @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Lokacija stavke" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden ima novi izgled!" }, @@ -4098,6 +4163,12 @@ "message": "Auto-ispuna i pretraga iz kartice Trezor je lakša i intuitivnija nego ikad prije. Razgledaj!" }, "accountActions": { - "message": "Account actions" + "message": "Radnje na računu" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index e17ad24130c..ecd5284d2ab 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Mappa szerkesztése" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Mappa törlése" }, @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Alap domain (ajánlott)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Egy vagy több szervezeti szabály érinti a generátor beállításokat." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Széf időkifutás művelet" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "A fiók nem egyezik." }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "A biometrikus adatok nincsenek beüzemelve." }, @@ -2328,7 +2375,7 @@ "message": "A mesterjelszó nem felel meg egy vagy több szervezeti szabályzatnak. A széf eléréséhez frissíteni kell a meszerjelszót. A továbblépés kijelentkeztet az aktuális munkamenetből és újra be kell jelentkezni. A többi eszközön lévő aktív munkamenetek akár egy óráig is aktívak maradhatnak." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "A szervezete letiltotta a megbízható eszközök titkosítását. Állítsunk be egy mesterjelszót a széf eléréséhez." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatikus regisztráció" @@ -3831,22 +3878,22 @@ "message": "Hitelesítő kulcs" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Automatikus kitöltés opciók" }, "websiteUri": { - "message": "Website (URI)" + "message": "Webhely (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "A webhely hozzáadásra került." }, "addWebsite": { - "message": "Add website" + "message": "Webhely hozzáadása" }, "deleteWebsite": { - "message": "Delete website" + "message": "Webhely törlése" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Alapértelmezés ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "$WEBSITE$ egyező érzékelés megjelenítése", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "$WEBSITE$ egyező érzékelés elrejtése", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Automatikus kitöltés oldalbetöltésnél?" }, "cardDetails": { "message": "Kártyaadatok" @@ -3897,6 +3944,18 @@ "data": { "message": "Adat" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Elem helyek" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4098,6 +4163,12 @@ "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" }, "accountActions": { - "message": "Account actions" + "message": "Fiókműveletek" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index b73ac29fa87..05dc4ed1a7b 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Sunting Folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Hapus Folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Satu atau lebih kebijakan organisasi mempengaruhi pengaturan pembuat sandi Anda." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Tindakan Batas Waktu Brankas" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Akun tidak cocok" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrik tidak diaktifkan" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index b4a0468cfaf..447cdcae573 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Modifica cartella" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Elimina cartella" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Una o più politiche dell'organizzazione stanno influenzando le impostazioni del tuo generatore." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Azione timeout cassaforte" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account non corrispondono" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Autenticazione biometrica non abilitata" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 6fe2f080a4a..3781e3afbd0 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "フォルダーを編集" }, + "newFolder": { + "message": "新しいフォルダー" + }, + "folderName": { + "message": "フォルダー名" + }, + "folderHintText": { + "message": "親フォルダーの名前の後に「/」を追加するとフォルダをネストします。例: ソーシャル/フォーラム" + }, + "noFoldersAdded": { + "message": "フォルダーが追加されていません" + }, + "createFoldersToOrganize": { + "message": "保管庫のアイテムを整理するフォルダーを作成します" + }, + "deleteFolderPermanently": { + "message": "このフォルダーを完全に削除しますか?" + }, "deleteFolder": { "message": "フォルダーを削除" }, @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "ベースドメイン (推奨)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "一つ以上の組織のポリシーがパスワード生成の設定に影響しています。" }, + "passwordGenerator": { + "message": "パスワード生成ツール" + }, + "usernameGenerator": { + "message": "ユーザー名生成ツール" + }, + "useThisPassword": { + "message": "このパスワードを使用する" + }, + "useThisUsername": { + "message": "このユーザー名を使用する" + }, + "securePasswordGenerated": { + "message": "安全なパスワードを生成しました! ウェブサイト上でパスワードを更新することを忘れないでください。" + }, + "useGeneratorHelpTextPartOne": { + "message": "生成機能", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "を使うと強力で一意なパスワードを作れます", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "保管庫タイムアウト時のアクション" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "アカウントが一致しません" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "生体認証のロック解除に失敗しました。生体認証キーでの保管庫のロック解除に失敗しました。生体認証を再度設定してください。" + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "生体認証キーが一致しません" + }, "biometricsNotEnabledTitle": { "message": "生体認証が有効になっていません" }, @@ -2328,7 +2375,7 @@ "message": "あなたのマスターパスワードは、組織のポリシーを満たしていません。保管庫にアクセスするには、今すぐマスターパスワードを更新する必要があります。この操作を続けると、現在のセッションがログアウトされ、再ログインする必要があります。他のデバイスでのアクティブなセッションは最大1時間継続する場合があります。" }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "あなたの組織は信頼できるデバイスの暗号化を無効化しました。保管庫にアクセスするにはマスターパスワードを設定してください。" }, "resetPasswordPolicyAutoEnroll": { "message": "自動登録" @@ -3831,22 +3878,22 @@ "message": "認証キー" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "自動入力オプション" }, "websiteUri": { - "message": "Website (URI)" + "message": "ウェブサイト (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "ウェブサイトを追加しました" }, "addWebsite": { - "message": "Add website" + "message": "ウェブサイトを追加" }, "deleteWebsite": { - "message": "Delete website" + "message": "ウェブサイトを削除" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "デフォルト ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "一致検出 $WEBSITE$を表示", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "一致検出 $WEBSITE$を非表示", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "ページ読み込み時に自動入力する" }, "cardDetails": { "message": "カード情報" @@ -3897,6 +3944,18 @@ "data": { "message": "データ" }, + "passkeys": { + "message": "パスキー", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "パスワード", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "パスキーでログイン", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "割り当て" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "アイテムの場所" }, + "fileSends": { + "message": "ファイル Send" + }, + "textSends": { + "message": "テキスト Send" + }, "bitwardenNewLook": { "message": "Bitwarden が新しい外観になりました。" }, @@ -4098,6 +4163,12 @@ "message": "保管庫タブからの自動入力と検索がこれまで以上に簡単で直感的になりました。" }, "accountActions": { - "message": "Account actions" + "message": "アカウントの操作" + }, + "showNumberOfAutofillSuggestions": { + "message": "拡張機能アイコンにログイン自動入力の候補の数を表示する" + }, + "systemDefault": { + "message": "システムのデフォルト" } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index e520ca8ea10..898742626b3 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "საქაღალდის რედაქტირება" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "საქაღალდის წაშლა" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 5b39467dbe0..4b4cec42bba 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 50a23b4de23..49c37c0cf5b 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "ಫೋಲ್ಡರ್ ಸಂಪಾದಿಸಿ" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "ಫೋಲ್ಡರ್ ಅಳಿಸಿ" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "ಒಂದು ಅಥವಾ ಹೆಚ್ಚಿನ ಸಂಸ್ಥೆ ನೀತಿಗಳು ನಿಮ್ಮ ಜನರೇಟರ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರುತ್ತವೆ" }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "ವಾಲ್ಟ್ ಸಮಯ ಮೀರುವ ಕ್ರಿಯೆ" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "ಖಾತೆ ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "ಬಯೊಮಿಟ್ರಿಕ್ಸ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿಲ್ಲ" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index ac48c83e2a1..bd94d590c2f 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -117,19 +117,19 @@ "message": "보안 코드 복사" }, "copyName": { - "message": "Copy name" + "message": "이름 복사" }, "copyCompany": { - "message": "Copy company" + "message": "회사 복사" }, "copySSN": { - "message": "Copy Social Security number" + "message": "주민등록번호 복사" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "여권 번호 복사" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "운전면허 번호 복사" }, "autoFill": { "message": "자동 완성" @@ -257,7 +257,7 @@ "message": "More from Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "bitwarden.com 으로 이동할까요?" }, "bitwardenForBusiness": { "message": "Bitwarden for Business" @@ -304,6 +304,24 @@ "editFolder": { "message": "폴더 편집" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "폴더 삭제" }, @@ -685,13 +703,13 @@ "message": "로그인 세션이 만료되었습니다." }, "logIn": { - "message": "Log in" + "message": "로그인" }, "restartRegistration": { "message": "Restart registration" }, "expiredLink": { - "message": "Expired link" + "message": "만료된 링크" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." @@ -761,7 +779,7 @@ "message": "새 URI" }, "addDomain": { - "message": "Add domain", + "message": "도메인 추가", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "하나 이상의 단체 정책이 생성기 규칙에 영항을 미치고 있습니다." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "보관함 시간 제한 초과시 동작" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "계정이 일치하지 않음" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "생체 인식이 활성화되지 않음" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 834658c4572..13d0652f47c 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Redaguoti aplankalą" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Šalinti aplankalą" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Viena ar daugiau organizacijos politikų turi įtakos Jūsų generatoriaus nustatymams." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault skirtojo laiko veiksmas" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Paskyros neatitikimas" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Trūksta biometrinių duomenų nustatymų" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 7712ff1d179..f954248d30e 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Labot mapi" }, + "newFolder": { + "message": "Jauna mape" + }, + "folderName": { + "message": "Mapes nosaukums" + }, + "folderHintText": { + "message": "Apakšmapes var izveidot, ja pievieno iekļaujošās mapes nosaukumu, aiz kura ir \"/\". Piemēram: Tīklošanās/Forumi" + }, + "noFoldersAdded": { + "message": "Nav pievienota neviena mape" + }, + "createFoldersToOrganize": { + "message": "Mapes ir izveidojamas, lai sakārtotu savas glabātavas vienumus" + }, + "deleteFolderPermanently": { + "message": "Vai tiešām neatgriezeniski izdzēst šo mapi?" + }, "deleteFolder": { "message": "Dzēst mapi" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Viens vai vairāki apvienības nosacījumi ietekmē veidotāja iestatījumus." }, + "passwordGenerator": { + "message": "Paroļu veidotājs" + }, + "usernameGenerator": { + "message": "Lietotājvārdu veidotājs" + }, + "useThisPassword": { + "message": "Izmantot šo paroli" + }, + "useThisUsername": { + "message": "Izmantot šo lietotājvārdu" + }, + "securePasswordGenerated": { + "message": "Droša parole izveidota. Neaizmirsti arī atjaunināt savu paroli tīmekļvietnē!" + }, + "useGeneratorHelpTextPartOne": { + "message": "Veidotājs ir izmantojams", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": ", lai izveidotu spēcīgu un vienreizēju paroli", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Glabātavas noildzes darbība" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Konta nesaderība" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometriskā atslēgšana neizdevās. Biometriskā slepenā atslēgai neizdevās atslēgt glabātavu. Lūgums vēlreiz mēģināt iestatīt biometriju." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometriskās atslēgas neatbilstība" + }, "biometricsNotEnabledTitle": { "message": "Biometrija nav iespējota" }, @@ -2328,7 +2375,7 @@ "message": "Galvenā parole neatbilst vienam vai vairākiem apvienības nosacījumiem. Ir jāatjaunina galvenā parole, lai varētu piekļūt glabātavai. Turpinot notiks atteikšanās no pašreizējās sesijas, un būs nepieciešams pieteikties no jauna. Citās ierīcēs esošās sesijas var turpināt darboties līdz vienai stundai." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Tava apvienība ir atspējojusi uzticamo ierīču šifrēšanu. Lūgums iestatīt galveno paroli, lai piekļūtu savai glabātavai." }, "resetPasswordPolicyAutoEnroll": { "message": "Automātiska ievietošana sarakstā" @@ -3897,6 +3944,18 @@ "data": { "message": "Dati" }, + "passkeys": { + "message": "Piekļuves atslēgas", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Paroles", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Pieteikties ar piekļuves atslēgu", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Vienuma atrašanās vieta" }, + "fileSends": { + "message": "Datņu Send" + }, + "textSends": { + "message": "Teksta Send" + }, "bitwardenNewLook": { "message": "Bitwarden ir jauns izskats." }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Konta darbības" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index c102e4a7b0d..db36de26433 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "ഫോൾഡർ തിരുത്തുക" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "ഫോൾഡർ ഇല്ലാതാക്കുക" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "ഒന്നോ അതിലധികമോ സംഘടന നയങ്ങൾ നിങ്ങളുടെ പാസ്സ്‌വേഡ് സൃഷ്ടാവിൻ്റെ ക്രമീകരണങ്ങളെ ബാധിക്കുന്നു" }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "വാൾട് ടൈം ഔട്ട് ആക്ഷൻ" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 32644cf9da5..f415cf6d332 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "फोल्डर संपादित करा" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "फोल्डर खोडून टाका" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 5b39467dbe0..4b4cec42bba 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 0e861ed319d..69e17a7d6d5 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Rediger mappen" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Slett mappen" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "En eller flere av virksomhetens regler påvirker generatorinnstillingene dine." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Handling ved tidsavbrudd i hvelvet" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Kontoen eksisterer ikke" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometri ikke aktivert" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 5b39467dbe0..4b4cec42bba 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index c50355f08aa..bff29a5e01e 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Map bewerken" }, + "newFolder": { + "message": "Nieuwe map" + }, + "folderName": { + "message": "Mapnaam" + }, + "folderHintText": { + "message": "Je kunt een map onderbrengen door het toevoegen van de naam van de bovenliggende map gevolgd door een \"/\". Voorbeeld: Social/Forums" + }, + "noFoldersAdded": { + "message": "Geen mappen toegevoegd" + }, + "createFoldersToOrganize": { + "message": "Maak mappen om je kluis items te organiseren" + }, + "deleteFolderPermanently": { + "message": "Weet je zeker dat je deze map definitief wilt verwijderen?" + }, "deleteFolder": { "message": "Map verwijderen" }, @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Basisdomein (aanbevolen)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Een of meer organisatiebeleidseisen heeft invloed op de instellingen van je generator." }, + "passwordGenerator": { + "message": "Wachtwoordgenerator" + }, + "usernameGenerator": { + "message": "Gebruikersnaamgenerator" + }, + "useThisPassword": { + "message": "Dit wachtwoord gebruiken" + }, + "useThisUsername": { + "message": "Deze gebruikersnaam gebruiken" + }, + "securePasswordGenerated": { + "message": "Veilig wachtwoord aangemaakt! Vergeet niet om je wachtwoord ook op de website bij te werken." + }, + "useGeneratorHelpTextPartOne": { + "message": "Gebruik de generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "om een sterk uniek wachtwoord te maken", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Actie bij time-out" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Accounts komt niet overeen" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometrische ontgrendelen mislukt. De biometrische geheime sleutel kon de kluis niet ontgrendelen. Probeer biometrische gegevens opnieuw in te stellen." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometrische sleutel discrepantie" + }, "biometricsNotEnabledTitle": { "message": "Biometrie niet ingeschakeld" }, @@ -2328,7 +2375,7 @@ "message": "Je hoofdwachtwoord voldoet niet aan en of meerdere oganisatiebeleidsonderdelen. Om toegang te krijgen tot de kluis, moet je je hoofdwachtwoord nu bijwerken. Doorgaan zal je huidige sessie uitloggen, waarna je opnieuw moet inloggen. Actieve sessies op andere apparaten blijven mogelijk nog een uur actief." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Je organisatie heeft het versleutelen van vertrouwde apparaten uitgeschakeld. Stel een hoofdwachtwoord in om toegang te krijgen tot je kluis." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatische inschrijving" @@ -3673,7 +3720,7 @@ "message": "Notifications" }, "appearance": { - "message": "Voorkomen" + "message": "Uiterlijk" }, "errorAssigningTargetCollection": { "message": "Fout bij toewijzen doelverzameling." @@ -3897,6 +3944,18 @@ "data": { "message": "Gegevens" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Wachtwoorden", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Inloggen met passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Toewijzen" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Itemlocatie" }, + "fileSends": { + "message": "Bestand-Sends" + }, + "textSends": { + "message": "Tekst-Sends" + }, "bitwardenNewLook": { "message": "Bitwarden heeft een nieuw uiterlijk!" }, @@ -4098,6 +4163,12 @@ "message": "Automatisch invullen en zoeken is makkelijker en intuïtiever dan ooit vanaf het tabblad Kluis. Kijk rond!" }, "accountActions": { - "message": "Account actions" + "message": "Accountacties" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 5b39467dbe0..4b4cec42bba 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 5b39467dbe0..4b4cec42bba 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 6d8055ff5f6..8ee343b65d1 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edytuj folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Usuń folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Co najmniej jedna zasada organizacji wpływa na ustawienia generatora." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Sposób blokowania sejfu" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Konto jest niezgodne" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Odblokowanie biometryczne nie powiodło się. Sekretny klucz biometryczny nie odblokował sejfu. Spróbuj skonfigurować biometrię ponownie." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Klucz biometryczny jest niepoprawny" + }, "biometricsNotEnabledTitle": { "message": "Dane biometryczne są wyłączone" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Dane" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Przypisz" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Lokalizacja elementu" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden ma nowy wygląd!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index bbee456fe54..69ed6479536 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -117,19 +117,19 @@ "message": "Copiar Código de Segurança" }, "copyName": { - "message": "Copy name" + "message": "Copiar nome" }, "copyCompany": { - "message": "Copy company" + "message": "Copiar empresa" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Cadastro de Pessoas Físicas" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Copiar número do passaporte" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Copiar número da CNH" }, "autoFill": { "message": "Autopreencher" @@ -304,6 +304,24 @@ "editFolder": { "message": "Editar Pasta" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Excluir Pasta" }, @@ -814,7 +832,7 @@ "message": "Pedir para adicionar um item se um não for encontrado no seu cofre. Aplica-se a todas as contas logadas." }, "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "message": "Mostrar cartões como sugestões de preenchimento automático na exibição do Cofre" }, "showCardsCurrentTab": { "message": "Mostrar cartões em páginas com guias." @@ -823,7 +841,7 @@ "message": "Exibir itens de cartão em páginas com abas para simplificar o preenchimento automático" }, "showIdentitiesInVaultView": { - "message": "Show identifies as Autofill suggestions on Vault view" + "message": "Mostrar identifica como sugestões de preenchimento automático na exibição do Cofre" }, "showIdentitiesCurrentTab": { "message": "Exibir Identidades na Aba Atual" @@ -1258,16 +1276,16 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Sugestões de preenchimento automático" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "Mostrar sugestões de preenchimento automático nos campos de formulários" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "Exibir sugestões quando o ícone for selecionado" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "Aplica-se a todas as contas conectadas." }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "Desative as configurações do gerenciador de senhas do seu navegador para evitar conflitos." @@ -1288,7 +1306,7 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "Preenchimento automático ao carregar a página" }, "enableAutoFillOnPageLoad": { "message": "Ativar o Autopreenchimento ao Carregar a Página" @@ -1297,7 +1315,7 @@ "message": "Se um formulário de login for detectado, realizar automaticamente um auto-preenchimento quando a página web carregar." }, "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", + "message": "$OPENTAG$Aviso:$CLOSETAG$ Comprometido ou sites não confiáveis podem explorar o autopreenchimento ao carregar a página.", "placeholders": { "openTag": { "content": "$1", @@ -1313,7 +1331,7 @@ "message": "Sites comprometidos ou não confiáveis podem tomar vantagem do autopreenchimento ao carregar a página." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "Saiba mais sobre riscos" }, "learnMoreAboutAutofill": { "message": "Saiba mais sobre preenchimento automático" @@ -1343,13 +1361,13 @@ "message": "Abrir cofre na barra lateral" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "Preencher automaticamente o último login utilizado para o site atual" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "Preenchimento automático do último cartão utilizado para o site atual" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "Autopreencher a última identidade usada para o site atual" }, "commandGeneratePasswordDesc": { "message": "Gerar e copiar uma nova senha aleatória para a área de transferência." @@ -1630,7 +1648,7 @@ "message": "Credenciais" }, "secureNotes": { - "message": "Notas Seguras" + "message": "Notas seguras" }, "clear": { "message": "Limpar", @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Domínio de base (recomendado)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1678,7 +1696,7 @@ "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "Detecção de Correspondência", + "message": "Detecção de correspondência", "description": "URI match detection for autofill." }, "defaultMatchDetection": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Uma ou mais políticas da organização estão afetando as suas configurações do gerador." }, + "passwordGenerator": { + "message": "Gerador de Senha" + }, + "usernameGenerator": { + "message": "Gerador de usuário" + }, + "useThisPassword": { + "message": "Use esta senha" + }, + "useThisUsername": { + "message": "Use este nome de usuário" + }, + "securePasswordGenerated": { + "message": "Senha segura gerada! Não se esqueça de atualizar também sua senha no site." + }, + "useGeneratorHelpTextPartOne": { + "message": "Usar 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": { + "message": "para criar uma senha única e forte", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Ação de Tempo Limite do Cofre" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "A conta não confere" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "O desbloqueio biométrico falhou. A chave secreta biométrica não conseguiu desbloquear o cofre. Tente configurar os dados biométricos novamente." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Falta de chave biométrica" + }, "biometricsNotEnabledTitle": { "message": "Biometria não ativada" }, @@ -2115,7 +2162,7 @@ "message": "Protegido por senha" }, "copyLink": { - "message": "Copy link" + "message": "Copiar link" }, "copySendLink": { "message": "Copiar link do Send", @@ -2328,7 +2375,7 @@ "message": "A sua senha mestra não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha mestra agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Sua organização desativou a criptografia confiável do dispositivo. Por favor, defina uma senha mestra para acessar o seu cofre." }, "resetPasswordPolicyAutoEnroll": { "message": "Inscrição Automática" @@ -2784,19 +2831,19 @@ "message": "Autofill shortcut" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "Alterar atalho" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "Gerenciar atalhos" }, "autofillShortcut": { "message": "Atalho para autopreenchimento" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "O atalho de acesso ao preenchimento automático não está definido. Altere isso nas configurações do navegador." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "O atalho de login de preenchimento automático é $COMMAND$. Gerencie todos os atalhos nas configurações do navegador.", "placeholders": { "command": { "content": "$1", @@ -2980,10 +3027,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 campo precisa de sua atenção." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "Campos $COUNT$ precisam de sua atenção.", "placeholders": { "count": { "content": "$1", @@ -3092,7 +3139,7 @@ "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "Desbloqueie sua conta para ver as sugestões de preenchimento automático", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { @@ -3100,7 +3147,7 @@ "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "Desbloqueie sua conta, abra em uma nova janela", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "fillCredentialsFor": { @@ -3124,27 +3171,27 @@ "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { - "message": "New login", + "message": "Novo login", "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "Adicionar novo item de login no cofre, abre em uma nova janela", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Novo cartão", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "Adicione um novo item do cartão do cofre, abre em uma nova janela", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "Nova identidade", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Add new vault identity item, opens in a new window", + "message": "Adicionar novo item de identidade do cofre, abre em uma nova janela", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { @@ -3264,7 +3311,7 @@ "message": "Senha do arquivo inválida, por favor informe a senha utilizada quando criou o arquivo de exportação." }, "destination": { - "message": "Destination" + "message": "Destino" }, "learnAboutImportOptions": { "message": "Saiba mais sobre suas opções de importação" @@ -3505,27 +3552,27 @@ "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "Continuar nas configurações do navegador?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "Continuar para o Centro de Ajuda?", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "Alterar as configurações de autopreenchimento e gerenciamento de senhas do seu navegador.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "Você pode ver e definir 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": { - "message": "Change your browser's autofill and password management settings.", + "message": "Alterar as configurações de autopreenchimento e gerenciamento de senhas do seu navegador.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "Você pode ver e definir 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": { @@ -3821,7 +3868,7 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "o número do cartão termina com", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { @@ -3831,22 +3878,22 @@ "message": "Chave do autenticador" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Opções de autopreenchimento" }, "websiteUri": { - "message": "Website (URI)" + "message": "Site (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Site adicionado" }, "addWebsite": { - "message": "Add website" + "message": "Adicionar site" }, "deleteWebsite": { - "message": "Delete website" + "message": "Excluir site" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Padrão ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Exibir detecção de correspondência $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Ocultar detecção de correspondência $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Preenchimento automático ao carregar a página?" }, "cardDetails": { "message": "Detalhes do cartão" @@ -3895,16 +3942,28 @@ "message": "Carregando" }, "data": { - "message": "Data" + "message": "Dado" + }, + "passkeys": { + "message": "Senhas", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Senhas", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Iniciar sessão com a chave de acesso", + "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { "message": "Atribuir" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "Apenas membros da organização com acesso a essas coleções poderão ver o item." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "Apenas membros da organização com acesso à essas coleções 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.", @@ -3943,13 +4002,13 @@ "message": "Use um campo vinculado quando estiver enfrentando problemas com o auto-preenchimento com um site específico." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "Digite o Id html do campo, nome, nome aria-label, ou espaço reservado." }, "editField": { - "message": "Edit field" + "message": "Editar campo" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "Editar $LABEL$", "placeholders": { "label": { "content": "$1", @@ -3958,7 +4017,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "Excluir $LABEL$", "placeholders": { "label": { "content": "$1", @@ -3967,7 +4026,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ adicionado", "placeholders": { "label": { "content": "$1", @@ -3976,7 +4035,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "Reordene $LABEL$. Use a tecla de seta para mover o item para cima ou para baixo.", "placeholders": { "label": { "content": "$1", @@ -3985,7 +4044,7 @@ } }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ se moveu para cima, posição $INDEX$ de $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4002,13 +4061,13 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "Selecione as coleções para atribuir" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "1 item será transferido permanentemente para a organização selecionada. Você não irá mais possuir este item." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "Itens $PERSONAL_ITEMS_COUNT$ serão transferidos permanentemente para a organização selecionada. Você não irá mais possuir esses itens.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4017,7 +4076,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "1 item será transferido permanentemente para $ORG$. Você não irá mais possuir este item.", "placeholders": { "org": { "content": "$1", @@ -4026,7 +4085,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "Os itens $PERSONAL_ITEMS_COUNT$ serão transferidos permanentemente para $ORG$. Você não irá mais possuir esses itens.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4039,13 +4098,13 @@ } }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "Coleções atribuídas com sucesso" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "Você selecionou nada." }, "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", + "message": "Itens selecionados movidos para $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4054,7 +4113,7 @@ } }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "Itens movidos para $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4063,7 +4122,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "Item movido para $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4072,7 +4131,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ se moveu para baixo, posição $INDEX$ de $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4091,13 +4150,25 @@ "itemLocation": { "message": "Localização do Item" }, + "fileSends": { + "message": "Arquivos enviados" + }, + "textSends": { + "message": "Texto enviado" + }, "bitwardenNewLook": { - "message": "Bitwarden has a new look!" + "message": "Bitwarden tem uma nova aparência!" }, "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" + "message": "É mais fácil e mais intuitivo do que nunca autopreenchimento e pesquise na guia Cofre. Dê uma olhada ao redor!" }, "accountActions": { - "message": "Account actions" + "message": "Ações da conta" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 6bdeb8160da..fc4ca1c3a5c 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Editar pasta" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Eliminar pasta" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Uma ou mais políticas da organização estão a afetar as suas definições do gerador." }, + "passwordGenerator": { + "message": "Gerador de palavras-passe" + }, + "usernameGenerator": { + "message": "Gerador de nomes de utilizador" + }, + "useThisPassword": { + "message": "Utilizar esta palavra-passe" + }, + "useThisUsername": { + "message": "Utilizar este nome de utilizador" + }, + "securePasswordGenerated": { + "message": "Palavra-passe segura gerada! Não se esqueça de atualizar também a sua palavra-passe no site." + }, + "useGeneratorHelpTextPartOne": { + "message": "Utilize 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": { + "message": "para criar uma palavra-passe forte e única", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Ação de tempo limite do cofre" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Incompatibilidade de contas" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "O desbloqueio biométrico falhou. A chave secreta biométrica não conseguiu desbloquear o cofre. Por favor, tente configurar a biometria novamente." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Incompatibilidade da chave biométrica" + }, "biometricsNotEnabledTitle": { "message": "Biometria não configurada" }, @@ -2328,7 +2375,7 @@ "message": "A sua palavra-passe mestra não cumpre uma ou mais políticas da sua organização. Para aceder ao cofre, tem de atualizar a sua palavra-passe mestra agora. Ao prosseguir, terminará a sua sessão atual e terá de iniciar sessão novamente. As sessões ativas noutros dispositivos poderão continuar ativas até uma hora." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "A sua organização desativou a encriptação de dispositivos fiáveis. Por favor, defina uma palavra-passe mestra para aceder ao seu cofre." }, "resetPasswordPolicyAutoEnroll": { "message": "Inscrição automática" @@ -3897,6 +3944,18 @@ "data": { "message": "Dados" }, + "passkeys": { + "message": "Chaves de acesso", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Palavras-passe", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Iniciar sessão com a chave de acesso", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Atribuir" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Localização do item" }, + "fileSends": { + "message": "Sends de ficheiros" + }, + "textSends": { + "message": "Sends de texto" + }, "bitwardenNewLook": { "message": "O Bitwarden tem um novo visual!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Ações da conta" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index b9a1d78fe50..c0a343477b5 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -3,27 +3,27 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden - Manager Gratuit de Parole", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "Acasă, la serviciu sau în deplasare, Bitwarden vă protejează toate parolele și informațiile sensibile", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Autentificați-vă sau creați un cont nou pentru a accesa seiful dvs. securizat." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Invitație acceptată" }, "createAccount": { "message": "Creare cont" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Setați o parolă puternică" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Finalizați crearea contului prin setarea unei parole" }, "login": { "message": "Conectare" @@ -53,7 +53,7 @@ "message": "Un indiciu pentru parola principală vă poate ajuta să v-o reamintiți dacă o uitați." }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "Dacă vă uitați parola, indiciul parolei poate fi trimis la adresa dvs. de e-mail. $CURRENT$/$MAXIMUM$ de caractere maxim.", "placeholders": { "current": { "content": "$1", @@ -72,10 +72,10 @@ "message": "Indiciu pentru parola principală (opțional)" }, "joinOrganization": { - "message": "Join organization" + "message": "Alăturați-vă organizației" }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "Finalizați aderarea la această organizație prin setarea unei parole principale." }, "tab": { "message": "Filă" @@ -117,31 +117,31 @@ "message": "Copiere cod de securitate" }, "copyName": { - "message": "Copy name" + "message": "Copiați numele" }, "copyCompany": { - "message": "Copy company" + "message": "Copiați firma" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Copiați numărul de securitate socială" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Copiați numărul pașaportului" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Copiați numărul de licență" }, "autoFill": { "message": "Auto-completare" }, "autoFillLogin": { - "message": "Autofill login" + "message": "Autocompletare date de autentificare" }, "autoFillCard": { - "message": "Autofill card" + "message": "Autocompletare card" }, "autoFillIdentity": { - "message": "Autofill identity" + "message": "Autocompletare identitate" }, "generatePasswordCopied": { "message": "Generare parolă (s-a copiat)" @@ -153,19 +153,19 @@ "message": "Nu există potrivire de autentificări" }, "noCards": { - "message": "No cards" + "message": "Niciun card" }, "noIdentities": { - "message": "No identities" + "message": "Nicio identitate" }, "addLoginMenu": { - "message": "Add login" + "message": "Adăugare date de autentificare" }, "addCardMenu": { - "message": "Add card" + "message": "Adăugare card" }, "addIdentityMenu": { - "message": "Add identity" + "message": "Adăugare identitate" }, "unlockVaultMenu": { "message": "Deblocați-vă seiful" @@ -213,25 +213,25 @@ "message": "Schimbare parolă principală" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Continuați către aplicația web?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Explorați mai multe caracteristici ale contului Bitwarden în aplicația web." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Continuați la Centrul de Ajutor?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Aflați mai multe despre cum să utilizați Bitwarden în Centrul de Ajutor." }, "continueToBrowserExtensionStore": { - "message": "Continue to browser extension store?" + "message": "Continuați la magazinul de extensii al browser-ului?" }, "continueToBrowserExtensionStoreDesc": { - "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + "message": "Ajutați-i pe alții să afle dacă Bitwarden este potrivit pentru ei. Vizitați magazinul de extensii al browser-ului dvs. și lăsați o evaluare acum." }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Puteți schimba parola principală în aplicația web Bitwarden." }, "fingerprintPhrase": { "message": "Fraza amprentă", @@ -248,43 +248,43 @@ "message": "Deconectare" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Despre Bitwarden" }, "about": { "message": "Despre" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Mai multe de la Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Continuați la bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden pentru Business" }, "bitwardenAuthenticator": { - "message": "Bitwarden Authenticator" + "message": "Autentificator Bitwarden" }, "continueToAuthenticatorPageDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website" + "message": "Autentificatorul Bitwarden vă permite să stocați chei de autentificare și să generați coduri TOTP pentru fluxurile de verificare în doi pași. Aflați mai multe de pe site-ul bitwarden.com" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Manager de secrete Bitwarden" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Stocați, gestionați și partajați în siguranță secretele dezvoltatorilor cu Bitwarden Secrets Manager. Aflați mai multe pe site-ul bitwarden.com." }, "passwordlessDotDev": { "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." + "message": "Creați experiențe de conectare fluide și sigure, fără parole tradiționale, cu Passwordless.dev. Aflați mai multe pe site-ul bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Bitwarden Families gratuit" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Sunteți eligibil pentru Bitwarden Families gratuit. Răscumpărați această ofertă astăzi în aplicația web." }, "version": { "message": "Versiune" @@ -304,6 +304,24 @@ "editFolder": { "message": "Editare dosar" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Ștergere dosar" }, @@ -345,7 +363,7 @@ "message": "Generează automat parole unice și puternice pentru autentificările dvs." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Aplicația web Bitwarden" }, "importItems": { "message": "Import de articole" @@ -366,7 +384,7 @@ "message": "Lungime" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "Lungimea minimă a parolei" }, "uppercase": { "message": "Litere mari (A-Z)" @@ -424,7 +442,7 @@ "message": "Parolă" }, "totp": { - "message": "Authenticator secret" + "message": "Cheie de autentificare" }, "passphrase": { "message": "Frază de acces" @@ -433,13 +451,13 @@ "message": "Favorit" }, "unfavorite": { - "message": "Unfavorite" + "message": "Elimină din favorite" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Item adăugat în favorite" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Item eliminat din favorite" }, "notes": { "message": "Note" @@ -463,7 +481,7 @@ "message": "Lansare" }, "launchWebsite": { - "message": "Launch website" + "message": "Lansați siteul web" }, "website": { "message": "Sait web" @@ -478,19 +496,19 @@ "message": "Altele" }, "unlockMethods": { - "message": "Unlock options" + "message": "Deblocați opțiunile" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Configurați metoda de deblocare care să schimbe acțiunea de expirare a seifului." }, "unlockMethodNeeded": { - "message": "Set up an unlock method in Settings" + "message": "Setați o metodă de deblocare in setări" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Expirarea sesiunii" }, "otherOptions": { - "message": "Other options" + "message": "Alte opțiuni" }, "rateExtension": { "message": "Evaluare extensie" @@ -533,7 +551,7 @@ "message": "Blocare imediată" }, "lockAll": { - "message": "Lock all" + "message": "Blochează toate" }, "immediately": { "message": "Imediat" @@ -581,16 +599,16 @@ "message": "Securitate" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Confirmați parola principală" }, "masterPassword": { - "message": "Master password" + "message": "Parola principală" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Parola principală nu poate fi recuperată dacă este uitată!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Indiciu pentru parola principală" }, "errorOccurred": { "message": "S-a produs o eroare" @@ -624,10 +642,10 @@ "message": "Noul dvs. cont a fost creat! Acum vă puteți autentifica." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "V-ați conectat cu succes" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Puteți închide această fereastră" }, "masterPassSent": { "message": "V-am trimis un e-mail cu indiciul parolei principale." @@ -636,7 +654,7 @@ "message": "Este necesar codul de verificare." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "Autentificarea a fost anulată sau a luat prea mult. Încercați din nou." }, "invalidVerificationCode": { "message": "Cod de verificare nevalid" @@ -655,49 +673,49 @@ "message": "Nu se pot auto-completa datele de conectare pentru această pagină. În schimb, puteți copia și lipi aceste date." }, "totpCaptureError": { - "message": "Unable to scan QR code from the current webpage" + "message": "Nu se poate scana codul QR din pagina web curentă" }, "totpCaptureSuccess": { - "message": "Authenticator key added" + "message": "Cheie autentificare adăugată" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "Scanează codul QR pentru autentificator din pagina web curentă" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Faceți autentificarea in 2 pași mai ușoară" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden poate stoca și completa coduri de verificare în doi pași. Copiați și lipiți cheia în acest câmp." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden poate stoca și completa coduri de verificare în doi pași. Selectați pictograma camerei foto pentru a face o captură de ecran a codului QR de autentificare al acestui site, sau copiați și lipiți cheia în acest câmp." }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Copiați cheia de autentificare (TOTP)" }, "loggedOut": { "message": "Deconectat" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Ați fost deconectat din contul dvs." }, "loginExpired": { "message": "Sesiunea de autentificare a expirat." }, "logIn": { - "message": "Log in" + "message": "Autentificare" }, "restartRegistration": { - "message": "Restart registration" + "message": "Reporniți înregistrarea" }, "expiredLink": { - "message": "Expired link" + "message": "Link expirat" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Vă rugăm să reporniți înregistrarea sau să încercați să vă conectați." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Este posibil să aveți deja un cont" }, "logOutConfirmation": { "message": "Sigur doriți să vă deconectați?" @@ -761,7 +779,7 @@ "message": "URI nou" }, "addDomain": { - "message": "Add domain", + "message": "Adăugați un domeniu", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { @@ -805,7 +823,7 @@ "message": "Solicitare de adăugare cont" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "Salvare în opțiuni seif" }, "addLoginNotificationDesc": { "message": "Solicitați adăugarea unui element dacă nu se găsește unul în seif." @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Una sau mai multe politici organizaționale vă afectează setările generatorului." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Acțiune la expirarea seifului" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Eroare de cont" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Datele biometrice nu sunt configurate" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 3dd4b5c4aa9..0890273f1ca 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Изменить папку" }, + "newFolder": { + "message": "Новый папка" + }, + "folderName": { + "message": "Название папки" + }, + "folderHintText": { + "message": "Создайте вложенную папку, добавив название родительской папки и символ \"/\". Пример: Сообщества/Форумы" + }, + "noFoldersAdded": { + "message": "Нет добавленных папок" + }, + "createFoldersToOrganize": { + "message": "Создавайте папки для упорядочивания элементов хранилища" + }, + "deleteFolderPermanently": { + "message": "Вы действительно хотите безвозвратно удалить эту папку?" + }, "deleteFolder": { "message": "Удалить папку" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "На настройки генератора влияют одна или несколько политик организации." }, + "passwordGenerator": { + "message": "Генератор паролей" + }, + "usernameGenerator": { + "message": "Генератор имени пользователя" + }, + "useThisPassword": { + "message": "Использовать этот пароль" + }, + "useThisUsername": { + "message": "Использовать это имя пользователя" + }, + "securePasswordGenerated": { + "message": "Безопасный пароль сгенерирован! Не забудьте также обновить свой пароль на сайте." + }, + "useGeneratorHelpTextPartOne": { + "message": "Использовать генератор", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "для создания надежного уникального пароля", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Действие по тайм-ауту хранилища" }, @@ -1848,7 +1889,7 @@ "message": "Заполнить и сохранить" }, "autoFillSuccessAndSavedUri": { - "message": "URI элемента заполнен и сохранен" + "message": "Элемент заполнен, URI сохранен" }, "autoFillSuccess": { "message": "Элемент заполнен " @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Несоответствие аккаунта" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Биометрическая разблокировка не удалась. Биометрический секретный ключ не смог разблокировать хранилище. Пожалуйста, попробуйте настроить биометрию еще раз." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Несовпадение биометрического ключа" + }, "biometricsNotEnabledTitle": { "message": "Биометрия не настроена" }, @@ -2328,7 +2375,7 @@ "message": "Ваш мастер-пароль не соответствует требованиям политики вашей организации. Для доступа к хранилищу вы должны обновить свой мастер-пароль прямо сейчас. При этом текущий сеанс будет завершен и потребуется повторная авторизация. Сеансы на других устройствах могут оставаться активными в течение часа." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "В вашей организации отключено шифрование доверенных устройств. Пожалуйста, установите мастер-пароль для доступа к вашему хранилищу." }, "resetPasswordPolicyAutoEnroll": { "message": "Автоматическое развертывание" @@ -3897,6 +3944,18 @@ "data": { "message": "Данные" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Пароли", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Войти с passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Назначить" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Расположение элемента" }, + "fileSends": { + "message": "Файловая Send" + }, + "textSends": { + "message": "Текстовая Send" + }, "bitwardenNewLook": { "message": "У Bitwarden новый облик!" }, @@ -4098,6 +4163,12 @@ "message": "Теперь автозаполнение и поиск на вкладке Хранилище стали проще и интуитивно понятнее, чем когда-либо. Осмотритесь!" }, "accountActions": { - "message": "Account actions" + "message": "Действия с аккаунтом" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index d7495836ec4..fecfdfe8240 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "බහාලුම සංස්කරණය" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "ෆෝල්ඩරය මකන්න" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "සංවිධාන ප්රතිපත්ති එකක් හෝ වැඩි ගණනක් ඔබේ උත්පාදක සැකසුම් වලට බලපායි." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "සුරක්ෂිතාගාරය කාලය ක්රියාකාරී" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "ගිණුම මිස්ගැලච්" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "ජීව විද්යාව සක්රීය කර නැත" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 6acf4e0e442..21e48ce3ded 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Upraviť priečinok" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Odstrániť priečinok" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Jedno alebo viac nastavení organizácie ovplyvňujú vaše nastavenia generátora." }, + "passwordGenerator": { + "message": "Generátor hesla" + }, + "usernameGenerator": { + "message": "Generátor používateľského mena" + }, + "useThisPassword": { + "message": "Použiť toto heslo" + }, + "useThisUsername": { + "message": "Použit toto používateľské meno" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Akcia pri vypršaní času pre trezor" }, @@ -1983,7 +2024,7 @@ "message": "Aplikácia Bitwarden Desktop musí byť pred použitím odomknutia pomocou biometrických údajov spustená." }, "errorEnableBiometricTitle": { - "message": "Nie je môžné povoliť biometriu" + "message": "Nie je môžné povoliť biometrické údaje" }, "errorEnableBiometricDesc": { "message": "Akcia bola zrušená desktopovou aplikáciou" @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Nezhoda účtu" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Odomknutie biometrickými údajmi zlyhalo. Tajný kľúč biometrických údajov nedokázal odomknúť trezor. Skúste biometrické údaje nastaviť znova." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Kľúč biometrických údajov sa nezhoduje" + }, "biometricsNotEnabledTitle": { "message": "Biometria nie je povolená" }, @@ -2328,7 +2375,7 @@ "message": "Vaše hlavné heslo nespĺňa jednu alebo viacero podmienok vašej organizácie. Ak chcete získať prístup k trezoru, musíte teraz aktualizovať svoje hlavné heslo. Pokračovaním sa odhlásite z aktuálnej relácie a budete sa musieť znova prihlásiť. Aktívne relácie na iných zariadeniach môžu zostať aktívne až jednu hodinu." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Vaša organizácia zakázala šifrovanie dôveryhodného zariadenia. Na prístup k trezoru nastavte hlavné heslo." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatická registrácia" @@ -3831,22 +3878,22 @@ "message": "Kľúč overovacej aplikácie" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Možnosti automatického vypĺňania" }, "websiteUri": { - "message": "Website (URI)" + "message": "Webová stránka (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Webová stránka pridaná" }, "addWebsite": { - "message": "Add website" + "message": "Pridať webovú stránku" }, "deleteWebsite": { - "message": "Delete website" + "message": "Odstrániť webovú stránku" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Predvolené ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Zobraziť spôsob mapovania $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Skryť spôsob mapovania $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Automaticky vyplniť pri načítaní stránky?" }, "cardDetails": { "message": "Podrobnosti o karte" @@ -3897,6 +3944,18 @@ "data": { "message": "Údaje" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Priradiť" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Umiestnenie položky" }, + "fileSends": { + "message": "Sendy so súborom" + }, + "textSends": { + "message": "Textové Sendy" + }, "bitwardenNewLook": { "message": "Bitwarden má nový vzhľad!" }, @@ -4098,6 +4163,12 @@ "message": "Automatické vypĺňanie a vyhľadávanie na karte Trezor je jednoduchšie a intuitívnejšie ako kedykoľvek predtým. Poobzerajte sa!" }, "accountActions": { - "message": "Account actions" + "message": "Operácie s účtom" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 0e2ae6a0bcd..540b0a3239a 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Uredi mapo" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Izbriši mapo" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Dejanje ob poteku roka" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 91029cf9c17..7e0f6fba670 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Уреди фасциклу" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Избриши фасциклу" }, @@ -1343,13 +1361,13 @@ "message": "Отвори сеф у бочну траку" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "Аутоматско попуњавање последњу коришћену пријаву за тренутну веб страницу" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "Аутоматско попуњавање последњу коришћену картицу за тренутну веб страницу" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "Аутоматско попуњавање последњи коришћен идентитет за тренутну веб страницу" }, "commandGeneratePasswordDesc": { "message": "Генеришите и копирајте нову случајну лозинку у привремену меморију" @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Основни домен (препоручено)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Једна или више смерница организације утичу на поставке вашег генератора." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Акција на тајмаут сефа" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Неподударање налога" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Биометрија није омогућена" }, @@ -2328,7 +2375,7 @@ "message": "Ваша главна лозинка не испуњава једну или више смерница ваше организације. Да бисте приступили сефу, морате одмах да ажурирате главну лозинку. Ако наставите, одјавићете се са ваше тренутне сесије, што захтева да се поново пријавите. Активне сесије на другим уређајима могу да остану активне до један сат." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Ваша организација је онемогућила шифровање поузданог уређаја. Поставите главну лозинку за приступ вашем трезору." }, "resetPasswordPolicyAutoEnroll": { "message": "Ауто пријављивање" @@ -2787,16 +2834,16 @@ "message": "Промени пречицу" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "Управљање пречицама" }, "autofillShortcut": { "message": "Пречице Ауто-пуњења" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "Пречица за ауто-попуњавање није подешена. Промените ово у подешавањима претраживача." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "Пречица за пријављивање за аутоматско попуњавање је $COMMAND$. Управљајте свим пречицама у подешавањима претраживача.", "placeholders": { "command": { "content": "$1", @@ -3831,22 +3878,22 @@ "message": "Кључ аутентификатора" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Опције Ауто-пуњења" }, "websiteUri": { - "message": "Website (URI)" + "message": "Вебсајт (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "Вебсајт додат" }, "addWebsite": { - "message": "Add website" + "message": "Додај вебсајт" }, "deleteWebsite": { - "message": "Delete website" + "message": "Обриши вебсајт" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Подразумевано ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Прикажи откривање подударања $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Сакриј откривање подударања $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Ауто-попуњавање при учитавању странице?" }, "cardDetails": { "message": "Детаљи картице" @@ -3897,6 +3944,18 @@ "data": { "message": "Подаци" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Додели" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Смештај ставке" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden има нови изглед!" }, @@ -4098,6 +4163,12 @@ "message": "Лакше је и интуитивније него икада да се аутоматски попуњава и тражи са картице Сефа. Проверите!" }, "accountActions": { - "message": "Account actions" + "message": "Акције везане за налог" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 41fa8fc130c..5b62f6d45fe 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Redigera mapp" }, + "newFolder": { + "message": "Ny mapp" + }, + "folderName": { + "message": "Mappnamn" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Skapa mappar för att organisera dina valvobjekt" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Radera mapp" }, @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Basdomän (rekommenderas)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "En eller flera organisationspolicyer påverkar dina generatorinställningar." }, + "passwordGenerator": { + "message": "Lösenordsgenerator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Åtgärd när valvets tidsgräns överskrids" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Kontoavvikelse" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometri är inte aktiverat" }, @@ -2787,7 +2834,7 @@ "message": "Ändra genväg" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "Hantera genvägar" }, "autofillShortcut": { "message": "Tangentbordsgenväg för automatisk ifyllnad" @@ -3834,19 +3881,19 @@ "message": "Auto-fill options" }, "websiteUri": { - "message": "Website (URI)" + "message": "Webbplats (URI)" }, "websiteAdded": { "message": "Website added" }, "addWebsite": { - "message": "Add website" + "message": "Lägg till webbplats" }, "deleteWebsite": { - "message": "Delete website" + "message": "Radera webbplats" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Standard ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Lösenord", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Tilldela" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden har fått ett nytt utseende!" }, @@ -4098,6 +4163,12 @@ "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" }, "accountActions": { - "message": "Account actions" + "message": "Kontoåtgärder" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 5b39467dbe0..4b4cec42bba 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Edit folder" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Delete folder" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index d4a834a253c..9bff3c0e79d 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "แก้ไขโฟลเดอร์" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "ลบโฟลเดอร์" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "นโยบายองค์กรอย่างน้อยหนึ่งนโยบายส่งผลต่อการตั้งค่าตัวสร้างของคุณ" }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "การดำเนินการหลังหมดเวลาล็อคตู้เซฟ" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Account missmatch" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Biometrics not set up" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 0bd43e67b5e..69acc9ba9b9 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Klasörü düzenle" }, + "newFolder": { + "message": "Yeni klasör" + }, + "folderName": { + "message": "Klasör adı" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "Hiç klasör eklenmedi" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Bu klasörü kalıcı olarak silmek istediğinizden emin misiniz?" + }, "deleteFolder": { "message": "Klasörü sil" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Bir ya da daha fazla kuruluş ilkesi, oluşturucu ayarlarınızı etkiliyor." }, + "passwordGenerator": { + "message": "Parola üreteci" + }, + "usernameGenerator": { + "message": "Kullanıcı adı üreteci" + }, + "useThisPassword": { + "message": "Bu parolayı kullan" + }, + "useThisUsername": { + "message": "Bu kullanıcı adını kullan" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Kasa zaman aşımı eylemi" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Hesap uyuşmazlığı" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biyometrik kilit açma başarısız oldu. Biyometrik gizli anahtarınız kasanın kilidini açamadı. Lütfen biyometriyi yeniden ayarlamayı deneyin." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biyometrik anahtar eşleşmedi" + }, "biometricsNotEnabledTitle": { "message": "Biyometri ayarlanmamış" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Veri" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Ata" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Kayıt konumu" }, + "fileSends": { + "message": "Dosya Send'leri" + }, + "textSends": { + "message": "Metin Send'leri" + }, "bitwardenNewLook": { "message": "Bitwarden'ın tasarımı güncellendi!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Otomatik öneri sayısını uzantı simgesinde göster" + }, + "systemDefault": { + "message": "Sistem varsayılanı" } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index b8ede486c6f..2f26091d8a6 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -141,7 +141,7 @@ "message": "Автозаповнення картки" }, "autoFillIdentity": { - "message": "Автозаповнення особистих даних" + "message": "Автозаповнення посвідчень" }, "generatePasswordCopied": { "message": "Генерувати пароль (з копіюванням)" @@ -165,7 +165,7 @@ "message": "Додати картку" }, "addIdentityMenu": { - "message": "Додати особисті дані" + "message": "Додати посвідчення" }, "unlockVaultMenu": { "message": "Розблокуйте сховище" @@ -304,6 +304,24 @@ "editFolder": { "message": "Редагування" }, + "newFolder": { + "message": "Нова тека" + }, + "folderName": { + "message": "Назва теки" + }, + "folderHintText": { + "message": "Зробіть теку вкладеною, вказавши після основної теки \"/\". Наприклад: Обговорення/Форуми" + }, + "noFoldersAdded": { + "message": "Немає доданих тек" + }, + "createFoldersToOrganize": { + "message": "Створіть теки для організації записів у сховищі" + }, + "deleteFolderPermanently": { + "message": "Ви дійсно хочете остаточно видалити цю теку?" + }, "deleteFolder": { "message": "Видалити теку" }, @@ -652,7 +670,7 @@ } }, "autofillError": { - "message": "Не вдається заповнити пароль на цій сторінці. Скопіюйте і вставте ім'я користувача та/або пароль." + "message": "Не вдається заповнити вибраний запис на цій сторінці. Скопіюйте і вставте інформацію вручну." }, "totpCaptureError": { "message": "Неможливо сканувати QR-код з поточної сторінки" @@ -712,7 +730,7 @@ "message": "Сталася неочікувана помилка." }, "nameRequired": { - "message": "Потрібна назва." + "message": "Необхідно ввести назву." }, "addedFolder": { "message": "Теку додано" @@ -1349,7 +1367,7 @@ "message": "Автозаповнення останньої використаної картки для цього вебсайту" }, "commandAutofillIdentityDesc": { - "message": "Автозаповнення останніх використаних особистих даних для цього вебсайту" + "message": "Автозаповнення останнього використаного посвідчення для цього вебсайту" }, "commandGeneratePasswordDesc": { "message": "Генерувати і копіювати новий випадковий пароль в буфер обміну" @@ -1504,7 +1522,7 @@ "message": "Повне ім'я" }, "identityName": { - "message": "Назва" + "message": "Назва посвідчення" }, "company": { "message": "Компанія" @@ -1564,7 +1582,7 @@ "message": "Картка" }, "typeIdentity": { - "message": "Особисті дані" + "message": "Посвідчення" }, "newItemHeader": { "message": "Новий $TYPE$", @@ -1704,7 +1722,7 @@ "message": "Типи" }, "allItems": { - "message": "Всі елементи" + "message": "Усі записи" }, "noPasswordsInList": { "message": "Немає паролів." @@ -1731,7 +1749,7 @@ "message": "Ви впевнені, що ніколи не хочете блокувати? Встановивши цю опцію, ключ шифрування вашого сховища зберігатиметься на вашому пристрої. Використовуючи цю опцію, вам слід бути певними в тому, що ваш пристрій має належний захист." }, "noOrganizationsList": { - "message": "Ви не входите до жодної організації. Організації дозволяють безпечно обмінюватися елементами з іншими користувачами." + "message": "Ви не входите до жодної організації. Організації дають змогу безпечно обмінюватися записами з іншими користувачами." }, "noCollectionsInList": { "message": "Немає збірок." @@ -1740,7 +1758,7 @@ "message": "Власник" }, "whoOwnsThisItem": { - "message": "Хто є власником цього елемента?" + "message": "Хто є власником цього запису?" }, "strong": { "message": "Надійний", @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "На параметри генератора впливають одна чи декілька політик організації." }, + "passwordGenerator": { + "message": "Генератор паролів" + }, + "usernameGenerator": { + "message": "Генератор імені користувача" + }, + "useThisPassword": { + "message": "Використати цей пароль" + }, + "useThisUsername": { + "message": "Використати це ім'я користувача" + }, + "securePasswordGenerated": { + "message": "Надійний пароль згенеровано! Обов'язково оновіть свій пароль на вебсайті." + }, + "useGeneratorHelpTextPartOne": { + "message": "Скористатися генератором", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "для створення надійного, унікального пароля", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Дія після часу очікування сховища" }, @@ -1851,7 +1892,7 @@ "message": "Запис заповнено і збережено" }, "autoFillSuccess": { - "message": "Запис заповнено" + "message": "Запис заповнено " }, "insecurePageWarning": { "message": "Попередження: це незахищена сторінка HTTP, тому будь-яка інформація, яку ви передаєте, потенційно може бути переглянута чи змінена сторонніми. Ці облікові дані було збережено на безпечній сторінці (HTTPS)." @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Невідповідність облікових записів" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Збій біометричного розблокування. Біометричний секретний ключ не зміг розблокувати сховище. Спробуйте налаштувати біометрію знову." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Біометричний ключ відрізняється" + }, "biometricsNotEnabledTitle": { "message": "Біометрію не налаштовано" }, @@ -2043,7 +2090,7 @@ "message": "Політика організації впливає на ваші параметри власності." }, "personalOwnershipPolicyInEffectImports": { - "message": "Політика організації заблокувала імпортування елементів до вашого особистого сховища." + "message": "Політика організації заблокувала імпортування записів до вашого особистого сховища." }, "domainsTitle": { "message": "Домени", @@ -2328,7 +2375,7 @@ "message": "Ваш головний пароль не відповідає одній або більше політикам вашої організації. Щоб отримати доступ до сховища, вам необхідно оновити свій головний пароль зараз. Продовживши, ви вийдете з поточного сеансу, після чого потрібно буде повторно виконати вхід. Сеанси на інших пристроях можуть залишатися активними протягом однієї години." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Ваша організація вимкнула шифрування довірених пристроїв. Встановіть головний пароль для доступу до сховища." }, "resetPasswordPolicyAutoEnroll": { "message": "Автоматичне розгортання" @@ -2461,7 +2508,7 @@ "message": "Експортування сховища організації" }, "exportingOrganizationVaultDesc": { - "message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Елементи особистих сховищ або інших організацій не будуть включені.", + "message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Записи особистих сховищ або інших організацій не будуть включені.", "placeholders": { "organization": { "content": "$1", @@ -2763,7 +2810,7 @@ "message": "Як працює автозаповнення" }, "autofillSelectInfoWithCommand": { - "message": "Виберіть елемент із цього екрану, скористайтеся комбінацією клавіш $COMMAND$, або дізнайтеся про інші можливості в налаштуваннях.", + "message": "Виберіть об'єкт із цього екрану, скористайтеся комбінацією клавіш $COMMAND$, або дізнайтеся про інші можливості в налаштуваннях.", "placeholders": { "command": { "content": "$1", @@ -2772,7 +2819,7 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Виберіть елемент із цього екрану або дізнайтеся про інші можливості в налаштуваннях." + "message": "Виберіть об'єкт із цього екрану або дізнайтеся про інші можливості в налаштуваннях." }, "gotIt": { "message": "Зрозуміло" @@ -3140,11 +3187,11 @@ "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "Особисті дані", + "message": "Нове посвідчення", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Додавання нового запису для особистих даних – відкриється нове вікно", + "message": "Додавання нового запису для посвідчення – відкриється нове вікно", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { @@ -3177,7 +3224,7 @@ "message": "Дані успішно імпортовано" }, "importSuccessNumberOfItems": { - "message": "Всього імпортовано $AMOUNT$ елементів.", + "message": "Всього імпортовано $AMOUNT$ записів.", "placeholders": { "amount": { "content": "$1", @@ -3286,7 +3333,7 @@ } }, "importUnassignedItemsError": { - "message": "Файл містить непризначені елементи." + "message": "Файл містить непризначені записи." }, "selectFormat": { "message": "Оберіть формат імпортованого файлу" @@ -3368,7 +3415,7 @@ "message": "Перезаписати ключ доступу?" }, "overwritePasskeyAlert": { - "message": "Цей елемент вже містить ключ доступу. Ви впевнені, що хочете перезаписати поточний ключ доступу?" + "message": "Цей запис уже містить ключ доступу. Ви впевнені, що хочете перезаписати поточний ключ доступу?" }, "featureNotSupported": { "message": "Функція ще не підтримується" @@ -3715,7 +3762,7 @@ } }, "itemsWithNoFolder": { - "message": "Елементи без теки" + "message": "Записи без теки" }, "itemDetails": { "message": "Подробиці запису" @@ -3743,7 +3790,7 @@ "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Елементи в деактивованих організаціях недоступні. Зверніться до власника вашої організації для отримання допомоги." + "message": "Записи в деактивованих організаціях недоступні. Зверніться до власника вашої організації для отримання допомоги." }, "additionalInformation": { "message": "Додаткова інформація" @@ -3897,6 +3944,18 @@ "data": { "message": "Дані" }, + "passkeys": { + "message": "Ключі доступу", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Паролі", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Увійти з ключем доступу", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Призначити" }, @@ -4005,10 +4064,10 @@ "message": "Оберіть збірки для призначення" }, "personalItemTransferWarningSingular": { - "message": "1 елемент буде остаточно перенесено до вибраної організації. Ви більше не будете власником цього елемента." + "message": "1 запис буде остаточно перенесено до вибраної організації. Ви більше не будете власником цього запису." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ елементи буде остаточно перенесено до вибраної організації. Ви більше не будете власником цих елементів.", + "message": "$PERSONAL_ITEMS_COUNT$ записів будуть остаточно перенесені до вибраної організації. Ви більше не будете власником цих записів.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4017,7 +4076,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 елемент буде остаточно перенесено до $ORG$. Ви більше не будете власником цього елемента.", + "message": "1 запис буде остаточно перенесено до $ORG$. Ви більше не будете власником цього запису.", "placeholders": { "org": { "content": "$1", @@ -4026,7 +4085,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ елементи буде остаточно перенесено до $ORG$. Ви більше не будете власником цих елементів.", + "message": "$PERSONAL_ITEMS_COUNT$ записів будуть остаточно перенесені до $ORG$. Ви більше не будете власником цих записів.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4054,7 +4113,7 @@ } }, "itemsMovedToOrg": { - "message": "Елементи переміщено до $ORGNAME$", + "message": "Записи переміщено до $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4063,7 +4122,7 @@ } }, "itemMovedToOrg": { - "message": "Елемент переміщено до $ORGNAME$", + "message": "Запис переміщено до $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4089,7 +4148,13 @@ } }, "itemLocation": { - "message": "Розташування елемента" + "message": "Розташування запису" + }, + "fileSends": { + "message": "Відправлення файлів" + }, + "textSends": { + "message": "Відправлення тексту" }, "bitwardenNewLook": { "message": "Bitwarden має новий вигляд!" @@ -4098,6 +4163,12 @@ "message": "Ще простіше автозаповнення та інтуїтивніший пошук у сховищі. Ознайомтеся!" }, "accountActions": { - "message": "Account actions" + "message": "Дії з обліковим записом" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index bd33243824f..38230fa91b0 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "Chỉnh sửa thư mục" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "Xóa thư mục" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "Các chính sách của tổ chức đang ảnh hưởng đến cài đặt tạo mật khẩu của bạn." }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "Hành động khi hết thời gian chờ của kho lưu trữ" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "Tài khoản không đúng" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "Sinh trắc học chưa được cài đặt" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Dữ liệu" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Gán" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Vị trí mục" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index fc4da25af27..1e4a9c964c2 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -120,16 +120,16 @@ "message": "复制名称" }, "copyCompany": { - "message": "复制公司" + "message": "复制公司信息" }, "copySSN": { - "message": "复制社会安全号码" + "message": "复制社会保障号码" }, "copyPassportNumber": { "message": "复制护照号码" }, "copyLicenseNumber": { - "message": "复制驾照号码" + "message": "复制许可证号码" }, "autoFill": { "message": "自动填充" @@ -304,6 +304,24 @@ "editFolder": { "message": "编辑文件夹" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "删除文件夹" }, @@ -345,7 +363,7 @@ "message": "自动生成安全可靠唯一的登录密码。" }, "bitWebVaultApp": { - "message": "Bitwarden 网页版应用" + "message": "Bitwarden 网页 App" }, "importItems": { "message": "导入项目" @@ -652,7 +670,7 @@ } }, "autofillError": { - "message": "无法在此页面上自动填充所选项目。请改为手工复制并粘贴。" + "message": "无法在此页面上自动填充所选项目。请改为手动复制并粘贴。" }, "totpCaptureError": { "message": "无法从当前网页扫描二维码" @@ -1104,7 +1122,7 @@ "message": "自动复制 TOTP" }, "disableAutoTotpCopyDesc": { - "message": "如果登录包含验证器密钥,当自动填充此登录时,TOTP 验证码将复制到剪贴板。" + "message": "如果登录包含验证器密钥,当自动填充此登录时,将 TOTP 验证码复制到剪贴板。" }, "enableAutoBiometricsPrompt": { "message": "启动时要求生物识别" @@ -1343,13 +1361,13 @@ "message": "在侧边栏中打开密码库" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "为当前网站自动填充最后一次使用的登录信息" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "为当前网站自动填充最后一次使用的支付卡信息" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "为当前网站自动填充最后一次使用的身份信息" }, "commandGeneratePasswordDesc": { "message": "生成一个新的随机密码并将其复制到剪贴板中。" @@ -1510,7 +1528,7 @@ "message": "公司" }, "ssn": { - "message": "社会保险号码" + "message": "社会保障号码" }, "passportNumber": { "message": "护照号码" @@ -1656,7 +1674,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "基础域(推荐)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "一个或多个组织策略正在影响您的生成器设置。" }, + "passwordGenerator": { + "message": "密码生成器" + }, + "usernameGenerator": { + "message": "用户名生成器" + }, + "useThisPassword": { + "message": "使用此密码" + }, + "useThisUsername": { + "message": "使用此用户名" + }, + "securePasswordGenerated": { + "message": "安全密码生成好了!别忘了也在网站上更新一下您的密码。" + }, + "useGeneratorHelpTextPartOne": { + "message": "使用此生成器", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "创建一个强大且唯一的密码", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "密码库超时动作" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "账户不匹配" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "生物识别解锁失败。生物识别安全钥匙解锁密码库失败。请尝试重新设置生物识别。" + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "生物识别密钥不匹配" + }, "biometricsNotEnabledTitle": { "message": "生物识别未设置" }, @@ -2328,7 +2375,7 @@ "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "您的组织禁用了信任设备加密。要访问您的密码库,请设置一个主密码。" }, "resetPasswordPolicyAutoEnroll": { "message": "自动注册" @@ -2757,7 +2804,7 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "您的组织策略已开启在页面加载时的自动填充。" + "message": "您的组织策略已开启页面加载时自动填充。" }, "howToAutofill": { "message": "如何自动填充" @@ -2787,16 +2834,16 @@ "message": "更改快捷键" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "管理快捷方式" + "message": "管理快捷键" }, "autofillShortcut": { "message": "自动填充键盘快捷键" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "未设置自动填充登录快捷键。请在浏览器设置中更改它。" }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "自动填充登录快捷键是 $COMMAND$。请在浏览器设置中管理所有快捷键。", "placeholders": { "command": { "content": "$1", @@ -2805,7 +2852,7 @@ } }, "autofillShortcutTextSafari": { - "message": "默认自动填充快捷方式:$COMMAND$。", + "message": "默认的自动填充快捷键:$COMMAND$", "placeholders": { "command": { "content": "$1", @@ -3062,7 +3109,7 @@ "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "页面加载时自动填充设置为默认设置。", + "message": "页面加载时自动填充设置为使用默认设置。", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { @@ -3831,22 +3878,22 @@ "message": "验证器密钥" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "自动填充选项" }, "websiteUri": { - "message": "Website (URI)" + "message": "网站 (URI)" }, "websiteAdded": { - "message": "Website added" + "message": "网址已添加" }, "addWebsite": { - "message": "Add website" + "message": "添加网站" }, "deleteWebsite": { - "message": "Delete website" + "message": "删除网站" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "默认 ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -3856,7 +3903,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "显示匹配检测 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3865,7 +3912,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "隐藏匹配检测 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -3874,7 +3921,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "页面加载时自动填充吗?" }, "cardDetails": { "message": "支付卡详情" @@ -3897,6 +3944,18 @@ "data": { "message": "数据" }, + "passkeys": { + "message": "通行密钥", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "密码", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "使用通行密钥登录", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "分配" }, @@ -4091,13 +4150,25 @@ "itemLocation": { "message": "项目位置" }, + "fileSends": { + "message": "文件 Send" + }, + "textSends": { + "message": "文本 Send" + }, "bitwardenNewLook": { - "message": "Bitwarden 具有一个新的外观!" + "message": "Bitwarden 拥有一个新的外观!" }, "bitwardenNewLookDesc": { "message": "从密码库标签页自动填充和搜索比以往任何时候都更简单直观。来看看吧!" }, "accountActions": { - "message": "Account actions" + "message": "账户操作" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 1d380358613..7e1a4bff408 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -304,6 +304,24 @@ "editFolder": { "message": "編輯資料夾" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "noFoldersAdded": { + "message": "No folders added" + }, + "createFoldersToOrganize": { + "message": "Create folders to organize your vault items" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "deleteFolder": { "message": "刪除資料夾" }, @@ -1803,6 +1821,29 @@ "passwordGeneratorPolicyInEffect": { "message": "一個或多個組織原則正影響密碼產生器設定。" }, + "passwordGenerator": { + "message": "Password generator" + }, + "usernameGenerator": { + "message": "Username generator" + }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "vaultTimeoutAction": { "message": "密碼庫逾時動作" }, @@ -2000,6 +2041,12 @@ "nativeMessagingWrongUserTitle": { "message": "帳戶不相符" }, + "nativeMessagingWrongUserKeyDesc": { + "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + }, + "nativeMessagingWrongUserKeyTitle": { + "message": "Biometric key missmatch" + }, "biometricsNotEnabledTitle": { "message": "生物特徵辨識未設定" }, @@ -3897,6 +3944,18 @@ "data": { "message": "Data" }, + "passkeys": { + "message": "Passkeys", + "description": "A section header for a list of passkeys." + }, + "passwords": { + "message": "Passwords", + "description": "A section header for a list of passwords." + }, + "logInWithPasskeyAriaLabel": { + "message": "Log in with passkey", + "description": "ARIA label for the inline menu button that logs in with a passkey." + }, "assign": { "message": "Assign" }, @@ -4091,6 +4150,12 @@ "itemLocation": { "message": "Item Location" }, + "fileSends": { + "message": "File Sends" + }, + "textSends": { + "message": "Text Sends" + }, "bitwardenNewLook": { "message": "Bitwarden has a new look!" }, @@ -4099,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/store/locales/zh_CN/copy.resx b/apps/browser/store/locales/zh_CN/copy.resx index e864e27ed0b..ea98321a499 100644 --- a/apps/browser/store/locales/zh_CN/copy.resx +++ b/apps/browser/store/locales/zh_CN/copy.resx @@ -170,7 +170,7 @@ Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 - 从多台设备同步和访问密码库 + 在多个设备间同步和访问您的密码库 在一个安全的密码库中管理您所有的登录信息和密码 From 0b5afa6252e239a5945153bf5fe7401b1e58103a Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Mon, 12 Aug 2024 08:38:11 -0500 Subject: [PATCH 26/59] [PM-10731] Move vault icons (#10453) * move NoFolders icon to vault library * move DeactivatedOrg icon to vault library * move Vault icon to vault library * move NoResults icon to vault library * clean up barrel file * Revert "move NoResults icon to vault library" This reverts commit 43abd45db799ca01339f579da3d52a84d64e670e. * re-export no results --- .../src/vault/popup/components/vault/vault-v2.component.ts | 5 +++-- .../browser/src/vault/popup/settings/folders-v2.component.ts | 4 ++-- libs/components/src/icon/icons/index.ts | 3 --- .../src/icon => vault/src}/icons/deactivated-org.ts | 2 +- libs/vault/src/icons/index.ts | 3 +++ libs/{components/src/icon => vault/src}/icons/no-folders.ts | 2 +- libs/{components/src/icon => vault/src}/icons/vault.ts | 2 +- libs/vault/src/index.ts | 2 ++ 8 files changed, 13 insertions(+), 10 deletions(-) rename libs/{components/src/icon => vault/src}/icons/deactivated-org.ts (98%) create mode 100644 libs/vault/src/icons/index.ts rename libs/{components/src/icon => vault/src}/icons/no-folders.ts (98%) rename libs/{components/src/icon => vault/src}/icons/vault.ts (99%) diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts index 11331277a9c..97f028895b0 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts @@ -8,6 +8,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { ButtonModule, Icons, NoItemsModule } from "@bitwarden/components"; +import { VaultIcons } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; @@ -73,8 +74,8 @@ export class VaultV2Component implements OnInit, OnDestroy { /** Visual state of the vault */ protected vaultState: VaultState | null = null; - protected vaultIcon = Icons.Vault; - protected deactivatedIcon = Icons.DeactivatedOrg; + protected vaultIcon = VaultIcons.Vault; + protected deactivatedIcon = VaultIcons.DeactivatedOrg; protected noResultsIcon = Icons.NoResults; protected VaultStateEnum = VaultState; diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.ts index 503484410b1..a0fa763313e 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -10,8 +10,8 @@ import { ButtonModule, DialogService, IconButtonModule, - Icons, } from "@bitwarden/components"; +import { VaultIcons } from "@bitwarden/vault"; import { ItemGroupComponent } from "../../../../../../libs/components/src/item/item-group.component"; import { ItemModule } from "../../../../../../libs/components/src/item/item.module"; @@ -46,7 +46,7 @@ import { NewItemDropdownV2Component } from "../components/vault-v2/new-item-drop export class FoldersV2Component { folders$: Observable; - NoFoldersIcon = Icons.NoFolders; + NoFoldersIcon = VaultIcons.NoFolders; constructor( private folderService: FolderService, diff --git a/libs/components/src/icon/icons/index.ts b/libs/components/src/icon/icons/index.ts index c0a8f3bd1a6..0ed0cc18975 100644 --- a/libs/components/src/icon/icons/index.ts +++ b/libs/components/src/icon/icons/index.ts @@ -1,6 +1,3 @@ -export * from "./deactivated-org"; export * from "./search"; export * from "./no-access"; -export * from "./vault"; export * from "./no-results"; -export * from "./no-folders"; diff --git a/libs/components/src/icon/icons/deactivated-org.ts b/libs/vault/src/icons/deactivated-org.ts similarity index 98% rename from libs/components/src/icon/icons/deactivated-org.ts rename to libs/vault/src/icons/deactivated-org.ts index 9bb9577669e..7d1871a091d 100644 --- a/libs/components/src/icon/icons/deactivated-org.ts +++ b/libs/vault/src/icons/deactivated-org.ts @@ -1,4 +1,4 @@ -import { svgIcon } from "../icon"; +import { svgIcon } from "@bitwarden/components"; export const DeactivatedOrg = svgIcon` diff --git a/libs/vault/src/icons/index.ts b/libs/vault/src/icons/index.ts new file mode 100644 index 00000000000..3bb5c285fbe --- /dev/null +++ b/libs/vault/src/icons/index.ts @@ -0,0 +1,3 @@ +export * from "./deactivated-org"; +export * from "./no-folders"; +export * from "./vault"; diff --git a/libs/components/src/icon/icons/no-folders.ts b/libs/vault/src/icons/no-folders.ts similarity index 98% rename from libs/components/src/icon/icons/no-folders.ts rename to libs/vault/src/icons/no-folders.ts index ee69aac1ae9..478cb6a6b79 100644 --- a/libs/components/src/icon/icons/no-folders.ts +++ b/libs/vault/src/icons/no-folders.ts @@ -1,4 +1,4 @@ -import { svgIcon } from "../icon"; +import { svgIcon } from "@bitwarden/components"; export const NoFolders = svgIcon` diff --git a/libs/components/src/icon/icons/vault.ts b/libs/vault/src/icons/vault.ts similarity index 99% rename from libs/components/src/icon/icons/vault.ts rename to libs/vault/src/icons/vault.ts index 21e0eda0165..4afe0920f7b 100644 --- a/libs/components/src/icon/icons/vault.ts +++ b/libs/vault/src/icons/vault.ts @@ -1,4 +1,4 @@ -import { svgIcon } from "../icon"; +import { svgIcon } from "@bitwarden/components"; export const Vault = svgIcon` diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index e9417495a76..d5841c7db06 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -12,3 +12,5 @@ export { } from "./components/assign-collections.component"; export { DownloadAttachmentComponent } from "./components/download-attachment/download-attachment.component"; + +export * as VaultIcons from "./icons"; From cd7aaacfd95eb5c67c656d6aff94b307684171e4 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:01:19 +0100 Subject: [PATCH 27/59] [PM-10026] [Defect] Zip code is not being enforced when subscribing in Password Manager (#10446) * resolve the postal code error * Resolve the issue if licence is uploaded * resolve the postal code issue on selfhost --- apps/web/src/app/billing/individual/premium.component.ts | 8 +++++--- .../billing/organizations/organization-plans.component.ts | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/billing/individual/premium.component.ts b/apps/web/src/app/billing/individual/premium.component.ts index 7b1e46805bd..b43d3cef342 100644 --- a/apps/web/src/app/billing/individual/premium.component.ts +++ b/apps/web/src/app/billing/individual/premium.component.ts @@ -65,9 +65,11 @@ export class PremiumComponent implements OnInit { } } submit = async () => { - if (!this.taxInfoComponent?.taxFormGroup.valid && this.taxInfoComponent?.taxFormGroup.touched) { - this.taxInfoComponent.taxFormGroup.markAllAsTouched(); - return; + if (this.taxInfoComponent) { + if (!this.taxInfoComponent?.taxFormGroup.valid) { + this.taxInfoComponent.taxFormGroup.markAllAsTouched(); + return; + } } this.licenseForm.markAllAsTouched(); this.addonForm.markAllAsTouched(); 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 13b15eb9ee1..995dcb23890 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -554,9 +554,11 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } submit = async () => { - if (!this.taxComponent?.taxFormGroup.valid && this.taxComponent?.taxFormGroup.touched) { - this.taxComponent?.taxFormGroup.markAllAsTouched(); - return; + if (this.taxComponent) { + if (!this.taxComponent?.taxFormGroup.valid) { + this.taxComponent?.taxFormGroup.markAllAsTouched(); + return; + } } if (this.singleOrgPolicyBlock) { From 7387a1115afd86fa10c1734aadf969b0ab032b12 Mon Sep 17 00:00:00 2001 From: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:25:22 -0400 Subject: [PATCH 28/59] Bumped client version(s) (#10482) --- apps/browser/package.json | 2 +- apps/browser/src/manifest.json | 2 +- apps/browser/src/manifest.v3.json | 2 +- apps/cli/package.json | 2 +- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- apps/web/package.json | 2 +- package-lock.json | 8 ++++---- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index bbcf0badbc5..a210b6353c4 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2024.7.1", + "version": "2024.8.0", "scripts": { "build": "cross-env MANIFEST_VERSION=3 webpack", "build:mv2": "webpack", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 111ee44d683..81f43ff82ad 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2024.7.1", + "version": "2024.8.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 e57a5522492..c6de26b8192 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2024.7.1", + "version": "2024.8.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/cli/package.json b/apps/cli/package.json index 5f417579081..cb0c2a01678 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2024.7.2", + "version": "2024.8.0", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 61d4607cea0..fd9c2351dd2 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2024.7.2", + "version": "2024.8.0", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index d3d99c4cfb3..59cbea3910c 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2024.7.2", + "version": "2024.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2024.7.2", + "version": "2024.8.0", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native", diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index a6bd0d9ef39..bed3b631dac 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2024.7.2", + "version": "2024.8.0", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/web/package.json b/apps/web/package.json index f8ef4a8030c..35ef8056ee5 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2024.7.3", + "version": "2024.8.0", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index 0362185a1a7..69190da2c23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -195,11 +195,11 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2024.7.1" + "version": "2024.8.0" }, "apps/cli": { "name": "@bitwarden/cli", - "version": "2024.7.2", + "version": "2024.8.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@koa/multer": "3.0.2", @@ -235,7 +235,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2024.7.2", + "version": "2024.8.0", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -249,7 +249,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2024.7.3" + "version": "2024.8.0" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From 334601e74fa3eefd670c3f0a3b2c9c5f3fde0913 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:13:34 -0400 Subject: [PATCH 29/59] [PM-10788] Handle `chrome.runtime.lastError` (#10479) * Handle `chrome.runtime.lastError` * Add Tests --- .../abstract-chrome-storage-api.service.ts | 20 +++++++++++--- .../chrome-storage-api.service.spec.ts | 27 +++++++++++++++++++ .../browser-local-storage.service.spec.ts | 19 +++++++++++++ .../services/browser-local-storage.service.ts | 17 +++++++++--- 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts b/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts index 4690562fd75..47a128bc1bc 100644 --- a/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts +++ b/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts @@ -74,8 +74,12 @@ export default abstract class AbstractChromeStorageService } async get(key: string): Promise { - return new Promise((resolve) => { - this.chromeStorageApi.get(key, (obj: any) => { + return new Promise((resolve, reject) => { + this.chromeStorageApi.get(key, (obj) => { + if (chrome.runtime.lastError) { + return reject(chrome.runtime.lastError); + } + if (obj != null && obj[key] != null) { resolve(this.processGetObject(obj[key])); return; @@ -98,16 +102,24 @@ export default abstract class AbstractChromeStorageService } const keyedObj = { [key]: obj }; - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this.chromeStorageApi.set(keyedObj, () => { + if (chrome.runtime.lastError) { + return reject(chrome.runtime.lastError); + } + resolve(); }); }); } async remove(key: string): Promise { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this.chromeStorageApi.remove(key, () => { + if (chrome.runtime.lastError) { + return reject(chrome.runtime.lastError); + } + resolve(); }); }); diff --git a/apps/browser/src/platform/services/abstractions/chrome-storage-api.service.spec.ts b/apps/browser/src/platform/services/abstractions/chrome-storage-api.service.spec.ts index ceadc16a58e..ac8a01375fa 100644 --- a/apps/browser/src/platform/services/abstractions/chrome-storage-api.service.spec.ts +++ b/apps/browser/src/platform/services/abstractions/chrome-storage-api.service.spec.ts @@ -51,6 +51,10 @@ describe("ChromeStorageApiService", () => { }); }); + afterEach(() => { + chrome.runtime.lastError = undefined; + }); + it("uses `objToStore` to prepare a value for set", async () => { const key = "key"; const value = { key: "value" }; @@ -73,6 +77,15 @@ describe("ChromeStorageApiService", () => { await service.save(key, null); expect(removeMock).toHaveBeenCalledWith(key, expect.any(Function)); }); + + it("translates chrome.runtime.lastError to promise rejection", async () => { + setMock.mockImplementation((data, callback) => { + chrome.runtime.lastError = new Error("Test Error"); + callback(); + }); + + await expect(async () => await service.save("test", {})).rejects.toThrow("Test Error"); + }); }); describe("get", () => { @@ -87,6 +100,10 @@ describe("ChromeStorageApiService", () => { }); }); + afterEach(() => { + chrome.runtime.lastError = undefined; + }); + it("returns a stored value when it is serialized", async () => { const value = { key: "value" }; store[key] = objToStore(value); @@ -112,5 +129,15 @@ describe("ChromeStorageApiService", () => { const result = await service.get(key); expect(result).toBeNull(); }); + + it("translates chrome.runtime.lastError to promise rejection", async () => { + getMock.mockImplementation((key, callback) => { + chrome.runtime.lastError = new Error("Test Error"); + callback(); + chrome.runtime.lastError = undefined; + }); + + await expect(async () => await service.get("test")).rejects.toThrow("Test Error"); + }); }); }); diff --git a/apps/browser/src/platform/services/browser-local-storage.service.spec.ts b/apps/browser/src/platform/services/browser-local-storage.service.spec.ts index bd79dd6fa56..13e26c26ddd 100644 --- a/apps/browser/src/platform/services/browser-local-storage.service.spec.ts +++ b/apps/browser/src/platform/services/browser-local-storage.service.spec.ts @@ -62,6 +62,7 @@ describe("BrowserLocalStorageService", () => { }); afterEach(() => { + chrome.runtime.lastError = undefined; jest.resetAllMocks(); }); @@ -121,6 +122,24 @@ describe("BrowserLocalStorageService", () => { expect(clearMock).toHaveBeenCalledTimes(1); }); + + it("throws if get has chrome.runtime.lastError", async () => { + getMock.mockImplementation((key, callback) => { + chrome.runtime.lastError = new Error("Get Test Error"); + callback(); + }); + + await expect(async () => await service.reseed()).rejects.toThrow("Get Test Error"); + }); + + it("throws if save has chrome.runtime.lastError", async () => { + saveMock.mockImplementation((obj, callback) => { + chrome.runtime.lastError = new Error("Save Test Error"); + callback(); + }); + + await expect(async () => await service.reseed()).rejects.toThrow("Save Test Error"); + }); }); describe.each(["get", "has", "save", "remove"] as const)("%s", (method) => { diff --git a/apps/browser/src/platform/services/browser-local-storage.service.ts b/apps/browser/src/platform/services/browser-local-storage.service.ts index 7957b6edeaf..0ba200055bb 100644 --- a/apps/browser/src/platform/services/browser-local-storage.service.ts +++ b/apps/browser/src/platform/services/browser-local-storage.service.ts @@ -81,8 +81,11 @@ export default class BrowserLocalStorageService extends AbstractChromeStorageSer * Clears local storage */ private async clear() { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this.chromeStorageApi.clear(() => { + if (chrome.runtime.lastError) { + return reject(chrome.runtime.lastError); + } resolve(); }); }); @@ -95,8 +98,12 @@ export default class BrowserLocalStorageService extends AbstractChromeStorageSer * @returns Promise resolving to keyed object of all stored data */ private async getAll(): Promise> { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this.chromeStorageApi.get(null, (allStorage) => { + if (chrome.runtime.lastError) { + return reject(chrome.runtime.lastError); + } + const resolved = Object.entries(allStorage).reduce( (agg, [key, value]) => { agg[key] = this.processGetObject(value); @@ -110,7 +117,7 @@ export default class BrowserLocalStorageService extends AbstractChromeStorageSer } private async saveAll(data: Record): Promise { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { const keyedData = Object.entries(data).reduce( (agg, [key, value]) => { agg[key] = objToStore(value); @@ -119,6 +126,10 @@ export default class BrowserLocalStorageService extends AbstractChromeStorageSer {} as Record, ); this.chromeStorageApi.set(keyedData, () => { + if (chrome.runtime.lastError) { + return reject(chrome.runtime.lastError); + } + resolve(); }); }); From a7adf952dbabb137bf6267e51f8c7cfc2dc4eb7c Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:29:22 -0400 Subject: [PATCH 30/59] [PM-10754] Store DeviceKey In Backup Storage Location (#10469) * Implement Backup Storage Location For Browser Disk * Remove Testing Change * Remove Comment * Add Comment For `disk-backup-local-storage` * Require Matching `valuesRequireDeserialization` values --- .../browser/src/background/main.background.ts | 8 +++ .../offscreen-document/offscreen-document.ts | 15 +++++ .../browser-storage-service.provider.ts | 3 + .../storage/offscreen-storage.service.ts | 55 +++++++++++++++++ .../src/popup/services/services.module.ts | 13 ++++ apps/web/src/app/core/core.module.ts | 2 +- .../app/platform/web-migration-runner.spec.ts | 2 +- .../src/app/platform/web-migration-runner.ts | 3 +- .../src/platform/state/state-definition.ts | 7 ++- .../src/platform/state/state-definitions.ts | 1 + .../primary-secondary-storage.service.ts | 59 +++++++++++++++++++ .../storage}/window-storage.service.ts | 4 +- 12 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 apps/browser/src/platform/storage/offscreen-storage.service.ts create mode 100644 libs/common/src/platform/storage/primary-secondary-storage.service.ts rename {apps/web/src/app/platform => libs/common/src/platform/storage}/window-storage.service.ts (90%) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 6faa0389561..c738b6eed96 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -143,6 +143,8 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state"; import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service"; /* eslint-enable import/no-restricted-paths */ +import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service"; +import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service"; import { SyncService } from "@bitwarden/common/platform/sync"; // eslint-disable-next-line no-restricted-imports -- Needed for service creation import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal"; @@ -239,6 +241,7 @@ import { ForegroundTaskSchedulerService } from "../platform/services/task-schedu import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service"; import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider"; import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service"; +import { OffscreenStorageService } from "../platform/storage/offscreen-storage.service"; import { ForegroundSyncService } from "../platform/sync/foreground-sync.service"; import { SyncServiceListener } from "../platform/sync/sync-service.listener"; import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; @@ -485,10 +488,15 @@ export default class MainBackground { ? mv3MemoryStorageCreator() // mv3 stores to local-backed session storage : this.memoryStorageForStateProviders; // mv2 stores to the same location + const localStorageStorageService = BrowserApi.isManifestVersion(3) + ? new OffscreenStorageService(this.offscreenDocumentService) + : new WindowStorageService(self.localStorage); + const storageServiceProvider = new BrowserStorageServiceProvider( this.storageService, this.memoryStorageForStateProviders, this.largeObjectMemoryStorageForStateProviders, + new PrimarySecondaryStorageService(this.storageService, localStorageStorageService), ); this.globalStateProvider = new DefaultGlobalStateProvider( diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.ts index 4994a6e9ba8..938e3191e0d 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.ts @@ -14,6 +14,9 @@ class OffscreenDocument implements OffscreenDocumentInterface { private readonly extensionMessageHandlers: OffscreenDocumentExtensionMessageHandlers = { offscreenCopyToClipboard: ({ message }) => this.handleOffscreenCopyToClipboard(message), offscreenReadFromClipboard: () => this.handleOffscreenReadFromClipboard(), + localStorageGet: ({ message }) => this.handleLocalStorageGet(message.key), + localStorageSave: ({ message }) => this.handleLocalStorageSave(message.key, message.value), + localStorageRemove: ({ message }) => this.handleLocalStorageRemove(message.key), }; /** @@ -39,6 +42,18 @@ class OffscreenDocument implements OffscreenDocumentInterface { return await BrowserClipboardService.read(self); } + private handleLocalStorageGet(key: string) { + return self.localStorage.getItem(key); + } + + private handleLocalStorageSave(key: string, value: string) { + self.localStorage.setItem(key, value); + } + + private handleLocalStorageRemove(key: string) { + self.localStorage.removeItem(key); + } + /** * Sets up the listener for extension messages. */ diff --git a/apps/browser/src/platform/storage/browser-storage-service.provider.ts b/apps/browser/src/platform/storage/browser-storage-service.provider.ts index e0214baef44..5854669138a 100644 --- a/apps/browser/src/platform/storage/browser-storage-service.provider.ts +++ b/apps/browser/src/platform/storage/browser-storage-service.provider.ts @@ -14,6 +14,7 @@ export class BrowserStorageServiceProvider extends StorageServiceProvider { diskStorageService: AbstractStorageService & ObservableStorageService, limitedMemoryStorageService: AbstractStorageService & ObservableStorageService, private largeObjectMemoryStorageService: AbstractStorageService & ObservableStorageService, + private readonly diskBackupLocalStorage: AbstractStorageService & ObservableStorageService, ) { super(diskStorageService, limitedMemoryStorageService); } @@ -26,6 +27,8 @@ export class BrowserStorageServiceProvider extends StorageServiceProvider { switch (location) { case "memory-large-object": return ["memory-large-object", this.largeObjectMemoryStorageService]; + case "disk-backup-local-storage": + return ["disk-backup-local-storage", this.diskBackupLocalStorage]; default: // Pass in computed location to super because they could have // override default "disk" with web "memory". diff --git a/apps/browser/src/platform/storage/offscreen-storage.service.ts b/apps/browser/src/platform/storage/offscreen-storage.service.ts new file mode 100644 index 00000000000..34d3bd7a9ac --- /dev/null +++ b/apps/browser/src/platform/storage/offscreen-storage.service.ts @@ -0,0 +1,55 @@ +import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; +import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; + +import { BrowserApi } from "../browser/browser-api"; +import { OffscreenDocumentService } from "../offscreen-document/abstractions/offscreen-document"; + +export class OffscreenStorageService implements AbstractStorageService { + constructor(private readonly offscreenDocumentService: OffscreenDocumentService) {} + + get valuesRequireDeserialization(): boolean { + return true; + } + + async get(key: string, options?: StorageOptions): Promise { + return await this.offscreenDocumentService.withDocument( + [chrome.offscreen.Reason.LOCAL_STORAGE], + "backup storage of user data", + async () => { + const response = await BrowserApi.sendMessageWithResponse("localStorageGet", { + key, + }); + if (response != null) { + return JSON.parse(response); + } + + return response; + }, + ); + } + async has(key: string, options?: StorageOptions): Promise { + return (await this.get(key, options)) != null; + } + + async save(key: string, obj: T, options?: StorageOptions): Promise { + await this.offscreenDocumentService.withDocument( + [chrome.offscreen.Reason.LOCAL_STORAGE], + "backup storage of user data", + async () => + await BrowserApi.sendMessageWithResponse("localStorageSave", { + key, + value: JSON.stringify(obj), + }), + ); + } + async remove(key: string, options?: StorageOptions): Promise { + await this.offscreenDocumentService.withDocument( + [chrome.offscreen.Reason.LOCAL_STORAGE], + "backup storage of user data", + async () => + await BrowserApi.sendMessageWithResponse("localStorageRemove", { + key, + }), + ); + } +} diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 2fba41d17ad..f6f3bf732b0 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -73,6 +73,8 @@ import { } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- Used for dependency injection import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state"; +import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service"; +import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -122,6 +124,10 @@ const OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE = new SafeInjectionToken< AbstractStorageService & ObservableStorageService >("OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE"); +const DISK_BACKUP_LOCAL_STORAGE = new SafeInjectionToken< + AbstractStorageService & ObservableStorageService +>("DISK_BACKUP_LOCAL_STORAGE"); + const needsBackgroundInit = BrowserPopupUtils.backgroundInitializationRequired(); const mainBackground: MainBackground = needsBackgroundInit ? createLocalBgService() @@ -496,6 +502,12 @@ const safeProviders: SafeProvider[] = [ }, deps: [], }), + safeProvider({ + provide: DISK_BACKUP_LOCAL_STORAGE, + useFactory: (diskStorage: AbstractStorageService & ObservableStorageService) => + new PrimarySecondaryStorageService(diskStorage, new WindowStorageService(self.localStorage)), + deps: [OBSERVABLE_DISK_STORAGE], + }), safeProvider({ provide: StorageServiceProvider, useClass: BrowserStorageServiceProvider, @@ -503,6 +515,7 @@ const safeProviders: SafeProvider[] = [ OBSERVABLE_DISK_STORAGE, OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE, + DISK_BACKUP_LOCAL_STORAGE, ], }), safeProvider({ diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 4d8acbde0d1..3b7ba0a727a 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -49,6 +49,7 @@ import { StorageServiceProvider } from "@bitwarden/common/platform/services/stor import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state"; import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service"; /* eslint-enable import/no-restricted-paths -- Implementation for memory storage */ +import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service"; import { DefaultThemeStateService, ThemeStateService, @@ -63,7 +64,6 @@ import { I18nService } from "../core/i18n.service"; import { WebEnvironmentService } from "../platform/web-environment.service"; import { WebMigrationRunner } from "../platform/web-migration-runner"; import { WebStorageServiceProvider } from "../platform/web-storage-service.provider"; -import { WindowStorageService } from "../platform/window-storage.service"; import { CollectionAdminService } from "../vault/core/collection-admin.service"; import { EventService } from "./event.service"; diff --git a/apps/web/src/app/platform/web-migration-runner.spec.ts b/apps/web/src/app/platform/web-migration-runner.spec.ts index 4b2949230e4..c27be4a145e 100644 --- a/apps/web/src/app/platform/web-migration-runner.spec.ts +++ b/apps/web/src/app/platform/web-migration-runner.spec.ts @@ -3,11 +3,11 @@ import { MockProxy, mock } from "jest-mock-extended"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; +import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service"; import { MigrationBuilder } from "@bitwarden/common/state-migrations/migration-builder"; import { MigrationHelper } from "@bitwarden/common/state-migrations/migration-helper"; import { WebMigrationRunner } from "./web-migration-runner"; -import { WindowStorageService } from "./window-storage.service"; describe("WebMigrationRunner", () => { let logService: MockProxy; diff --git a/apps/web/src/app/platform/web-migration-runner.ts b/apps/web/src/app/platform/web-migration-runner.ts index 392eeeae045..7ac10cd2e08 100644 --- a/apps/web/src/app/platform/web-migration-runner.ts +++ b/apps/web/src/app/platform/web-migration-runner.ts @@ -4,10 +4,9 @@ import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; +import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service"; import { MigrationHelper } from "@bitwarden/common/state-migrations/migration-helper"; -import { WindowStorageService } from "./window-storage.service"; - export class WebMigrationRunner extends MigrationRunner { constructor( diskStorage: AbstractStorageService, diff --git a/libs/common/src/platform/state/state-definition.ts b/libs/common/src/platform/state/state-definition.ts index f1e7dc80abd..3caa03c95a5 100644 --- a/libs/common/src/platform/state/state-definition.ts +++ b/libs/common/src/platform/state/state-definition.ts @@ -25,9 +25,12 @@ export type ClientLocations = { /** * Overriding storage location for browser clients. * - * "memory-large-object" is used to store non-countable objects in memory. This exists due to limited persistent memory available to browser extensions. + * `"memory-large-object"` is used to store non-countable objects in memory. This exists due to limited persistent memory available to browser extensions. + * + * `"disk-backup-local-storage"` is used to store object in both disk and in `localStorage`. Data is stored in both locations but is only retrieved + * from `localStorage` when a null-ish value is retrieved from disk first. */ - browser: StorageLocation | "memory-large-object"; + browser: StorageLocation | "memory-large-object" | "disk-backup-local-storage"; /** * Overriding storage location for desktop clients. */ diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index f7609e877d8..cbf4cd28f2f 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -46,6 +46,7 @@ export const AUTH_REQUEST_DISK_LOCAL = new StateDefinition("authRequestLocal", " export const AVATAR_DISK = new StateDefinition("avatar", "disk", { web: "disk-local" }); export const DEVICE_TRUST_DISK_LOCAL = new StateDefinition("deviceTrust", "disk", { web: "disk-local", + browser: "disk-backup-local-storage", }); export const KDF_CONFIG_DISK = new StateDefinition("kdfConfig", "disk"); export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk"); diff --git a/libs/common/src/platform/storage/primary-secondary-storage.service.ts b/libs/common/src/platform/storage/primary-secondary-storage.service.ts new file mode 100644 index 00000000000..df62010fdc1 --- /dev/null +++ b/libs/common/src/platform/storage/primary-secondary-storage.service.ts @@ -0,0 +1,59 @@ +import { AbstractStorageService, ObservableStorageService } from "../abstractions/storage.service"; +import { StorageOptions } from "../models/domain/storage-options"; + +export class PrimarySecondaryStorageService + implements AbstractStorageService, ObservableStorageService +{ + // Only follow the primary storage service as updates should all be done to both + updates$ = this.primaryStorageService.updates$; + + constructor( + private readonly primaryStorageService: AbstractStorageService & ObservableStorageService, + // Secondary service doesn't need to be observable as the only `updates$` are listened to from the primary store + private readonly secondaryStorageService: AbstractStorageService, + ) { + if ( + primaryStorageService.valuesRequireDeserialization !== + secondaryStorageService.valuesRequireDeserialization + ) { + throw new Error( + "Differing values for valuesRequireDeserialization between storage services is not supported.", + ); + } + } + get valuesRequireDeserialization(): boolean { + return this.primaryStorageService.valuesRequireDeserialization; + } + + async get(key: string, options?: StorageOptions): Promise { + const primaryValue = await this.primaryStorageService.get(key, options); + + // If it's null-ish try the secondary location for its value + if (primaryValue == null) { + return await this.secondaryStorageService.get(key, options); + } + + return primaryValue; + } + + async has(key: string, options?: StorageOptions): Promise { + return ( + (await this.primaryStorageService.has(key, options)) || + (await this.secondaryStorageService.has(key, options)) + ); + } + + async save(key: string, obj: T, options?: StorageOptions): Promise { + await Promise.allSettled([ + this.primaryStorageService.save(key, obj, options), + this.secondaryStorageService.save(key, obj, options), + ]); + } + + async remove(key: string, options?: StorageOptions): Promise { + await Promise.allSettled([ + this.primaryStorageService.remove(key, options), + this.secondaryStorageService.remove(key, options), + ]); + } +} diff --git a/apps/web/src/app/platform/window-storage.service.ts b/libs/common/src/platform/storage/window-storage.service.ts similarity index 90% rename from apps/web/src/app/platform/window-storage.service.ts rename to libs/common/src/platform/storage/window-storage.service.ts index d5ba1283a95..4eba94b2f2b 100644 --- a/apps/web/src/app/platform/window-storage.service.ts +++ b/libs/common/src/platform/storage/window-storage.service.ts @@ -4,8 +4,8 @@ import { AbstractStorageService, ObservableStorageService, StorageUpdate, -} from "@bitwarden/common/platform/abstractions/storage.service"; -import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; +} from "../abstractions/storage.service"; +import { StorageOptions } from "../models/domain/storage-options"; export class WindowStorageService implements AbstractStorageService, ObservableStorageService { private readonly updatesSubject = new Subject(); From 6bc0ffc9302925452be134a1baaea0d0867f81a0 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 12 Aug 2024 20:42:04 +0200 Subject: [PATCH 31/59] [Beeep] [PM-10767] Improve username importing on protonpass importer (#10471) * Improve username importing on protonpass importer * Conditionally write proton importer custom fields --- libs/importer/spec/protonpass-json-importer.spec.ts | 4 ++-- .../spec/test-data/protonpass-json/protonpass.json.ts | 4 ++-- .../importers/protonpass/protonpass-json-importer.ts | 11 +++++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/libs/importer/spec/protonpass-json-importer.spec.ts b/libs/importer/spec/protonpass-json-importer.spec.ts index 5f6bc4fd689..f58e4d5f257 100644 --- a/libs/importer/spec/protonpass-json-importer.spec.ts +++ b/libs/importer/spec/protonpass-json-importer.spec.ts @@ -31,8 +31,8 @@ describe("Protonpass Json Importer", () => { expect(uriView.uri).toEqual("https://example.com/"); expect(cipher.notes).toEqual("My login secure note."); - expect(cipher.fields.at(0).name).toEqual("itemUsername"); - expect(cipher.fields.at(0).value).toEqual("someOtherUsername"); + expect(cipher.fields.at(0).name).toEqual("email"); + expect(cipher.fields.at(0).value).toEqual("Email"); expect(cipher.fields.at(3).name).toEqual("second 2fa secret"); expect(cipher.fields.at(3).value).toEqual("TOTPCODE"); diff --git a/libs/importer/spec/test-data/protonpass-json/protonpass.json.ts b/libs/importer/spec/test-data/protonpass-json/protonpass.json.ts index 55c2a9109e1..b4214031667 100644 --- a/libs/importer/spec/test-data/protonpass-json/protonpass.json.ts +++ b/libs/importer/spec/test-data/protonpass-json/protonpass.json.ts @@ -49,12 +49,12 @@ export const testData: ProtonPassJsonFile = { ], type: "login", content: { - itemEmail: "Username", + itemEmail: "Email", password: "Password", urls: ["https://example.com/", "https://example2.com/"], totpUri: "otpauth://totp/Test%20Login%20-%20Personal%20Vault:Username?issuer=Test%20Login%20-%20Personal%20Vault&secret=TOTPCODE&algorithm=SHA1&digits=6&period=30", - itemUsername: "someOtherUsername", + itemUsername: "Username", }, }, state: 1, diff --git a/libs/importer/src/importers/protonpass/protonpass-json-importer.ts b/libs/importer/src/importers/protonpass/protonpass-json-importer.ts index 229e508db5a..b680f0a3872 100644 --- a/libs/importer/src/importers/protonpass/protonpass-json-importer.ts +++ b/libs/importer/src/importers/protonpass/protonpass-json-importer.ts @@ -48,10 +48,17 @@ export class ProtonPassJsonImporter extends BaseImporter implements Importer { case "login": { const loginContent = item.data.content as ProtonPassLoginItemContent; cipher.login.uris = this.makeUriArray(loginContent.urls); - cipher.login.username = this.getValueOrDefault(loginContent.itemEmail); + + cipher.login.username = this.getValueOrDefault(loginContent.itemUsername); + // if the cipher has no username then the email is used as the username + if (cipher.login.username == null) { + cipher.login.username = this.getValueOrDefault(loginContent.itemEmail); + } else { + this.processKvp(cipher, "email", loginContent.itemEmail); + } + cipher.login.password = this.getValueOrDefault(loginContent.password); cipher.login.totp = this.getValueOrDefault(loginContent.totpUri); - this.processKvp(cipher, "itemUsername", loginContent.itemUsername); for (const extraField of item.data.extraFields) { this.processKvp( cipher, From d5cc2d65180f16540f318acb7da1a7aef402980d Mon Sep 17 00:00:00 2001 From: Yann Plougonven--Lastennet <77398553+Yann-Plougonven@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:45:32 +0200 Subject: [PATCH 32/59] [PM-10764] Fix ProtonPass creditCard expirationDate import (#10473) * Fix ProtonPass creditCard expirationDate import with protonpass-json-importer.ts * Update protonpass CC expirationDate testData to newest version Between 1.3.1 and 1.12.2 ProtonPass has changed the format of exported CC expiration dates from MMYYYY to YYYY-MM --------- Co-authored-by: Daniel James Smith --- .../spec/test-data/protonpass-json/protonpass.json.ts | 2 +- .../src/importers/protonpass/protonpass-json-importer.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/importer/spec/test-data/protonpass-json/protonpass.json.ts b/libs/importer/spec/test-data/protonpass-json/protonpass.json.ts index b4214031667..ddbe9e89b9e 100644 --- a/libs/importer/spec/test-data/protonpass-json/protonpass.json.ts +++ b/libs/importer/spec/test-data/protonpass-json/protonpass.json.ts @@ -102,7 +102,7 @@ export const testData: ProtonPassJsonFile = { cardType: 0, number: "1234222233334444", verificationNumber: "333", - expirationDate: "012025", + expirationDate: "2025-01", pin: "1234", }, }, diff --git a/libs/importer/src/importers/protonpass/protonpass-json-importer.ts b/libs/importer/src/importers/protonpass/protonpass-json-importer.ts index b680f0a3872..7a7018bde22 100644 --- a/libs/importer/src/importers/protonpass/protonpass-json-importer.ts +++ b/libs/importer/src/importers/protonpass/protonpass-json-importer.ts @@ -84,9 +84,9 @@ export class ProtonPassJsonImporter extends BaseImporter implements Importer { cipher.card.code = this.getValueOrDefault(creditCardContent.verificationNumber); if (!this.isNullOrWhitespace(creditCardContent.expirationDate)) { - cipher.card.expMonth = creditCardContent.expirationDate.substring(0, 2); + cipher.card.expMonth = creditCardContent.expirationDate.substring(5, 7); cipher.card.expMonth = cipher.card.expMonth.replace(/^0+/, ""); - cipher.card.expYear = creditCardContent.expirationDate.substring(2, 6); + cipher.card.expYear = creditCardContent.expirationDate.substring(0, 4); } if (!this.isNullOrWhitespace(creditCardContent.pin)) { From 0d829b7398cc1425426b5d78333757c8fc1ec441 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:51:57 -0400 Subject: [PATCH 33/59] Auth/PM-3515 - Lock component Tech Debt Clean up (#10332) * PM-3515 - Lock component - remove isUnlocked check on lock comp load b/c lock guard should cover all cases with its existing logic for all clients. * PM-3515 - VaultTimeoutSettingsSvc - Add new canLock method * PM-3515 - Refactor logic out of lock component that belongs in lock guard. Update lock guard to reject route activation if a user can't lock whereas we used to log the user out when they landed on the lock comp. * PM-3515 - WIP on testing all lock guard scenarios * PM-3515 - Refactor lock guard tests + add more tests * PM-3515 - LockGuard - if TDE user that is authN directly navigates from login-init to lock for whatever reason (only possible on web with url bar), reject that navigation directly instead of throwing them up to the redirect guard * PM-3515 - More LockGuard tests * PM-3515 - Update comment --- .../src/auth/components/lock.component.ts | 32 --- .../src/auth/guards/lock.guard.spec.ts | 235 ++++++++++++++++++ libs/angular/src/auth/guards/lock.guard.ts | 21 +- .../vault-timeout-settings.service.ts | 6 + .../vault-timeout-settings.service.spec.ts | 32 +++ .../vault-timeout-settings.service.ts | 9 +- 6 files changed, 297 insertions(+), 38 deletions(-) create mode 100644 libs/angular/src/auth/guards/lock.guard.spec.ts diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts index 88b042c5b8e..28cc7d810d7 100644 --- a/libs/angular/src/auth/components/lock.component.ts +++ b/libs/angular/src/auth/components/lock.component.ts @@ -16,14 +16,12 @@ import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractio import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { MasterPasswordVerification, MasterPasswordVerificationResponse, } from "@bitwarden/common/auth/types/verification"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -316,36 +314,6 @@ export class LockComponent implements OnInit, OnDestroy { } private async load(userId: UserId) { - // TODO: Investigate PM-3515 - - // The loading of the lock component works as follows: - // 1. If the user is unlocked, we're here in error so we navigate to the home page - // 2. First, is locking a valid timeout action? If not, we will log the user out. - // 3. If locking IS a valid timeout action, we proceed to show the user the lock screen. - // The user will be able to unlock as follows: - // - If they have a PIN set, they will be presented with the PIN input - // - If they have a master password and no PIN, they will be presented with the master password input - // - If they have biometrics enabled, they will be presented with the biometric prompt - - const isUnlocked = await firstValueFrom( - this.authService - .authStatusFor$(userId) - .pipe(map((status) => status === AuthenticationStatus.Unlocked)), - ); - if (isUnlocked) { - // navigate to home - await this.router.navigate(["/"]); - return; - } - - const availableVaultTimeoutActions = await firstValueFrom( - this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(userId), - ); - const supportsLock = availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock); - if (!supportsLock) { - return await this.vaultTimeoutService.logOut(userId); - } - this.pinLockType = await this.pinService.getPinLockType(userId); const ephemeralPinSet = await this.pinService.getPinKeyEncryptedUserKeyEphemeral(userId); diff --git a/libs/angular/src/auth/guards/lock.guard.spec.ts b/libs/angular/src/auth/guards/lock.guard.spec.ts new file mode 100644 index 00000000000..7ff7feb9206 --- /dev/null +++ b/libs/angular/src/auth/guards/lock.guard.spec.ts @@ -0,0 +1,235 @@ +import { TestBed } from "@angular/core/testing"; +import { Router } from "@angular/router"; +import { RouterTestingModule } from "@angular/router/testing"; +import { MockProxy, mock } from "jest-mock-extended"; +import { BehaviorSubject, of } from "rxjs"; + +import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { ClientType } from "@bitwarden/common/enums"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { lockGuard } from "./lock.guard"; + +interface SetupParams { + authStatus: AuthenticationStatus; + canLock?: boolean; + isLegacyUser?: boolean; + clientType?: ClientType; + everHadUserKey?: boolean; + supportsDeviceTrust?: boolean; + hasMasterPassword?: boolean; +} + +describe("lockGuard", () => { + const setup = (setupParams: SetupParams) => { + const authService: MockProxy = mock(); + authService.authStatusFor$.mockReturnValue(of(setupParams.authStatus)); + + const vaultTimeoutSettingsService: MockProxy = + mock(); + vaultTimeoutSettingsService.canLock.mockResolvedValue(setupParams.canLock); + + const cryptoService: MockProxy = mock(); + cryptoService.isLegacyUser.mockResolvedValue(setupParams.isLegacyUser); + cryptoService.everHadUserKey$ = of(setupParams.everHadUserKey); + + const platformUtilService: MockProxy = mock(); + platformUtilService.getClientType.mockReturnValue(setupParams.clientType); + + const messagingService: MockProxy = mock(); + + const deviceTrustService: MockProxy = + mock(); + deviceTrustService.supportsDeviceTrust$ = of(setupParams.supportsDeviceTrust); + + const userVerificationService: MockProxy = + mock(); + userVerificationService.hasMasterPassword.mockResolvedValue(setupParams.hasMasterPassword); + + const accountService: MockProxy = mock(); + const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>(null); + accountService.activeAccount$ = activeAccountSubject; + activeAccountSubject.next( + Object.assign( + { + name: "Test User 1", + email: "test@email.com", + emailVerified: true, + } as AccountInfo, + { id: "test-id" as UserId }, + ), + ); + + const testBed = TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([ + { path: "", component: EmptyComponent }, + { path: "lock", component: EmptyComponent, canActivate: [lockGuard()] }, + { path: "non-lock-route", component: EmptyComponent }, + { path: "migrate-legacy-encryption", component: EmptyComponent }, + ]), + ], + providers: [ + { provide: AuthService, useValue: authService }, + { provide: MessagingService, useValue: messagingService }, + { provide: AccountService, useValue: accountService }, + { provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService }, + { provide: CryptoService, useValue: cryptoService }, + { provide: PlatformUtilsService, useValue: platformUtilService }, + { provide: DeviceTrustServiceAbstraction, useValue: deviceTrustService }, + { provide: UserVerificationService, useValue: userVerificationService }, + ], + }); + + return { + router: testBed.inject(Router), + messagingService, + }; + }; + + it("should be created", () => { + const { router } = setup({ + authStatus: AuthenticationStatus.Locked, + }); + expect(router).toBeTruthy(); + }); + + it("should redirect to the root route when the user is Unlocked", async () => { + const { router } = setup({ + authStatus: AuthenticationStatus.Unlocked, + }); + + await router.navigate(["lock"]); + expect(router.url).toBe("/"); + }); + + it("should redirect to the root route when the user is LoggedOut", async () => { + const { router } = setup({ + authStatus: AuthenticationStatus.LoggedOut, + }); + + await router.navigate(["lock"]); + expect(router.url).toBe("/"); + }); + + it("should allow navigation to the lock route when the user is Locked and they can lock", async () => { + const { router } = setup({ + authStatus: AuthenticationStatus.Locked, + canLock: true, + }); + + await router.navigate(["lock"]); + expect(router.url).toBe("/lock"); + }); + + it("should allow navigation to the lock route when the user is locked, they can lock, and device trust is not supported", async () => { + const { router } = setup({ + authStatus: AuthenticationStatus.Locked, + canLock: true, + supportsDeviceTrust: false, + }); + + await router.navigate(["lock"]); + expect(router.url).toBe("/lock"); + }); + + it("should not allow navigation to the lock route when canLock is false", async () => { + const { router } = setup({ + authStatus: AuthenticationStatus.Locked, + canLock: false, + }); + + await router.navigate(["lock"]); + expect(router.url).toBe("/"); + }); + + it("should log user out if they are a legacy user on a desktop client", async () => { + const { router, messagingService } = setup({ + authStatus: AuthenticationStatus.Locked, + canLock: true, + isLegacyUser: true, + clientType: ClientType.Desktop, + }); + + await router.navigate(["lock"]); + expect(router.url).toBe("/"); + expect(messagingService.send).toHaveBeenCalledWith("logout"); + }); + + it("should log user out if they are a legacy user on a browser extension client", async () => { + const { router, messagingService } = setup({ + authStatus: AuthenticationStatus.Locked, + canLock: true, + isLegacyUser: true, + clientType: ClientType.Browser, + }); + + await router.navigate(["lock"]); + expect(router.url).toBe("/"); + expect(messagingService.send).toHaveBeenCalledWith("logout"); + }); + + it("should send the user to migrate-legacy-encryption if they are a legacy user on a web client", async () => { + const { router } = setup({ + authStatus: AuthenticationStatus.Locked, + canLock: true, + isLegacyUser: true, + clientType: ClientType.Web, + }); + + await router.navigate(["lock"]); + expect(router.url).toBe("/migrate-legacy-encryption"); + }); + + it("should allow navigation to the lock route when device trust is supported, the user has a MP, and the user is coming from the login-initiated page", async () => { + const { router } = setup({ + authStatus: AuthenticationStatus.Locked, + canLock: true, + isLegacyUser: false, + clientType: ClientType.Web, + everHadUserKey: false, + supportsDeviceTrust: true, + hasMasterPassword: true, + }); + + await router.navigate(["lock"], { queryParams: { from: "login-initiated" } }); + expect(router.url).toBe("/lock?from=login-initiated"); + }); + + it("should allow navigation to the lock route when TDE is disabled, the user doesn't have a MP, and the user has had a user key", async () => { + const { router } = setup({ + authStatus: AuthenticationStatus.Locked, + canLock: true, + supportsDeviceTrust: false, + hasMasterPassword: false, + everHadUserKey: true, + }); + + await router.navigate(["lock"]); + expect(router.url).toBe("/lock"); + }); + + it("should not allow navigation to the lock route when device trust is supported and the user has not ever had a user key", async () => { + const { router } = setup({ + authStatus: AuthenticationStatus.Locked, + canLock: true, + isLegacyUser: false, + clientType: ClientType.Web, + everHadUserKey: false, + supportsDeviceTrust: true, + hasMasterPassword: false, + }); + + await router.navigate(["lock"]); + expect(router.url).toBe("/"); + }); +}); diff --git a/libs/angular/src/auth/guards/lock.guard.ts b/libs/angular/src/auth/guards/lock.guard.ts index 8cd5290ebc8..440e6931a09 100644 --- a/libs/angular/src/auth/guards/lock.guard.ts +++ b/libs/angular/src/auth/guards/lock.guard.ts @@ -7,6 +7,8 @@ import { } from "@angular/router"; import { firstValueFrom } from "rxjs"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; @@ -19,7 +21,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl /** * Only allow access to this route if the vault is locked. * If TDE is enabled then the user must also have had a user key at some point. - * Otherwise redirect to root. + * Otherwise reject navigation. * * TODO: This should return Observable once we can remove all the promises */ @@ -35,12 +37,22 @@ export function lockGuard(): CanActivateFn { const messagingService = inject(MessagingService); const router = inject(Router); const userVerificationService = inject(UserVerificationService); + const vaultTimeoutSettingsService = inject(VaultTimeoutSettingsService); + const accountService = inject(AccountService); - const authStatus = await authService.getAuthStatus(); + const activeUser = await firstValueFrom(accountService.activeAccount$); + + const authStatus = await firstValueFrom(authService.authStatusFor$(activeUser.id)); if (authStatus !== AuthenticationStatus.Locked) { return router.createUrlTree(["/"]); } + // if user can't lock, they can't access the lock screen + const canLock = await vaultTimeoutSettingsService.canLock(activeUser.id); + if (!canLock) { + return false; + } + // If legacy user on web, redirect to migration page if (await cryptoService.isLegacyUser()) { if (platformUtilService.getClientType() === ClientType.Web) { @@ -65,11 +77,10 @@ export function lockGuard(): CanActivateFn { return true; } - // If authN user with TDE directly navigates to lock, kick them upwards so redirect guard can - // properly route them to the login decryption options component. + // If authN user with TDE directly navigates to lock, reject that navigation const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$); if (tdeEnabled && !everHadUserKey) { - return router.createUrlTree(["/"]); + return false; } return true; diff --git a/libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts b/libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts index 5bf38f3b571..69539e6fea0 100644 --- a/libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts +++ b/libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts @@ -25,6 +25,12 @@ export abstract class VaultTimeoutSettingsService { */ availableVaultTimeoutActions$: (userId?: string) => Observable; + /** + * Evaluates the user's available vault timeout actions and returns a boolean representing + * if the user can lock or not + */ + canLock: (userId: string) => Promise; + /** * Gets the vault timeout action for the given user id. The returned value is * calculated based on the current state, if a max vault timeout policy applies to the user, diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts index e9839fc4e63..177c75ed5b8 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts @@ -127,6 +127,38 @@ describe("VaultTimeoutSettingsService", () => { }); }); + describe("canLock", () => { + it("returns true if the user can lock", async () => { + jest + .spyOn(vaultTimeoutSettingsService, "availableVaultTimeoutActions$") + .mockReturnValue(of([VaultTimeoutAction.Lock])); + + const result = await vaultTimeoutSettingsService.canLock("userId" as UserId); + + expect(result).toBe(true); + }); + + it("returns false if the user only has the log out vault timeout action", async () => { + jest + .spyOn(vaultTimeoutSettingsService, "availableVaultTimeoutActions$") + .mockReturnValue(of([VaultTimeoutAction.LogOut])); + + const result = await vaultTimeoutSettingsService.canLock("userId" as UserId); + + expect(result).toBe(false); + }); + + it("returns false if the user has no vault timeout actions", async () => { + jest + .spyOn(vaultTimeoutSettingsService, "availableVaultTimeoutActions$") + .mockReturnValue(of([])); + + const result = await vaultTimeoutSettingsService.canLock("userId" as UserId); + + expect(result).toBe(false); + }); + }); + describe("getVaultTimeoutActionByUserId$", () => { it("should throw an error if no user id is provided", async () => { expect(() => vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(null)).toThrow( diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts index d48729e9c61..e6587ade70d 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts @@ -90,10 +90,17 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA await this.cryptoService.refreshAdditionalKeys(); } - availableVaultTimeoutActions$(userId?: string) { + availableVaultTimeoutActions$(userId?: string): Observable { return defer(() => this.getAvailableVaultTimeoutActions(userId)); } + async canLock(userId: UserId): Promise { + const availableVaultTimeoutActions: VaultTimeoutAction[] = await firstValueFrom( + this.availableVaultTimeoutActions$(userId), + ); + return availableVaultTimeoutActions?.includes(VaultTimeoutAction.Lock) || false; + } + async isBiometricLockSet(userId?: string): Promise { const biometricUnlockPromise = userId == null From b2db633714c1cc9b21b13e83365335fd7d4ae642 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:25:43 -0500 Subject: [PATCH 34/59] only allow for new folders when on the folder screen (#10489) --- .../src/vault/popup/settings/folders-v2.component.html | 5 ++++- .../browser/src/vault/popup/settings/folders-v2.component.ts | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.html b/apps/browser/src/vault/popup/settings/folders-v2.component.html index 7ac69876cc0..21e00757a29 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.html +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.html @@ -1,7 +1,10 @@ - + + {{ "new" | i18n }} +
    diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.ts index a0fa763313e..ce196132f88 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -23,7 +23,6 @@ import { AddEditFolderDialogComponent, AddEditFolderDialogData, } from "../components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component"; -import { NewItemDropdownV2Component } from "../components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component"; @Component({ standalone: true, @@ -31,7 +30,6 @@ import { NewItemDropdownV2Component } from "../components/vault-v2/new-item-drop imports: [ CommonModule, JslibModule, - NewItemDropdownV2Component, PopOutComponent, PopupPageComponent, PopupHeaderComponent, From 295fb8f7a4bd99df23435b7a926fcd14c19a6a87 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Mon, 12 Aug 2024 17:26:47 -0400 Subject: [PATCH 35/59] [PM-3530][PM-8588] persist extension route history (#9556) Save the extension popup route history and restore it after closing and re-opening the popup. --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> --- .../extension-anon-layout-wrapper.stories.ts | 11 +- .../browser/src/background/main.background.ts | 11 ++ .../popup/layout/popup-header.component.ts | 9 +- .../popup/layout/popup-layout.stories.ts | 8 ++ .../view-cache/popup-router-cache.service.ts | 131 ++++++++++++++++++ .../view-cache/popup-router-cache.spec.ts | 113 +++++++++++++++ .../popup-view-cache-background.service.ts | 63 +++++++++ apps/browser/src/popup/app-routing.module.ts | 2 + apps/browser/src/popup/app.component.ts | 3 + .../popup/send-v2/send-v2.component.spec.ts | 2 + .../vault-generator-dialog.component.spec.ts | 3 + libs/common/src/enums/feature-flag.enum.ts | 2 + .../src/platform/state/state-definitions.ts | 3 + .../src/cipher-view/cipher-view.component.ts | 7 - 14 files changed, 356 insertions(+), 12 deletions(-) create mode 100644 apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts create mode 100644 apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts create mode 100644 apps/browser/src/platform/services/popup-view-cache-background.service.ts diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index c447ccffd78..44060f991ff 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -28,6 +28,7 @@ import { UserId } from "@bitwarden/common/types/guid"; import { ButtonModule, I18nMockService } from "@bitwarden/components"; import { RegistrationCheckEmailIcon } from "../../../../../../libs/auth/src/angular/icons/registration-check-email.icon"; +import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; import { ExtensionAnonLayoutWrapperDataService } from "./extension-anon-layout-wrapper-data.service"; import { @@ -145,7 +146,15 @@ const decorators = (options: { ], }), applicationConfig({ - providers: [importProvidersFrom(RouterModule.forRoot(options.routes))], + providers: [ + importProvidersFrom(RouterModule.forRoot(options.routes)), + { + provide: PopupRouterCacheService, + useValue: { + back() {}, + } as Partial, + }, + ], }), ]; }; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index c738b6eed96..7d38682c200 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -236,6 +236,7 @@ import I18nService from "../platform/services/i18n.service"; import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service"; import { BackgroundPlatformUtilsService } from "../platform/services/platform-utils/background-platform-utils.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; +import { PopupViewCacheBackgroundService } from "../platform/services/popup-view-cache-background.service"; import { BackgroundTaskSchedulerService } from "../platform/services/task-scheduler/background-task-scheduler.service"; import { ForegroundTaskSchedulerService } from "../platform/services/task-scheduler/foreground-task-scheduler.service"; import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service"; @@ -371,6 +372,8 @@ export default class MainBackground { private isSafari: boolean; private nativeMessagingBackground: NativeMessagingBackground; + private popupViewCacheBackgroundService: PopupViewCacheBackgroundService; + constructor(public popupOnlyContext: boolean = false) { // Services const lockedCallback = async (userId?: string) => { @@ -571,6 +574,10 @@ export default class MainBackground { logoutCallback, ); + this.popupViewCacheBackgroundService = new PopupViewCacheBackgroundService( + this.globalStateProvider, + ); + const migrationRunner = new MigrationRunner( this.storageService, this.logService, @@ -1201,6 +1208,8 @@ export default class MainBackground { await (this.i18nService as I18nService).init(); (this.eventUploadService as EventUploadService).init(true); + this.popupViewCacheBackgroundService.startObservingTabChanges(); + if (this.popupOnlyContext) { return; } @@ -1295,6 +1304,7 @@ export default class MainBackground { }), ), ); + await this.popupViewCacheBackgroundService.clearState(); await this.accountService.switchAccount(userId); await switchPromise; // Clear sequentialized caches @@ -1383,6 +1393,7 @@ export default class MainBackground { this.vaultTimeoutSettingsService.clear(userBeingLoggedOut), this.vaultFilterService.clear(), this.biometricStateService.logout(userBeingLoggedOut), + this.popupViewCacheBackgroundService.clearState(), /* We intentionally do not clear: * - autofillSettingsService * - badgeSettingsService diff --git a/apps/browser/src/platform/popup/layout/popup-header.component.ts b/apps/browser/src/platform/popup/layout/popup-header.component.ts index 1e41f7ccbe0..fcf7f57c89f 100644 --- a/apps/browser/src/platform/popup/layout/popup-header.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-header.component.ts @@ -1,5 +1,5 @@ import { BooleanInput, coerceBooleanProperty } from "@angular/cdk/coercion"; -import { CommonModule, Location } from "@angular/common"; +import { CommonModule } from "@angular/common"; import { Component, Input, Signal, inject } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -10,6 +10,8 @@ import { TypographyModule, } from "@bitwarden/components"; +import { PopupRouterCacheService } from "../view-cache/popup-router-cache.service"; + import { PopupPageComponent } from "./popup-page.component"; @Component({ @@ -19,6 +21,7 @@ import { PopupPageComponent } from "./popup-page.component"; imports: [TypographyModule, CommonModule, IconButtonModule, JslibModule, AsyncActionsModule], }) export class PopupHeaderComponent { + private popupRouterCacheService = inject(PopupRouterCacheService); protected pageContentScrolled: Signal = inject(PopupPageComponent).isScrolled; /** Background color */ @@ -46,8 +49,6 @@ export class PopupHeaderComponent { **/ @Input() backAction: FunctionReturningAwaitable = async () => { - this.location.back(); + return this.popupRouterCacheService.back(); }; - - constructor(private location: Location) {} } diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index 3d067b53056..affa804cc79 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -16,6 +16,8 @@ import { SectionComponent, } from "@bitwarden/components"; +import { PopupRouterCacheService } from "../view-cache/popup-router-cache.service"; + import { PopupFooterComponent } from "./popup-footer.component"; import { PopupHeaderComponent } from "./popup-header.component"; import { PopupPageComponent } from "./popup-page.component"; @@ -334,6 +336,12 @@ export default { { useHash: true }, ), ), + { + provide: PopupRouterCacheService, + useValue: { + back() {}, + } as Partial, + }, ], }), ], 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 new file mode 100644 index 00000000000..52b08ac09ab --- /dev/null +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts @@ -0,0 +1,131 @@ +import { Location } from "@angular/common"; +import { Injectable, inject } from "@angular/core"; +import { + ActivatedRouteSnapshot, + CanActivateFn, + NavigationEnd, + Router, + UrlSerializer, +} from "@angular/router"; +import { filter, first, firstValueFrom, map, Observable, of, switchMap } from "rxjs"; + +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { GlobalStateProvider } from "@bitwarden/common/platform/state"; + +import { POPUP_ROUTE_HISTORY_KEY } from "../../../platform/services/popup-view-cache-background.service"; +import BrowserPopupUtils from "../browser-popup-utils"; + +/** + * Preserves route history when opening and closing the popup + * + * Routes marked with `doNotSaveUrl` will not be stored + **/ +@Injectable({ + providedIn: "root", +}) +export class PopupRouterCacheService { + private router = inject(Router); + private state = inject(GlobalStateProvider).get(POPUP_ROUTE_HISTORY_KEY); + private location = inject(Location); + + constructor() { + // init history with existing state + this.history$() + .pipe(first()) + .subscribe((history) => history.forEach((location) => this.location.go(location))); + + // update state when route change occurs + this.router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + filter((_event: NavigationEnd) => { + const state: ActivatedRouteSnapshot = this.router.routerState.snapshot.root; + + let child = state.firstChild; + while (child.firstChild) { + child = child.firstChild; + } + + return !child?.data?.doNotSaveUrl ?? true; + }), + switchMap((event) => this.push(event.url)), + ) + .subscribe(); + } + + history$(): Observable { + return this.state.state$; + } + + async setHistory(state: string[]): Promise { + return this.state.update(() => state); + } + + /** Get the last item from the history stack, or `null` if empty */ + last$(): Observable { + return this.history$().pipe( + map((history) => { + if (!history || history.length === 0) { + return null; + } + return history[history.length - 1]; + }), + ); + } + + /** + * If in browser popup, push new route onto history stack + */ + private async push(url: string): Promise { + if (!BrowserPopupUtils.inPopup(window) || url === (await firstValueFrom(this.last$()))) { + return; + } + await this.state.update((prevState) => (prevState == null ? [url] : prevState.concat(url))); + } + + /** + * Navigate back in history + */ + async back() { + await this.state.update((prevState) => prevState.slice(0, -1)); + + const url = this.router.url; + this.location.back(); + if (url !== this.router.url) { + return; + } + + // if no history is present, fallback to vault page + await this.router.navigate([""]); + } +} + +/** + * Redirect to the last visited route. Should be applied to root route. + * + * If `FeatureFlag.PersistPopupView` is disabled, do nothing. + **/ +export const popupRouterCacheGuard = (() => { + const configService = inject(ConfigService); + const popupHistoryService = inject(PopupRouterCacheService); + const urlSerializer = inject(UrlSerializer); + + return configService.getFeatureFlag$(FeatureFlag.PersistPopupView).pipe( + switchMap((featureEnabled) => { + if (!featureEnabled) { + return of(true); + } + + return popupHistoryService.last$().pipe( + map((url: string) => { + if (!url) { + return true; + } + + return urlSerializer.parse(url); + }), + ); + }), + ); +}) satisfies CanActivateFn; diff --git a/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts b/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts new file mode 100644 index 00000000000..dde7f10500b --- /dev/null +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts @@ -0,0 +1,113 @@ +import { Component } from "@angular/core"; +import { TestBed } from "@angular/core/testing"; +import { Router, UrlSerializer, UrlTree } from "@angular/router"; +import { RouterTestingModule } from "@angular/router/testing"; +import { mock } from "jest-mock-extended"; +import { firstValueFrom, of } from "rxjs"; + +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { GlobalStateProvider } from "@bitwarden/common/platform/state"; +import { FakeGlobalStateProvider } from "@bitwarden/common/spec"; + +import { PopupRouterCacheService, popupRouterCacheGuard } from "./popup-router-cache.service"; + +const flushPromises = async () => await new Promise(process.nextTick); + +@Component({ template: "" }) +export class EmptyComponent {} + +describe("Popup router cache guard", () => { + const configServiceMock = mock(); + const fakeGlobalStateProvider = new FakeGlobalStateProvider(); + + let testBed: TestBed; + let serializer: UrlSerializer; + let router: Router; + + let service: PopupRouterCacheService; + + beforeEach(async () => { + jest.spyOn(configServiceMock, "getFeatureFlag$").mockReturnValue(of(true)); + + testBed = TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([ + { path: "a", component: EmptyComponent }, + { path: "b", component: EmptyComponent }, + { + path: "c", + component: EmptyComponent, + data: { doNotSaveUrl: true }, + }, + ]), + ], + providers: [ + { provide: ConfigService, useValue: configServiceMock }, + { provide: GlobalStateProvider, useValue: fakeGlobalStateProvider }, + ], + }); + + await testBed.compileComponents(); + + router = testBed.inject(Router); + serializer = testBed.inject(UrlSerializer); + + service = testBed.inject(PopupRouterCacheService); + + await service.setHistory([]); + }); + + it("returns true if the history stack is empty", async () => { + const response = await firstValueFrom( + testBed.runInInjectionContext(() => popupRouterCacheGuard()), + ); + + expect(response).toBe(true); + }); + + it("redirects to the latest stored route", async () => { + await router.navigate(["a"]); + await router.navigate(["b"]); + + const response = (await firstValueFrom( + testBed.runInInjectionContext(() => popupRouterCacheGuard()), + )) as UrlTree; + + expect(serializer.serialize(response)).toBe("/b"); + }); + + it("back method redirects to the previous route", async () => { + await router.navigate(["a"]); + await router.navigate(["b"]); + + // wait for router events subscription + await flushPromises(); + + expect(await firstValueFrom(service.history$())).toEqual(["/a", "/b"]); + + await service.back(); + + expect(await firstValueFrom(service.history$())).toEqual(["/a"]); + }); + + it("does not save ignored routes", async () => { + await router.navigate(["a"]); + await router.navigate(["b"]); + await router.navigate(["c"]); + + const response = (await firstValueFrom( + testBed.runInInjectionContext(() => popupRouterCacheGuard()), + )) as UrlTree; + + expect(serializer.serialize(response)).toBe("/b"); + }); + + it("does not save duplicate routes", async () => { + await router.navigate(["a"]); + await router.navigate(["a"]); + + await flushPromises(); + + expect(await firstValueFrom(service.history$())).toEqual(["/a"]); + }); +}); diff --git a/apps/browser/src/platform/services/popup-view-cache-background.service.ts b/apps/browser/src/platform/services/popup-view-cache-background.service.ts new file mode 100644 index 00000000000..3427a02d3fa --- /dev/null +++ b/apps/browser/src/platform/services/popup-view-cache-background.service.ts @@ -0,0 +1,63 @@ +import { switchMap, merge, delay, filter, map } from "rxjs"; + +import { + POPUP_VIEW_MEMORY, + KeyDefinition, + GlobalStateProvider, +} from "@bitwarden/common/platform/state"; + +import { fromChromeEvent } from "../browser/from-chrome-event"; + +const popupClosedPortName = "new_popup"; + +export const POPUP_ROUTE_HISTORY_KEY = new KeyDefinition( + POPUP_VIEW_MEMORY, + "popup-route-history", + { + deserializer: (jsonValue) => jsonValue, + }, +); + +export class PopupViewCacheBackgroundService { + private popupRouteHistoryState = this.globalStateProvider.get(POPUP_ROUTE_HISTORY_KEY); + + constructor(private globalStateProvider: GlobalStateProvider) {} + + startObservingTabChanges() { + merge( + // on tab changed, excluding extension tabs + fromChromeEvent(chrome.tabs.onActivated).pipe( + switchMap(([tabInfo]) => chrome.tabs.get(tabInfo.tabId)), + map((tab) => tab.url || tab.pendingUrl), + filter((url) => !url.startsWith(chrome.runtime.getURL(""))), + ), + + // on popup closed, with 2 minute delay that is cancelled by re-opening the popup + fromChromeEvent(chrome.runtime.onConnect).pipe( + filter(([port]) => port.name === popupClosedPortName), + switchMap(([port]) => fromChromeEvent(port.onDisconnect).pipe(delay(1000 * 60 * 2))), + ), + ) + .pipe(switchMap(() => this.clearState())) + .subscribe(); + } + + async clearState() { + return Promise.all([ + this.popupRouteHistoryState.update(() => [], { shouldUpdate: this.objNotEmpty }), + ]); + } + + private objNotEmpty(obj: object): boolean { + return Object.keys(obj ?? {}).length !== 0; + } +} + +/** + * Communicates to {@link PopupViewCacheBackgroundService} that the extension popup has been closed. + * + * Call in the foreground. + **/ +export const initPopupClosedListener = () => { + chrome.runtime.connect({ name: popupClosedPortName }); +}; diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index b7b4e41778b..5c4c01bfc16 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -48,6 +48,7 @@ import { NotificationsSettingsV1Component } from "../autofill/popup/settings/not import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumComponent } from "../billing/popup/settings/premium.component"; import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; +import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; import { GeneratorComponent } from "../tools/popup/generator/generator.component"; import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component"; import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component"; @@ -105,6 +106,7 @@ const routes: Routes = [ pathMatch: "full", children: [], // Children lets us have an empty component. canActivate: [ + popupRouterCacheGuard, redirectGuard({ loggedIn: "/tabs/current", loggedOut: "/home", locked: "/lock" }), ], }, diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 287e9096684..7ac3e02160d 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -20,6 +20,7 @@ import { } from "@bitwarden/components"; import { BrowserApi } from "../platform/browser/browser-api"; +import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service"; import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service"; import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; @@ -59,6 +60,8 @@ export class AppComponent implements OnInit, OnDestroy { ) {} async ngOnInit() { + initPopupClosedListener(); + // Component states must not persist between closing and reopening the popup, otherwise they become dead objects // Clear them aggressively to make sure this doesn't occur await this.clearComponentStates(); 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 53a0441eecd..50e5531743a 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 @@ -34,6 +34,7 @@ import { CurrentAccountComponent } from "../../../auth/popup/account-switching/c import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; import { SendV2Component, SendState } from "./send-v2.component"; @@ -102,6 +103,7 @@ describe("SendV2Component", () => { { provide: SendItemsService, useValue: sendItemsService }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: SendListFiltersService, useValue: sendListFiltersService }, + { provide: PopupRouterCacheService, useValue: mock() }, ], }).compileComponents(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts index d25dfadf5bc..d37bc367110 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts @@ -7,6 +7,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; +import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; + import { GeneratorDialogParams, GeneratorDialogResult, @@ -39,6 +41,7 @@ describe("VaultGeneratorDialogComponent", () => { { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: DIALOG_DATA, useValue: dialogData }, { provide: DialogRef, useValue: mockDialogRef }, + { provide: PopupRouterCacheService, useValue: mock() }, ], }) .overrideComponent(VaultGeneratorDialogComponent, { diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 3b2849e0f54..87b372d3b13 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -13,6 +13,7 @@ export enum FeatureFlag { AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section", EnableDeleteProvider = "AC-1218-delete-provider", ExtensionRefresh = "extension-refresh", + PersistPopupView = "persist-popup-view", RestrictProviderAccess = "restrict-provider-access", PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service", UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", @@ -54,6 +55,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE, [FeatureFlag.EnableDeleteProvider]: FALSE, [FeatureFlag.ExtensionRefresh]: FALSE, + [FeatureFlag.PersistPopupView]: FALSE, [FeatureFlag.RestrictProviderAccess]: FALSE, [FeatureFlag.PM4154_BulkEncryptionService]: FALSE, [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index cbf4cd28f2f..f3880633e06 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -111,6 +111,9 @@ export const CRYPTO_MEMORY = new StateDefinition("crypto", "memory"); export const DESKTOP_SETTINGS_DISK = new StateDefinition("desktopSettings", "disk"); export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk"); export const ENVIRONMENT_MEMORY = new StateDefinition("environment", "memory"); +export const POPUP_VIEW_MEMORY = new StateDefinition("popupView", "memory", { + browser: "memory-large-object", +}); export const SYNC_DISK = new StateDefinition("sync", "disk", { web: "memory" }); export const THEMING_DISK = new StateDefinition("theming", "disk", { web: "disk-local" }); export const TRANSLATION_DISK = new StateDefinition("translation", "disk", { web: "disk-local" }); diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index 7900fddbe06..092b7dba396 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -13,10 +13,6 @@ import { CollectionView } from "@bitwarden/common/vault/models/view/collection.v import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { SearchModule } from "@bitwarden/components"; -import { PopupFooterComponent } from "../../../../apps/browser/src/platform/popup/layout/popup-footer.component"; -import { PopupHeaderComponent } from "../../../../apps/browser/src/platform/popup/layout/popup-header.component"; -import { PopupPageComponent } from "../../../../apps/browser/src/platform/popup/layout/popup-page.component"; - import { AdditionalOptionsComponent } from "./additional-options/additional-options.component"; import { AttachmentsV2ViewComponent } from "./attachments/attachments-v2-view.component"; import { AutofillOptionsViewComponent } from "./autofill-options/autofill-options-view.component"; @@ -35,9 +31,6 @@ import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-ide CommonModule, SearchModule, JslibModule, - PopupPageComponent, - PopupHeaderComponent, - PopupFooterComponent, ItemDetailsV2Component, AdditionalOptionsComponent, AttachmentsV2ViewComponent, From 2665afffdda539dd03a1df9a6c43b7a6c8dff2fe Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 13 Aug 2024 11:13:04 +0200 Subject: [PATCH 36/59] [PM-8316] Fix account switcher on logged out account on startup (#10342) * Fix account switcher on logged out account on startup * Undo space removal * Add additional checks for null values * Fix account switching on timeout on browser --- apps/browser/src/background/main.background.ts | 12 ++++++++++++ .../app/layout/account-switcher.component.ts | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 7d38682c200..0f124d2c5dd 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1234,6 +1234,18 @@ export default class MainBackground { ); } + // If the user is logged out, switch to the next account + const active = await firstValueFrom(this.accountService.activeAccount$); + if (active == null) { + return; + } + const authStatus = await firstValueFrom( + this.authService.authStatuses$.pipe(map((statuses) => statuses[active.id])), + ); + if (authStatus === AuthenticationStatus.LoggedOut) { + const nextUpAccount = await firstValueFrom(this.accountService.nextUpAccount$); + await this.switchAccount(nextUpAccount?.id); + } await this.initOverlayAndTabsBackground(); return new Promise((resolve) => { diff --git a/apps/desktop/src/app/layout/account-switcher.component.ts b/apps/desktop/src/app/layout/account-switcher.component.ts index f641d801b8d..02d98512609 100644 --- a/apps/desktop/src/app/layout/account-switcher.component.ts +++ b/apps/desktop/src/app/layout/account-switcher.component.ts @@ -151,6 +151,24 @@ export class AccountSwitcherComponent { ); } + async ngOnInit() { + const active = await firstValueFrom(this.accountService.activeAccount$); + if (active == null) { + return; + } + const authStatus = await firstValueFrom( + this.authService.authStatuses$.pipe(map((statuses) => statuses[active.id])), + ); + if (authStatus === AuthenticationStatus.LoggedOut) { + const nextUpAccount = await firstValueFrom(this.accountService.nextUpAccount$); + if (nextUpAccount != null) { + await this.switch(nextUpAccount.id); + } else { + await this.addAccount(); + } + } + } + toggle() { this.isOpen = !this.isOpen; } From a559a113d3eb5c770c84b54d36f4a496f590f73c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 13 Aug 2024 11:15:37 +0200 Subject: [PATCH 37/59] Move dialogs out of native messaging (#10411) --- .../settings/account-security.component.ts | 99 ++++++++------ .../background/nativeMessaging.background.ts | 125 +++++------------- apps/browser/src/models/biometricErrors.ts | 34 ++++- 3 files changed, 130 insertions(+), 128 deletions(-) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 820cdf11cdc..076c03801aa 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -1,3 +1,4 @@ +import { DialogRef } from "@angular/cdk/dialog"; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { @@ -382,49 +383,73 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { return; } - const awaitDesktopDialogRef = AwaitDesktopDialogComponent.open(this.dialogService); - const awaitDesktopDialogClosed = firstValueFrom(awaitDesktopDialogRef.closed); + let awaitDesktopDialogRef: DialogRef | undefined; + let biometricsResponseReceived = false; await this.cryptoService.refreshAdditionalKeys(); - await Promise.race([ - awaitDesktopDialogClosed.then(async (result) => { - if (result !== true) { - this.form.controls.biometric.setValue(false); - } - }), - this.platformUtilsService - .authenticateBiometric() - .then((result) => { - this.form.controls.biometric.setValue(result); - if (!result) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorEnableBiometricTitle"), - this.i18nService.t("errorEnableBiometricDesc"), - ); - } - }) - .catch((e) => { - // Handle connection errors - this.form.controls.biometric.setValue(false); + const waitForUserDialogPromise = async () => { + // only show waiting dialog if we have waited for 200 msec to prevent double dialog + // the os will respond instantly if the dialog shows successfully, and the desktop app will respond instantly if something is wrong + await new Promise((resolve) => setTimeout(resolve, 200)); + if (biometricsResponseReceived) { + return; + } - const error = BiometricErrors[e.message as BiometricErrorTypes]; + awaitDesktopDialogRef = AwaitDesktopDialogComponent.open(this.dialogService); + const result = await firstValueFrom(awaitDesktopDialogRef.closed); + if (result !== true) { + this.form.controls.biometric.setValue(false); + } + }; - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.dialogService.openSimpleDialog({ - title: { key: error.title }, - content: { key: error.description }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); - }) - .finally(() => { + const biometricsPromise = async () => { + try { + const result = await this.platformUtilsService.authenticateBiometric(); + + // prevent duplicate dialog + biometricsResponseReceived = true; + if (awaitDesktopDialogRef) { awaitDesktopDialogRef.close(true); - }), - ]); + } + + this.form.controls.biometric.setValue(result); + if (!result) { + this.platformUtilsService.showToast( + "error", + this.i18nService.t("errorEnableBiometricTitle"), + this.i18nService.t("errorEnableBiometricDesc"), + ); + } + } catch (e) { + // prevent duplicate dialog + biometricsResponseReceived = true; + if (awaitDesktopDialogRef) { + awaitDesktopDialogRef.close(true); + } + + this.form.controls.biometric.setValue(false); + + if (e.message == "canceled") { + return; + } + + const error = BiometricErrors[e.message as BiometricErrorTypes]; + await this.dialogService.openSimpleDialog({ + title: { key: error.title }, + content: { key: error.description }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "danger", + }); + } finally { + if (awaitDesktopDialogRef) { + awaitDesktopDialogRef.close(true); + } + } + }; + + await Promise.race([waitForUserDialogPromise(), biometricsPromise()]); } else { await this.biometricStateService.setBiometricUnlockEnabled(false); await this.biometricStateService.setFingerprintValidated(false); diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index e19485c7118..19c55f9f696 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -21,7 +21,7 @@ import { BrowserApi } from "../platform/browser/browser-api"; import RuntimeBackground from "./runtime.background"; const MessageValidTimeout = 10 * 1000; -const EncryptionAlgorithm = "sha1"; +const HashAlgorithmForEncryption = "sha1"; type Message = { command: string; @@ -64,6 +64,7 @@ export class NativeMessagingBackground { private port: browser.runtime.Port | chrome.runtime.Port; private resolver: any = null; + private rejecter: any = null; private privateKey: Uint8Array = null; private publicKey: Uint8Array = null; private secureSetupResolve: any = null; @@ -137,7 +138,7 @@ export class NativeMessagingBackground { const decrypted = await this.cryptoFunctionService.rsaDecrypt( encrypted, this.privateKey, - EncryptionAlgorithm, + HashAlgorithmForEncryption, ); if (this.validatingFingerprint) { @@ -158,19 +159,10 @@ export class NativeMessagingBackground { this.privateKey = null; this.connected = false; - this.messagingService.send("showDialog", { - title: { key: "nativeMessagingInvalidEncryptionTitle" }, - content: { key: "nativeMessagingInvalidEncryptionDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", + this.rejecter({ + message: "invalidateEncryption", }); - - if (this.resolver) { - this.resolver(message); - } - - break; + return; case "verifyFingerprint": { if (this.sharedSecret == null) { this.validatingFingerprint = true; @@ -181,8 +173,10 @@ export class NativeMessagingBackground { break; } case "wrongUserId": - this.showWrongUserDialog(); - break; + this.rejecter({ + message: "wrongUserId", + }); + return; default: // Ignore since it belongs to another device if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) { @@ -215,26 +209,6 @@ export class NativeMessagingBackground { }); } - showWrongUserDialog() { - this.messagingService.send("showDialog", { - title: { key: "nativeMessagingWrongUserTitle" }, - content: { key: "nativeMessagingWrongUserDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); - } - - showIncorrectUserKeyDialog() { - this.messagingService.send("showDialog", { - title: { key: "nativeMessagingWrongUserKeyTitle" }, - content: { key: "nativeMessagingWrongUserKeyDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); - } - async send(message: Message) { if (!this.connected) { await this.connect(); @@ -260,7 +234,14 @@ export class NativeMessagingBackground { getResponse(): Promise { return new Promise((resolve, reject) => { - this.resolver = resolve; + this.resolver = function (response: any) { + resolve(response); + }; + this.rejecter = function (resp: any) { + reject({ + message: resp, + }); + }; }); } @@ -286,13 +267,7 @@ export class NativeMessagingBackground { this.privateKey = null; this.connected = false; - this.messagingService.send("showDialog", { - title: { key: "nativeMessagingInvalidEncryptionTitle" }, - content: { key: "nativeMessagingInvalidEncryptionDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); + this.rejecter("invalidateEncryption"); } } @@ -311,35 +286,11 @@ export class NativeMessagingBackground { switch (message.command) { case "biometricUnlock": { - if (message.response === "not enabled") { - this.messagingService.send("showDialog", { - title: { key: "biometricsNotEnabledTitle" }, - content: { key: "biometricsNotEnabledDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); - break; - } else if (message.response === "not supported") { - this.messagingService.send("showDialog", { - title: { key: "biometricsNotSupportedTitle" }, - content: { key: "biometricsNotSupportedDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); - break; - } else if (message.response === "not unlocked") { - this.messagingService.send("showDialog", { - title: { key: "biometricsNotUnlockedTitle" }, - content: { key: "biometricsNotUnlockedDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); - break; - } else if (message.response === "canceled") { - break; + if ( + ["not enabled", "not supported", "not unlocked", "canceled"].includes(message.response) + ) { + this.rejecter(message.response); + return; } // Check for initial setup of biometric unlock @@ -374,12 +325,7 @@ export class NativeMessagingBackground { } else { this.logService.error("Unable to verify biometric unlocked userkey"); await this.cryptoService.clearKeys(activeUserId); - this.showIncorrectUserKeyDialog(); - - // Exit early - if (this.resolver) { - this.resolver(message); - } + this.rejecter("userkey wrong"); return; } } else { @@ -387,18 +333,17 @@ export class NativeMessagingBackground { } } catch (e) { this.logService.error("Unable to set key: " + e); - this.messagingService.send("showDialog", { - title: { key: "biometricsFailedTitle" }, - content: { key: "biometricsFailedDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); + this.rejecter("userkey wrong"); + return; + } - // Exit early - if (this.resolver) { - this.resolver(message); - } + // Verify key is correct by attempting to decrypt a secret + try { + await this.cryptoService.getFingerprint(await this.stateService.getUserId()); + } catch (e) { + this.logService.error("Unable to verify key: " + e); + await this.cryptoService.clearKeys(); + this.rejecter("userkey wrong"); return; } diff --git a/apps/browser/src/models/biometricErrors.ts b/apps/browser/src/models/biometricErrors.ts index 822a5c16f42..570c776f563 100644 --- a/apps/browser/src/models/biometricErrors.ts +++ b/apps/browser/src/models/biometricErrors.ts @@ -3,7 +3,15 @@ type BiometricError = { description: string; }; -export type BiometricErrorTypes = "startDesktop" | "desktopIntegrationDisabled"; +export type BiometricErrorTypes = + | "startDesktop" + | "desktopIntegrationDisabled" + | "not enabled" + | "not supported" + | "not unlocked" + | "invalidateEncryption" + | "userkey wrong" + | "wrongUserId"; export const BiometricErrors: Record = { startDesktop: { @@ -14,4 +22,28 @@ export const BiometricErrors: Record = { title: "desktopIntegrationDisabledTitle", description: "desktopIntegrationDisabledDesc", }, + "not enabled": { + title: "biometricsNotEnabledTitle", + description: "biometricsNotEnabledDesc", + }, + "not supported": { + title: "biometricsNotSupportedTitle", + description: "biometricsNotSupportedDesc", + }, + "not unlocked": { + title: "biometricsUnlockNotUnlockedTitle", + description: "biometricsUnlockNotUnlockedDesc", + }, + invalidateEncryption: { + title: "nativeMessagingInvalidEncryptionTitle", + description: "nativeMessagingInvalidEncryptionDesc", + }, + "userkey wrong": { + title: "nativeMessagingWrongUserKeyTitle", + description: "nativeMessagingWrongUserKeyDesc", + }, + wrongUserId: { + title: "biometricsWrongUserTitle", + description: "biometricsWrongUserDesc", + }, }; From cdc82f13b0147202f1e8dd71a5c9870bc60efece Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:36:40 +0100 Subject: [PATCH 38/59] Resolve the seat adjustment issue (#10265) --- .../organizations/adjust-subscription.component.html | 8 +++++--- .../organizations/adjust-subscription.component.ts | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/web/src/app/billing/organizations/adjust-subscription.component.html b/apps/web/src/app/billing/organizations/adjust-subscription.component.html index 9fe8d205407..61197e86d22 100644 --- a/apps/web/src/app/billing/organizations/adjust-subscription.component.html +++ b/apps/web/src/app/billing/organizations/adjust-subscription.component.html @@ -5,8 +5,9 @@ {{ "subscriptionSeats" | i18n }} - {{ "total" | i18n }}: {{ additionalSeatCount || 0 }} × - {{ seatPrice | currency: "$" }} = {{ adjustedSeatTotal | currency: "$" }} / + {{ "total" | i18n }}: + {{ adjustSubscriptionForm.value.newSeatCount || 0 }} × + {{ seatPrice | currency: "$" }} = {{ seatTotalCost | currency: "$" }} / {{ interval | i18n }}
    @@ -43,7 +44,8 @@ step="1" /> - {{ "maxSeatCost" | i18n }}: {{ additionalMaxSeatCount || 0 }} × + {{ "maxSeatCost" | i18n }}: + {{ adjustSubscriptionForm.value.newMaxSeats || 0 }} × {{ seatPrice | currency: "$" }} = {{ maxSeatTotal | currency: "$" }} / {{ interval | i18n }} diff --git a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts index b843c79cb95..c98a6b97c41 100644 --- a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts @@ -99,11 +99,12 @@ export class AdjustSubscription implements OnInit, OnDestroy { : 0; } - get adjustedSeatTotal(): number { - return this.additionalSeatCount * this.seatPrice; + get maxSeatTotal(): number { + return Math.abs((this.adjustSubscriptionForm.value.newMaxSeats ?? 0) * this.seatPrice); } - get maxSeatTotal(): number { - return this.additionalMaxSeatCount * this.seatPrice; + get seatTotalCost(): number { + const totalSeat = Math.abs(this.adjustSubscriptionForm.value.newSeatCount * this.seatPrice); + return totalSeat; } } From 7b508b1ad79547244c2786af5d97173d738dd7ab Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Tue, 13 Aug 2024 08:07:36 -0400 Subject: [PATCH 39/59] [PM-8933] Require userId on setUserKey (#9675) * Updated all sets of user key to pass in userId * Added userId on auth request login. * Fixed tests. * Fixed tests to pass in UserId * Added parameter to tests. * Addressed PR feedback. * Merged main --- .../browser/src/background/main.background.ts | 1 - .../background/nativeMessaging.background.ts | 9 ++++----- apps/cli/src/auth/commands/unlock.command.ts | 2 +- .../services/electron-crypto.service.ts | 2 +- .../src/auth/components/lock.component.ts | 19 +++++++++++++------ .../login-via-auth-request.component.ts | 2 ++ .../auth-request.service.abstraction.ts | 4 ++++ .../auth-request-login.strategy.spec.ts | 4 ++-- .../auth-request-login.strategy.ts | 4 ++-- .../sso-login.strategy.spec.ts | 6 +++--- .../login-strategies/sso-login.strategy.ts | 6 ++++-- .../auth-request/auth-request.service.spec.ts | 16 ++++++++++++---- .../auth-request/auth-request.service.ts | 7 ++++--- .../platform/services/crypto.service.spec.ts | 6 ++++++ .../src/platform/services/crypto.service.ts | 16 +++++++--------- 15 files changed, 65 insertions(+), 39 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 0f124d2c5dd..8fbab335b63 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1067,7 +1067,6 @@ export default class MainBackground { this.messagingService, this.appIdService, this.platformUtilsService, - this.stateService, this.logService, this.authService, this.biometricStateService, diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 19c55f9f696..55374709601 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -9,7 +9,6 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -79,7 +78,6 @@ export class NativeMessagingBackground { private messagingService: MessagingService, private appIdService: AppIdService, private platformUtilsService: PlatformUtilsService, - private stateService: StateService, private logService: LogService, private authService: AuthService, private biometricStateService: BiometricStateService, @@ -214,7 +212,7 @@ export class NativeMessagingBackground { await this.connect(); } - message.userId = await this.stateService.getUserId(); + message.userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; message.timestamp = Date.now(); if (this.platformUtilsService.isSafari()) { @@ -367,13 +365,14 @@ export class NativeMessagingBackground { const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); this.publicKey = publicKey; this.privateKey = privateKey; + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.sendUnencrypted({ command: "setupEncryption", publicKey: Utils.fromBufferToB64(publicKey), - userId: await this.stateService.getUserId(), + userId: userId, }); return new Promise((resolve, reject) => (this.secureSetupResolve = resolve)); @@ -391,7 +390,7 @@ export class NativeMessagingBackground { private async showFingerprintDialog() { const fingerprint = await this.cryptoService.getFingerprint( - await this.stateService.getUserId(), + (await firstValueFrom(this.accountService.activeAccount$))?.id, this.publicKey, ); diff --git a/apps/cli/src/auth/commands/unlock.command.ts b/apps/cli/src/auth/commands/unlock.command.ts index d767ee80b37..f4486ff9667 100644 --- a/apps/cli/src/auth/commands/unlock.command.ts +++ b/apps/cli/src/auth/commands/unlock.command.ts @@ -69,7 +69,7 @@ export class UnlockCommand { } const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey); - await this.cryptoService.setUserKey(userKey); + await this.cryptoService.setUserKey(userKey, userId); if (await this.keyConnectorService.getConvertAccountRequired()) { const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand( diff --git a/apps/desktop/src/platform/services/electron-crypto.service.ts b/apps/desktop/src/platform/services/electron-crypto.service.ts index 10982e7270f..8a6a51f4c01 100644 --- a/apps/desktop/src/platform/services/electron-crypto.service.ts +++ b/apps/desktop/src/platform/services/electron-crypto.service.ts @@ -69,7 +69,7 @@ export class ElectronCryptoService extends CryptoService { await super.clearStoredUserKey(keySuffix, userId); } - protected override async storeAdditionalKeys(key: UserKey, userId?: UserId) { + protected override async storeAdditionalKeys(key: UserKey, userId: UserId) { await super.storeAdditionalKeys(key, userId); const storeBiometricKey = await this.shouldStoreKey(KeySuffixOptions.Biometric, userId); diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts index 28cc7d810d7..b7ebf991e31 100644 --- a/libs/angular/src/auth/components/lock.component.ts +++ b/libs/angular/src/auth/components/lock.component.ts @@ -134,10 +134,13 @@ export class LockComponent implements OnInit, OnDestroy { } await this.biometricStateService.setUserPromptCancelled(); - const userKey = await this.cryptoService.getUserKeyFromStorage(KeySuffixOptions.Biometric); + const userKey = await this.cryptoService.getUserKeyFromStorage( + KeySuffixOptions.Biometric, + this.activeUserId, + ); if (userKey) { - await this.setUserKeyAndContinue(userKey, false); + await this.setUserKeyAndContinue(userKey, this.activeUserId, false); } return !!userKey; @@ -174,7 +177,7 @@ export class LockComponent implements OnInit, OnDestroy { const userKey = await this.pinService.decryptUserKeyWithPin(this.pin, userId); if (userKey) { - await this.setUserKeyAndContinue(userKey); + await this.setUserKeyAndContinue(userKey, userId); return; // successfully unlocked } @@ -257,11 +260,15 @@ export class LockComponent implements OnInit, OnDestroy { const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( response.masterKey, ); - await this.setUserKeyAndContinue(userKey, true); + await this.setUserKeyAndContinue(userKey, userId, true); } - private async setUserKeyAndContinue(key: UserKey, evaluatePasswordAfterUnlock = false) { - await this.cryptoService.setUserKey(key); + private async setUserKeyAndContinue( + key: UserKey, + userId: UserId, + evaluatePasswordAfterUnlock = false, + ) { + await this.cryptoService.setUserKey(key, userId); // Now that we have a decrypted user key in memory, we can check if we // need to establish trust on the current device diff --git a/libs/angular/src/auth/components/login-via-auth-request.component.ts b/libs/angular/src/auth/components/login-via-auth-request.component.ts index 91e815cf783..f6356314f5b 100644 --- a/libs/angular/src/auth/components/login-via-auth-request.component.ts +++ b/libs/angular/src/auth/components/login-via-auth-request.component.ts @@ -386,6 +386,7 @@ export class LoginViaAuthRequestComponent await this.authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash( adminAuthReqResponse, privateKey, + userId, ); } else { // Flow 3: masterPasswordHash is null @@ -393,6 +394,7 @@ export class LoginViaAuthRequestComponent await this.authRequestService.setUserKeyAfterDecryptingSharedUserKey( adminAuthReqResponse, privateKey, + userId, ); } diff --git a/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts b/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts index 7e82045c5f4..aa5f52a8c9c 100644 --- a/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts +++ b/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts @@ -49,19 +49,23 @@ export abstract class AuthRequestServiceAbstraction { * Sets the `UserKey` from an auth request. Auth request must have a `UserKey`. * @param authReqResponse The auth request. * @param authReqPrivateKey The private key corresponding to the public key sent in the auth request. + * @param userId The ID of the user for whose account we will set the key. */ abstract setUserKeyAfterDecryptingSharedUserKey: ( authReqResponse: AuthRequestResponse, authReqPrivateKey: ArrayBuffer, + userId: UserId, ) => Promise; /** * Sets the `MasterKey` and `MasterKeyHash` from an auth request. Auth request must have a `MasterKey` and `MasterKeyHash`. * @param authReqResponse The auth request. * @param authReqPrivateKey The private key corresponding to the public key sent in the auth request. + * @param userId The ID of the user for whose account we will set the keys. */ abstract setKeysAfterDecryptingSharedMasterKeyAndHash: ( authReqResponse: AuthRequestResponse, authReqPrivateKey: ArrayBuffer, + userId: UserId, ) => Promise; /** * Decrypts a `UserKey` from a public key encrypted `UserKey`. diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts index 36c572af852..9e9efa12bab 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts @@ -159,7 +159,7 @@ describe("AuthRequestLoginStrategy", () => { mockUserId, ); expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key); - expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey); + expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey, mockUserId); expect(deviceTrustService.trustDeviceIfRequired).toHaveBeenCalled(); expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey, mockUserId); }); @@ -184,7 +184,7 @@ describe("AuthRequestLoginStrategy", () => { // setMasterKeyEncryptedUserKey, setUserKey, and setPrivateKey should still be called expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key); - expect(cryptoService.setUserKey).toHaveBeenCalledWith(decUserKey); + expect(cryptoService.setUserKey).toHaveBeenCalledWith(decUserKey, mockUserId); expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey, mockUserId); // trustDeviceIfRequired should be called diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts index c8fc066fe0e..9998abb30d3 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts @@ -102,7 +102,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy { await this.cryptoService.setMasterKeyEncryptedUserKey(response.key); if (authRequestCredentials.decryptedUserKey) { - await this.cryptoService.setUserKey(authRequestCredentials.decryptedUserKey); + await this.cryptoService.setUserKey(authRequestCredentials.decryptedUserKey, userId); } else { await this.trySetUserKeyWithMasterKey(userId); @@ -115,7 +115,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy { const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); if (masterKey) { const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey); - await this.cryptoService.setUserKey(userKey); + await this.cryptoService.setUserKey(userKey, userId); } } diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index 82169a19d14..8e28a2c0222 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -252,7 +252,7 @@ describe("SsoLoginStrategy", () => { expect(deviceTrustService.getDeviceKey).toHaveBeenCalledTimes(1); expect(deviceTrustService.decryptUserKeyWithDeviceKey).toHaveBeenCalledTimes(1); expect(cryptoSvcSetUserKeySpy).toHaveBeenCalledTimes(1); - expect(cryptoSvcSetUserKeySpy).toHaveBeenCalledWith(mockUserKey); + expect(cryptoSvcSetUserKeySpy).toHaveBeenCalledWith(mockUserKey, userId); }); it("does not set the user key when deviceKey is missing", async () => { @@ -498,7 +498,7 @@ describe("SsoLoginStrategy", () => { undefined, undefined, ); - expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey); + expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey, userId); }); }); @@ -554,7 +554,7 @@ describe("SsoLoginStrategy", () => { undefined, undefined, ); - expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey); + expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey, userId); }); }); }); diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.ts index 5c979c55599..5ddf7428d24 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.ts @@ -255,6 +255,7 @@ export class SsoLoginStrategy extends LoginStrategy { await this.authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash( adminAuthReqResponse, adminAuthReqStorable.privateKey, + userId, ); } else { // if masterPasswordHash is null, we will always receive authReqResponse.key @@ -262,6 +263,7 @@ export class SsoLoginStrategy extends LoginStrategy { await this.authRequestService.setUserKeyAfterDecryptingSharedUserKey( adminAuthReqResponse, adminAuthReqStorable.privateKey, + userId, ); } @@ -321,7 +323,7 @@ export class SsoLoginStrategy extends LoginStrategy { ); if (userKey) { - await this.cryptoService.setUserKey(userKey); + await this.cryptoService.setUserKey(userKey, userId); } } @@ -337,7 +339,7 @@ export class SsoLoginStrategy extends LoginStrategy { } const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey); - await this.cryptoService.setUserKey(userKey); + await this.cryptoService.setUserKey(userKey, userId); } protected override async setPrivateKey( diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts b/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts index a3b4400588b..885856517b8 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts @@ -133,14 +133,18 @@ describe("AuthRequestService", () => { cryptoService.setUserKey.mockResolvedValueOnce(undefined); // Act - await sut.setUserKeyAfterDecryptingSharedUserKey(mockAuthReqResponse, mockPrivateKey); + await sut.setUserKeyAfterDecryptingSharedUserKey( + mockAuthReqResponse, + mockPrivateKey, + mockUserId, + ); // Assert expect(sut.decryptPubKeyEncryptedUserKey).toBeCalledWith( mockAuthReqResponse.key, mockPrivateKey, ); - expect(cryptoService.setUserKey).toBeCalledWith(mockDecryptedUserKey); + expect(cryptoService.setUserKey).toBeCalledWith(mockDecryptedUserKey, mockUserId); }); }); @@ -169,7 +173,11 @@ describe("AuthRequestService", () => { cryptoService.setUserKey.mockResolvedValueOnce(undefined); // Act - await sut.setKeysAfterDecryptingSharedMasterKeyAndHash(mockAuthReqResponse, mockPrivateKey); + await sut.setKeysAfterDecryptingSharedMasterKeyAndHash( + mockAuthReqResponse, + mockPrivateKey, + mockUserId, + ); // Assert expect(sut.decryptPubKeyEncryptedMasterKeyAndHash).toBeCalledWith( @@ -190,7 +198,7 @@ describe("AuthRequestService", () => { undefined, undefined, ); - expect(cryptoService.setUserKey).toHaveBeenCalledWith(mockDecryptedUserKey); + expect(cryptoService.setUserKey).toHaveBeenCalledWith(mockDecryptedUserKey, mockUserId); }); }); diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.ts b/libs/auth/src/common/services/auth-request/auth-request.service.ts index 028721c5133..68302cae92d 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.ts @@ -126,17 +126,19 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { async setUserKeyAfterDecryptingSharedUserKey( authReqResponse: AuthRequestResponse, authReqPrivateKey: Uint8Array, + userId: UserId, ) { const userKey = await this.decryptPubKeyEncryptedUserKey( authReqResponse.key, authReqPrivateKey, ); - await this.cryptoService.setUserKey(userKey); + await this.cryptoService.setUserKey(userKey, userId); } async setKeysAfterDecryptingSharedMasterKeyAndHash( authReqResponse: AuthRequestResponse, authReqPrivateKey: Uint8Array, + userId: UserId, ) { const { masterKey, masterKeyHash } = await this.decryptPubKeyEncryptedMasterKeyAndHash( authReqResponse.key, @@ -148,11 +150,10 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey); // Set masterKey + masterKeyHash in state after decryption (in case decryption fails) - const userId = (await firstValueFrom(this.accountService.activeAccount$)).id; await this.masterPasswordService.setMasterKey(masterKey, userId); await this.masterPasswordService.setMasterKeyHash(masterKeyHash, userId); - await this.cryptoService.setUserKey(userKey); + await this.cryptoService.setUserKey(userKey, userId); } // Decryption helpers diff --git a/libs/common/src/platform/services/crypto.service.spec.ts b/libs/common/src/platform/services/crypto.service.spec.ts index 1b88922ca54..2386ad13711 100644 --- a/libs/common/src/platform/services/crypto.service.spec.ts +++ b/libs/common/src/platform/services/crypto.service.spec.ts @@ -263,6 +263,12 @@ describe("cryptoService", () => { await expect(cryptoService.setUserKey(null, mockUserId)).rejects.toThrow("No key provided."); }); + it("throws if userId is null", async () => { + await expect(cryptoService.setUserKey(mockUserKey, null)).rejects.toThrow( + "No userId provided.", + ); + }); + describe("Pin Key refresh", () => { const mockPinKeyEncryptedUserKey = new EncString( "2.AAAw2vTUePO+CCyokcIfVw==|DTBNlJ5yVsV2Bsk3UU3H6Q==|YvFBff5gxWqM+UsFB6BKimKxhC32AtjF3IStpU1Ijwg=", diff --git a/libs/common/src/platform/services/crypto.service.ts b/libs/common/src/platform/services/crypto.service.ts index 0fe5268f25e..6d99f920825 100644 --- a/libs/common/src/platform/services/crypto.service.ts +++ b/libs/common/src/platform/services/crypto.service.ts @@ -89,12 +89,16 @@ export class CryptoService implements CryptoServiceAbstraction { ); } - async setUserKey(key: UserKey, userId?: UserId): Promise { + async setUserKey(key: UserKey, userId: UserId): Promise { if (key == null) { throw new Error("No key provided. Lock the user to clear the key"); } + if (userId == null) { + throw new Error("No userId provided."); + } + // Set userId to ensure we have one for the account status update - [userId, key] = await this.stateProvider.setUserState(USER_KEY, key, userId); + await this.stateProvider.setUserState(USER_KEY, key, userId); await this.stateProvider.setUserState(USER_EVER_HAD_USER_KEY, true, userId); await this.storeAdditionalKeys(key, userId); @@ -701,13 +705,7 @@ export class CryptoService implements CryptoServiceAbstraction { * @param key The user key * @param userId The desired user */ - protected async storeAdditionalKeys(key: UserKey, userId?: UserId) { - userId ??= await firstValueFrom(this.stateProvider.activeUserId$); - - if (userId == null) { - throw new Error("Cannot store additional keys, no user Id resolved."); - } - + protected async storeAdditionalKeys(key: UserKey, userId: UserId) { const storeAuto = await this.shouldStoreKey(KeySuffixOptions.Auto, userId); if (storeAuto) { await this.stateService.setUserKeyAutoUnlock(key.keyB64, { userId: userId }); From baf919a4e3dc60ba5aa7563b7cb0689ad5d96ce8 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 13 Aug 2024 07:51:43 -0500 Subject: [PATCH 40/59] [PM-10700] Navigate to View after cipher creation (#10484) * refactor params subscription variable names * refactor param subscription to return a tuple - this is going to be helpful when multiple params are involved * navigate the user back to the vault when a new cipher is created * add unit tests for view-v2 component * prefer replaceUrl to avoid having to pass a query param * Fix grammar of mocking comment --- .../add-edit/add-edit-v2.component.ts | 5 +- .../view-v2/view-v2.component.spec.ts | 107 ++++++++++++++++++ .../vault-v2/view-v2/view-v2.component.ts | 11 +- 3 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts 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 938cc7d8e7c..1c4519f4307 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 @@ -228,7 +228,10 @@ export class AddEditV2Component implements OnInit { return; } - this.location.back(); + await this.router.navigate(["/view-cipher"], { + replaceUrl: true, + queryParams: { cipherId: cipher.id }, + }); } subscribeToParams(): void { diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts new file mode 100644 index 00000000000..b7f3aa2f430 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -0,0 +1,107 @@ +import { ComponentFixture, fakeAsync, flush, TestBed } from "@angular/core/testing"; +import { ActivatedRoute, Router } from "@angular/router"; +import { mock } from "jest-mock-extended"; +import { Subject } from "rxjs"; + +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; + +import { ViewV2Component } from "./view-v2.component"; + +// 'qrcode-parser' is used by `BrowserTotpCaptureService` but is an es6 module that jest can't compile. +// Mock the entire module here to prevent jest from throwing an error. I wasn't able to find a way to mock the +// `BrowserTotpCaptureService` where jest would not load the file in the first place. +jest.mock("qrcode-parser", () => {}); + +describe("ViewV2Component", () => { + let component: ViewV2Component; + let fixture: ComponentFixture; + const params$ = new Subject(); + const mockNavigate = jest.fn(); + + const mockCipher = { + id: "122-333-444", + type: CipherType.Login, + }; + + const mockCipherService = { + get: jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }), + getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}), + }; + + beforeEach(async () => { + mockNavigate.mockClear(); + + await TestBed.configureTestingModule({ + imports: [ViewV2Component], + providers: [ + { provide: Router, useValue: { navigate: mockNavigate } }, + { provide: CipherService, useValue: mockCipherService }, + { provide: LogService, useValue: mock() }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: ConfigService, useValue: mock() }, + { provide: ActivatedRoute, useValue: { queryParams: params$ } }, + { + provide: I18nService, + useValue: { + t: (key: string, ...rest: string[]) => { + if (rest?.length) { + return `${key} ${rest.join(" ")}`; + } + return key; + }, + }, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ViewV2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe("queryParams", () => { + it("loads an existing cipher", fakeAsync(() => { + params$.next({ cipherId: "122-333-444" }); + + flush(); // Resolve all promises + + expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444"); + expect(component.cipher).toEqual(mockCipher); + })); + + it("sets the correct header text", fakeAsync(() => { + // Set header text for a login + mockCipher.type = CipherType.Login; + params$.next({ cipherId: mockCipher.id }); + flush(); // Resolve all promises + + expect(component.headerText).toEqual("viewItemHeader typelogin"); + + // Set header text for a card + mockCipher.type = CipherType.Card; + params$.next({ cipherId: mockCipher.id }); + flush(); // Resolve all promises + + expect(component.headerText).toEqual("viewItemHeader typecard"); + + // Set header text for an identity + mockCipher.type = CipherType.Identity; + params$.next({ cipherId: mockCipher.id }); + flush(); // Resolve all promises + + expect(component.headerText).toEqual("viewItemHeader typeidentity"); + + // Set header text for a secure note + mockCipher.type = CipherType.SecureNote; + params$.next({ cipherId: mockCipher.id }); + flush(); // Resolve all promises + + expect(component.headerText).toEqual("viewItemHeader note"); + })); + }); +}); diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index 8039ac18651..ccc2658e59e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -54,7 +54,6 @@ import { PopupPageComponent } from "./../../../../../platform/popup/layout/popup }) export class ViewV2Component { headerText: string; - cipherId: string; cipher: CipherView; organization$: Observable; folder$: Observable; @@ -75,14 +74,14 @@ export class ViewV2Component { subscribeToParams(): void { this.route.queryParams .pipe( - switchMap((param) => { - return this.getCipherData(param.cipherId); + switchMap(async (params): Promise => { + return await this.getCipherData(params.cipherId); }), takeUntilDestroyed(), ) - .subscribe((data) => { - this.cipher = data; - this.headerText = this.setHeader(data.type); + .subscribe((cipher) => { + this.cipher = cipher; + this.headerText = this.setHeader(cipher.type); }); } From c3c0c727920391fe9fdb503a01dfe5b980422d4b Mon Sep 17 00:00:00 2001 From: Matt Bishop Date: Tue, 13 Aug 2024 09:08:09 -0400 Subject: [PATCH 41/59] Move Chromatic to more secure execution model (#10449) * Move Chromatic to more secure execution model * Remove a permission we don't need here --- .github/workflows/chromatic.yml | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index ab08d509b37..f422c3560e6 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -1,25 +1,35 @@ ---- name: Chromatic on: push: - branches-ignore: - - 'renovate/**' - paths-ignore: - - '.github/workflows/**' + branches: + - "main" + - "rc" + - "hotfix-rc" + pull_request_target: + types: [opened, synchronize] jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + chromatic: name: Chromatic runs-on: ubuntu-22.04 + needs: check-run + permissions: + contents: read + pull-requests: write steps: - - name: Checkout repo + - name: Check out repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: + ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - - name: Get Node Version + - name: Get Node version id: retrieve-node-version run: | NODE_NVMRC=$(cat .nvmrc) @@ -31,7 +41,7 @@ jobs: with: node-version: ${{ steps.retrieve-node-version.outputs.node_version }} - - name: Cache npm + - name: Cache NPM id: npm-cache uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 with: @@ -41,7 +51,7 @@ jobs: - name: Install Node dependencies run: npm ci - # Manual build the storybook to resolve a chromatic/storybook bug related to TurboSnap + # Manually build the Storybook to resolve a bug related to TurboSnap - name: Build Storybook run: npm run build-storybook:ci From 127811166b0ef11a37e995ade6d4a0d5e9020650 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 13 Aug 2024 15:43:56 +0200 Subject: [PATCH 42/59] Revert "[PM-8316] Fix account switcher on logged out account on startup (#10342)" (#10498) This reverts commit 2665afffdda539dd03a1df9a6c43b7a6c8dff2fe. --- apps/browser/src/background/main.background.ts | 12 ------------ .../app/layout/account-switcher.component.ts | 18 ------------------ 2 files changed, 30 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 8fbab335b63..c5f36dfe68b 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1233,18 +1233,6 @@ export default class MainBackground { ); } - // If the user is logged out, switch to the next account - const active = await firstValueFrom(this.accountService.activeAccount$); - if (active == null) { - return; - } - const authStatus = await firstValueFrom( - this.authService.authStatuses$.pipe(map((statuses) => statuses[active.id])), - ); - if (authStatus === AuthenticationStatus.LoggedOut) { - const nextUpAccount = await firstValueFrom(this.accountService.nextUpAccount$); - await this.switchAccount(nextUpAccount?.id); - } await this.initOverlayAndTabsBackground(); return new Promise((resolve) => { diff --git a/apps/desktop/src/app/layout/account-switcher.component.ts b/apps/desktop/src/app/layout/account-switcher.component.ts index 02d98512609..f641d801b8d 100644 --- a/apps/desktop/src/app/layout/account-switcher.component.ts +++ b/apps/desktop/src/app/layout/account-switcher.component.ts @@ -151,24 +151,6 @@ export class AccountSwitcherComponent { ); } - async ngOnInit() { - const active = await firstValueFrom(this.accountService.activeAccount$); - if (active == null) { - return; - } - const authStatus = await firstValueFrom( - this.authService.authStatuses$.pipe(map((statuses) => statuses[active.id])), - ); - if (authStatus === AuthenticationStatus.LoggedOut) { - const nextUpAccount = await firstValueFrom(this.accountService.nextUpAccount$); - if (nextUpAccount != null) { - await this.switch(nextUpAccount.id); - } else { - await this.addAccount(); - } - } - } - toggle() { this.isOpen = !this.isOpen; } From 5ce5f836e207700369a4dccaf82b4993018cfa09 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 13 Aug 2024 09:12:48 -0500 Subject: [PATCH 43/59] Fix failing test on main (#10499) * mock popuproutercache to fix failing test * add `OnInit` implementation to fix linting * Revert "add `OnInit` implementation to fix linting" This reverts commit 50adcacba35dfba69b87e8e2e2e206fa905a8705. --- .../components/vault-v2/view-v2/view-v2.component.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index b7f3aa2f430..15693ff18d0 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -10,6 +10,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; + import { ViewV2Component } from "./view-v2.component"; // 'qrcode-parser' is used by `BrowserTotpCaptureService` but is an es6 module that jest can't compile. @@ -44,6 +46,7 @@ describe("ViewV2Component", () => { { provide: LogService, useValue: mock() }, { provide: PlatformUtilsService, useValue: mock() }, { provide: ConfigService, useValue: mock() }, + { provide: PopupRouterCacheService, useValue: mock() }, { provide: ActivatedRoute, useValue: { queryParams: params$ } }, { provide: I18nService, From 43da67ee511aa83b45dfa93b223ea9d13f808d4a Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:12:59 -0400 Subject: [PATCH 44/59] Fix nativeMessaging background by replacing removed state service usage with account service for user id retrieval (#10501) --- apps/browser/src/background/nativeMessaging.background.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 55374709601..777af9538b0 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -337,7 +337,8 @@ export class NativeMessagingBackground { // Verify key is correct by attempting to decrypt a secret try { - await this.cryptoService.getFingerprint(await this.stateService.getUserId()); + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + await this.cryptoService.getFingerprint(userId); } catch (e) { this.logService.error("Unable to verify key: " + e); await this.cryptoService.clearKeys(); From 471dd3bd7b23ac0ceb745456f27565540cc42188 Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:45:41 -0500 Subject: [PATCH 45/59] [PM-10294] Remove FC v1 from Clients (#10422) * chore: remove fc v1 from org.canEditAnyCollection and update callers, refs PM-10294 * chore: remove fc v1 from collectionView.canEdit and update callers, refs PM-10294 * chore: remove fc v1 from organization.canEditAllCiphers and update callers, refs PM-10294 * chore: remove fc v1 from canDeleteAnyCollection, collection views, update callers, refs PM-10294 * chore: remove fc v1 from canEditUser/GroupAccess, refs PM-10294 * chore: remove fc v1 from canViewCollectionInfo, refs PM-10294 * chore: remove fc v1 from account component, refs PM-10294 * fix: remove fc v1 from collections component, refs PM-10294 * fix: update vault-items component, refs PM-10294 * fix: remove fc v1 from collection-dialog and collections components, refs PM-10294 * chore: remove ConfigService from group-add-edit and account components, refs PM-10294 * chore: change canEditAnyCollection to getter and update callers, refs PM-10294 * chore: change canEditUnmanagedCollections to getter and update callers, refs PM-10294 * chore: change canDeleteAnyCollection to getter and update callers, refs PM-10294 * chore: remove deprecated observable and update comments with v1, refs PM-10294 * chore: remove ununsed ConfigService from collection-dialog component, refs PM-10294 * chore: remove final fc v1 ref for vault-collection-row, refs PM-10294 --- .../manage/group-add-edit.component.ts | 43 +--- .../member-dialog/member-dialog.component.ts | 101 +++----- .../settings/account.component.html | 2 +- .../settings/account.component.ts | 7 - .../collection-dialog.component.html | 10 +- .../collection-dialog.component.ts | 161 ++++++------ .../vault-collection-row.component.ts | 13 +- .../vault-items/vault-items.component.html | 1 - .../vault-items/vault-items.component.ts | 43 +--- .../vault/core/views/collection-admin.view.ts | 41 +--- .../bulk-delete-dialog.component.ts | 23 +- .../collections.component.html | 8 +- .../individual-vault/collections.component.ts | 8 +- .../vault-header/vault-header.component.ts | 9 +- .../individual-vault/vault.component.html | 1 - .../vault/individual-vault/vault.component.ts | 38 +-- .../app/vault/org-vault/add-edit.component.ts | 26 +- .../vault/org-vault/attachments.component.ts | 34 +-- .../vault/org-vault/collections.component.ts | 10 +- .../vault-header/vault-header.component.ts | 13 +- .../app/vault/org-vault/vault.component.html | 40 +-- .../app/vault/org-vault/vault.component.ts | 231 ++++-------------- .../components/collections.component.ts | 11 +- .../vault/components/add-edit.component.ts | 19 +- .../models/domain/organization.ts | 28 +-- .../src/vault/models/view/collection.view.ts | 39 +-- .../assign-collections.component.ts | 11 +- 27 files changed, 254 insertions(+), 717 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index 8df770686f4..c12d133f37b 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -19,9 +19,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -96,9 +94,6 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { private organization$ = this.organizationService .get$(this.organizationId) .pipe(shareReplay({ refCount: true })); - private flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollectionsV1, - ); protected PermissionMode = PermissionMode; protected ResultType = GroupAddEditDialogResultType; @@ -179,27 +174,19 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { shareReplay({ refCount: true, bufferSize: 1 }), ); - protected allowAdminAccessToAllCollectionItems$ = combineLatest([ - this.organization$, - this.flexibleCollectionsV1Enabled$, - ]).pipe( - map(([organization, flexibleCollectionsV1Enabled]) => { - if (!flexibleCollectionsV1Enabled) { - return true; - } - + protected allowAdminAccessToAllCollectionItems$ = this.organization$.pipe( + map((organization) => { return organization.allowAdminAccessToAllCollectionItems; }), ); protected canAssignAccessToAnyCollection$ = combineLatest([ this.organization$, - this.flexibleCollectionsV1Enabled$, this.allowAdminAccessToAllCollectionItems$, ]).pipe( map( - ([org, flexibleCollectionsV1Enabled, allowAdminAccessToAllCollectionItems]) => - org.canEditAnyCollection(flexibleCollectionsV1Enabled) || + ([org, allowAdminAccessToAllCollectionItems]) => + org.canEditAnyCollection || // Manage Groups custom permission cannot edit any collection but they can assign access from this dialog // if permitted by collection management settings (org.permissions.manageGroups && allowAdminAccessToAllCollectionItems), @@ -224,7 +211,6 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { private changeDetectorRef: ChangeDetectorRef, private dialogService: DialogService, private organizationService: OrganizationService, - private configService: ConfigService, private accountService: AccountService, private collectionAdminService: CollectionAdminService, ) { @@ -242,27 +228,13 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { this.cannotAddSelfToGroup$, this.accountService.activeAccount$, this.organization$, - this.flexibleCollectionsV1Enabled$, ]) .pipe(takeUntil(this.destroy$)) .subscribe( - ([ - collections, - members, - group, - restrictGroupAccess, - activeAccount, - organization, - flexibleCollectionsV1Enabled, - ]) => { + ([collections, members, group, restrictGroupAccess, activeAccount, organization]) => { this.members = members; this.group = group; - this.collections = mapToAccessItemViews( - collections, - organization, - flexibleCollectionsV1Enabled, - group, - ); + this.collections = mapToAccessItemViews(collections, organization, group); if (this.group != undefined) { // Must detect changes so that AccessSelector @Inputs() are aware of the latest @@ -384,7 +356,6 @@ function mapToAccessSelections(group: GroupView, items: AccessItemView[]): Acces function mapToAccessItemViews( collections: CollectionAdminView[], organization: Organization, - flexibleCollectionsV1Enabled: boolean, group?: GroupView, ): AccessItemView[] { return ( @@ -396,7 +367,7 @@ function mapToAccessItemViews( type: AccessItemType.Collection, labelName: c.name, listName: c.name, - readonly: !c.canEditGroupAccess(organization, flexibleCollectionsV1Enabled), + readonly: !c.canEditGroupAccess(organization), readonlyPermission: accessSelection ? convertToPermission(accessSelection) : undefined, }; }) diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 81830d12138..ef36f2b80ba 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -23,8 +23,6 @@ import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permi import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; @@ -143,7 +141,6 @@ export class MemberDialogComponent implements OnDestroy { private userService: UserAdminService, private organizationUserService: OrganizationUserService, private dialogService: DialogService, - private configService: ConfigService, private accountService: AccountService, organizationService: OrganizationService, ) { @@ -174,15 +171,8 @@ export class MemberDialogComponent implements OnDestroy { ? this.userService.get(this.params.organizationId, this.params.organizationUserId) : of(null); - this.allowAdminAccessToAllCollectionItems$ = combineLatest([ - this.organization$, - this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1), - ]).pipe( - map(([organization, flexibleCollectionsV1Enabled]) => { - if (!flexibleCollectionsV1Enabled) { - return true; - } - + this.allowAdminAccessToAllCollectionItems$ = this.organization$.pipe( + map((organization) => { return organization.allowAdminAccessToAllCollectionItems; }), ); @@ -208,18 +198,13 @@ export class MemberDialogComponent implements OnDestroy { } }); - const flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollectionsV1, - ); - this.canAssignAccessToAnyCollection$ = combineLatest([ this.organization$, - flexibleCollectionsV1Enabled$, this.allowAdminAccessToAllCollectionItems$, ]).pipe( map( - ([org, flexibleCollectionsV1Enabled, allowAdminAccessToAllCollectionItems]) => - org.canEditAnyCollection(flexibleCollectionsV1Enabled) || + ([org, allowAdminAccessToAllCollectionItems]) => + org.canEditAnyCollection || // Manage Users custom permission cannot edit any collection but they can assign access from this dialog // if permitted by collection management settings (org.permissions.manageUsers && allowAdminAccessToAllCollectionItems), @@ -231,49 +216,39 @@ export class MemberDialogComponent implements OnDestroy { collections: this.collectionAdminService.getAll(this.params.organizationId), userDetails: userDetails$, groups: groups$, - flexibleCollectionsV1Enabled: flexibleCollectionsV1Enabled$, }) .pipe(takeUntil(this.destroy$)) - .subscribe( - ({ organization, collections, userDetails, groups, flexibleCollectionsV1Enabled }) => { - this.setFormValidators(organization); + .subscribe(({ organization, collections, userDetails, groups }) => { + this.setFormValidators(organization); - // Groups tab: populate available groups - this.groupAccessItems = [].concat( - groups.map((g) => mapGroupToAccessItemView(g)), + // Groups tab: populate available groups + this.groupAccessItems = [].concat( + groups.map((g) => mapGroupToAccessItemView(g)), + ); + + // Collections tab: Populate all available collections (including current user access where applicable) + this.collectionAccessItems = collections + .map((c) => + mapCollectionToAccessItemView( + c, + organization, + userDetails == null + ? undefined + : c.users.find((access) => access.id === userDetails.id), + ), + ) + // But remove collections that we can't assign access to, unless the user is already assigned + .filter( + (item) => + !item.readonly || userDetails?.collections.some((access) => access.id == item.id), ); - // Collections tab: Populate all available collections (including current user access where applicable) - this.collectionAccessItems = collections - .map((c) => - mapCollectionToAccessItemView( - c, - organization, - flexibleCollectionsV1Enabled, - userDetails == null - ? undefined - : c.users.find((access) => access.id === userDetails.id), - ), - ) - // But remove collections that we can't assign access to, unless the user is already assigned - .filter( - (item) => - !item.readonly || userDetails?.collections.some((access) => access.id == item.id), - ); + if (userDetails != null) { + this.loadOrganizationUser(userDetails, groups, collections, organization); + } - if (userDetails != null) { - this.loadOrganizationUser( - userDetails, - groups, - collections, - organization, - flexibleCollectionsV1Enabled, - ); - } - - this.loading = false; - }, - ); + this.loading = false; + }); } private setFormValidators(organization: Organization) { @@ -297,7 +272,6 @@ export class MemberDialogComponent implements OnDestroy { groups: GroupView[], collections: CollectionAdminView[], organization: Organization, - flexibleCollectionsV1Enabled: boolean, ) { if (!userDetails) { throw new Error("Could not find user to edit."); @@ -341,13 +315,7 @@ export class MemberDialogComponent implements OnDestroy { // Populate additional collection access via groups (rendered as separate rows from user access) this.collectionAccessItems = this.collectionAccessItems.concat( collectionsFromGroups.map(({ collection, accessSelection, group }) => - mapCollectionToAccessItemView( - collection, - organization, - flexibleCollectionsV1Enabled, - accessSelection, - group, - ), + mapCollectionToAccessItemView(collection, organization, accessSelection, group), ), ); @@ -621,7 +589,6 @@ export class MemberDialogComponent implements OnDestroy { function mapCollectionToAccessItemView( collection: CollectionAdminView, organization: Organization, - flexibleCollectionsV1Enabled: boolean, accessSelection?: CollectionAccessSelectionView, group?: GroupView, ): AccessItemView { @@ -630,9 +597,7 @@ function mapCollectionToAccessItemView( id: group ? `${collection.id}-${group.id}` : collection.id, labelName: collection.name, listName: collection.name, - readonly: - group !== undefined || - !collection.canEditUserAccess(organization, flexibleCollectionsV1Enabled), + readonly: group !== undefined || !collection.canEditUserAccess(organization), readonlyPermission: accessSelection ? convertToPermission(accessSelection) : undefined, viaGroupName: group?.name, }; diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.html b/apps/web/src/app/admin-console/organizations/settings/account.component.html index f453546fcad..af605dfd273 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.html +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.html @@ -56,7 +56,7 @@ >

    {{ "collectionManagement" | i18n }}

    {{ "collectionManagementDesc" | i18n }}

    - + {{ "allowAdminAccessToAllCollectionItemsDesc" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index c53a4991d5e..7cfbee166e3 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -10,8 +10,6 @@ import { OrganizationCollectionManagementUpdateRequest } from "@bitwarden/common import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { OrganizationUpdateRequest } from "@bitwarden/common/admin-console/models/request/organization-update.request"; import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -40,10 +38,6 @@ export class AccountComponent implements OnInit, OnDestroy { org: OrganizationResponse; taxFormPromise: Promise; - flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollectionsV1, - ); - // FormGroup validators taken from server Organization domain object protected formGroup = this.formBuilder.group({ orgName: this.formBuilder.control( @@ -83,7 +77,6 @@ export class AccountComponent implements OnInit, OnDestroy { private organizationApiService: OrganizationApiServiceAbstraction, private dialogService: DialogService, private formBuilder: FormBuilder, - private configService: ConfigService, ) {} async ngOnInit() { diff --git a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.html b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.html index 0640681cf44..fbe0649c7aa 100644 --- a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.html +++ b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.html @@ -80,13 +80,9 @@ {{ "grantCollectionAccessMembersOnly" | i18n }} - {{ " " + ("adminCollectionAccess" | i18n) }} + + {{ " " + ("adminCollectionAccess" | i18n) }} +
    (); protected organizations$: Observable; @@ -113,7 +107,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private organizationUserService: OrganizationUserService, - private configService: ConfigService, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, ) { @@ -163,95 +156,90 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { groups: groups$, // Collection(s) needed to map readonlypermission for (potential) access selector disabled state users: this.organizationUserService.getAllUsers(orgId, { includeCollections: true }), - flexibleCollectionsV1: this.flexibleCollectionsV1Enabled$, }) .pipe(takeUntil(this.formGroup.controls.selectedOrg.valueChanges), takeUntil(this.destroy$)) - .subscribe( - ({ organization, collections: allCollections, groups, users, flexibleCollectionsV1 }) => { - this.organization = organization; - this.accessItems = [].concat( - groups.map((group) => mapGroupToAccessItemView(group, this.collectionId)), - users.data.map((user) => mapUserToAccessItemView(user, this.collectionId)), - ); + .subscribe(({ organization, collections: allCollections, groups, users }) => { + this.organization = organization; + this.accessItems = [].concat( + groups.map((group) => mapGroupToAccessItemView(group, this.collectionId)), + users.data.map((user) => mapUserToAccessItemView(user, this.collectionId)), + ); - // Force change detection to update the access selector's items - this.changeDetectorRef.detectChanges(); + // Force change detection to update the access selector's items + this.changeDetectorRef.detectChanges(); - this.nestOptions = this.params.limitNestedCollections - ? allCollections.filter((c) => c.manage) - : allCollections; + this.nestOptions = this.params.limitNestedCollections + ? allCollections.filter((c) => c.manage) + : allCollections; - if (this.params.collectionId) { - this.collection = allCollections.find((c) => c.id === this.collectionId); - // Ensure we don't allow nesting the current collection within itself - this.nestOptions = this.nestOptions.filter((c) => c.id !== this.collectionId); + if (this.params.collectionId) { + this.collection = allCollections.find((c) => c.id === this.collectionId); + // Ensure we don't allow nesting the current collection within itself + this.nestOptions = this.nestOptions.filter((c) => c.id !== this.collectionId); - if (!this.collection) { - throw new Error("Could not find collection to edit."); - } - - // Parse the name to find its parent name - const { name, parent: parentName } = parseName(this.collection); - - // Determine if the user can see/select the parent collection - if (parentName !== undefined) { - if ( - this.organization.canViewAllCollections && - !allCollections.find((c) => c.name === parentName) - ) { - // The user can view all collections, but the parent was not found -> assume it has been deleted - this.deletedParentName = parentName; - } else if (!this.nestOptions.find((c) => c.name === parentName)) { - // We cannot find the current parent collection in our list of options, so add a placeholder - this.nestOptions.unshift({ name: parentName } as CollectionView); - } - } - - const accessSelections = mapToAccessSelections(this.collection); - this.formGroup.patchValue({ - name, - externalId: this.collection.externalId, - parent: parentName, - access: accessSelections, - }); - this.showDeleteButton = - !this.dialogReadonly && - this.collection.canDelete(organization, flexibleCollectionsV1); - } else { - const parent = this.nestOptions.find((c) => c.id === this.params.parentCollectionId); - const currentOrgUserId = users.data.find( - (u) => u.userId === this.organization?.userId, - )?.id; - const initialSelection: AccessItemValue[] = - currentOrgUserId !== undefined - ? [ - { - id: currentOrgUserId, - type: AccessItemType.Member, - permission: CollectionPermission.Manage, - }, - ] - : []; - - this.formGroup.patchValue({ - parent: parent?.name ?? undefined, - access: initialSelection, - }); + if (!this.collection) { + throw new Error("Could not find collection to edit."); } - if (flexibleCollectionsV1 && !organization.allowAdminAccessToAllCollectionItems) { - this.formGroup.controls.access.addValidators(validateCanManagePermission); - } else { - this.formGroup.controls.access.removeValidators(validateCanManagePermission); + // Parse the name to find its parent name + const { name, parent: parentName } = parseName(this.collection); + + // Determine if the user can see/select the parent collection + if (parentName !== undefined) { + if ( + this.organization.canViewAllCollections && + !allCollections.find((c) => c.name === parentName) + ) { + // The user can view all collections, but the parent was not found -> assume it has been deleted + this.deletedParentName = parentName; + } else if (!this.nestOptions.find((c) => c.name === parentName)) { + // We cannot find the current parent collection in our list of options, so add a placeholder + this.nestOptions.unshift({ name: parentName } as CollectionView); + } } - this.formGroup.controls.access.updateValueAndValidity(); - this.handleFormGroupReadonly(this.dialogReadonly); + const accessSelections = mapToAccessSelections(this.collection); + this.formGroup.patchValue({ + name, + externalId: this.collection.externalId, + parent: parentName, + access: accessSelections, + }); + this.showDeleteButton = !this.dialogReadonly && this.collection.canDelete(organization); + } else { + const parent = this.nestOptions.find((c) => c.id === this.params.parentCollectionId); + const currentOrgUserId = users.data.find( + (u) => u.userId === this.organization?.userId, + )?.id; + const initialSelection: AccessItemValue[] = + currentOrgUserId !== undefined + ? [ + { + id: currentOrgUserId, + type: AccessItemType.Member, + permission: CollectionPermission.Manage, + }, + ] + : []; - this.loading = false; - this.showAddAccessWarning = this.handleAddAccessWarning(flexibleCollectionsV1); - }, - ); + this.formGroup.patchValue({ + parent: parent?.name ?? undefined, + access: initialSelection, + }); + } + + if (!organization.allowAdminAccessToAllCollectionItems) { + this.formGroup.controls.access.addValidators(validateCanManagePermission); + } else { + this.formGroup.controls.access.removeValidators(validateCanManagePermission); + } + this.formGroup.controls.access.updateValueAndValidity(); + + this.handleFormGroupReadonly(this.dialogReadonly); + + this.loading = false; + this.showAddAccessWarning = this.handleAddAccessWarning(); + }); } protected get collectionId() { @@ -361,9 +349,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { this.destroy$.complete(); } - private handleAddAccessWarning(flexibleCollectionsV1: boolean): boolean { + private handleAddAccessWarning(): boolean { if ( - flexibleCollectionsV1 && !this.organization?.allowAdminAccessToAllCollectionItems && this.params.isAddAccessCollection ) { diff --git a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts index 873bdd3e1a0..b5f910cd1a0 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts @@ -34,7 +34,6 @@ export class VaultCollectionRowComponent { @Input() organizations: Organization[]; @Input() groups: GroupView[]; @Input() showPermissionsColumn: boolean; - @Input() flexibleCollectionsV1Enabled: boolean; @Input() restrictProviderAccess: boolean; @Output() onEvent = new EventEmitter(); @@ -57,10 +56,6 @@ export class VaultCollectionRowComponent { } get showAddAccess() { - if (!this.flexibleCollectionsV1Enabled) { - return false; - } - if (this.collection.id == Unassigned) { return false; } @@ -71,7 +66,7 @@ export class VaultCollectionRowComponent { return ( !this.organization?.allowAdminAccessToAllCollectionItems && this.collection.unmanaged && - this.organization?.canEditUnmanagedCollections() + this.organization?.canEditUnmanagedCollections ); } @@ -114,10 +109,6 @@ export class VaultCollectionRowComponent { } protected get showCheckbox() { - if (this.flexibleCollectionsV1Enabled) { - return this.collection?.id !== Unassigned; - } - - return this.canDeleteCollection; + return this.collection?.id !== Unassigned; } } 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 a4f41d25078..2f294a758db 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 @@ -113,7 +113,6 @@ [canDeleteCollection]="canDeleteCollection(item.collection)" [canEditCollection]="canEditCollection(item.collection)" [canViewCollectionInfo]="canViewCollectionInfo(item.collection)" - [flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled" [restrictProviderAccess]="restrictProviderAccess" [checked]="selection.isSelected(item)" (checkedToggled)="selection.toggle(item)" 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 bfb30f3f769..2709091b0c8 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 @@ -44,7 +44,6 @@ export class VaultItemsComponent { @Input() showBulkAddToCollections = false; @Input() showPermissionsColumn = false; @Input() viewingOrgVault: boolean; - @Input({ required: true }) flexibleCollectionsV1Enabled = false; @Input() addAccessStatus: number; @Input() addAccessToggle: boolean; @Input() restrictProviderAccess: boolean; @@ -120,7 +119,7 @@ export class VaultItemsComponent { const organization = this.allOrganizations.find((o) => o.id === collection.organizationId); - return collection.canEdit(organization, this.flexibleCollectionsV1Enabled); + return collection.canEdit(organization); } protected canDeleteCollection(collection: CollectionView): boolean { @@ -131,12 +130,12 @@ export class VaultItemsComponent { const organization = this.allOrganizations.find((o) => o.id === collection.organizationId); - return collection.canDelete(organization, this.flexibleCollectionsV1Enabled); + return collection.canDelete(organization); } protected canViewCollectionInfo(collection: CollectionView) { const organization = this.allOrganizations.find((o) => o.id === collection.organizationId); - return collection.canViewCollectionInfo(organization, this.flexibleCollectionsV1Enabled); + return collection.canViewCollectionInfo(organization); } protected toggleAll() { @@ -214,11 +213,7 @@ export class VaultItemsComponent { const organization = this.allOrganizations.find((o) => o.id === cipher.organizationId); return ( - (organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) && - this.viewingOrgVault) || + (organization.canEditAllCiphers(this.restrictProviderAccess) && this.viewingOrgVault) || cipher.edit ); } @@ -230,21 +225,12 @@ export class VaultItemsComponent { this.selection.clear(); - if (this.flexibleCollectionsV1Enabled) { - // Every item except for the Unassigned collection is selectable, individual bulk actions check the user's permission - this.editableItems = items.filter( - (item) => - item.cipher !== undefined || - (item.collection !== undefined && item.collection.id !== Unassigned), - ); - } else { - // only collections the user can delete are selectable - this.editableItems = items.filter( - (item) => - item.cipher !== undefined || - (item.collection !== undefined && this.canDeleteCollection(item.collection)), - ); - } + // Every item except for the Unassigned collection is selectable, individual bulk actions check the user's permission + this.editableItems = items.filter( + (item) => + item.cipher !== undefined || + (item.collection !== undefined && item.collection.id !== Unassigned), + ); this.dataSource.data = items; } @@ -293,10 +279,7 @@ export class VaultItemsComponent { const organization = this.allOrganizations.find((o) => o.id === orgId); const canEditOrManageAllCiphers = - organization?.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) && this.viewingOrgVault; + organization?.canEditAllCiphers(this.restrictProviderAccess) && this.viewingOrgVault; const collectionNotSelected = this.selection.selected.filter((item) => item.collection).length === 0; @@ -317,9 +300,7 @@ export class VaultItemsComponent { const canEditOrManageAllCiphers = organizations.length > 0 && - organizations.every((org) => - org?.canEditAllCiphers(this.flexibleCollectionsV1Enabled, this.restrictProviderAccess), - ); + organizations.every((org) => org?.canEditAllCiphers(this.restrictProviderAccess)); const canDeleteCollections = this.selection.selected .filter((item) => item.collection) diff --git a/apps/web/src/app/vault/core/views/collection-admin.view.ts b/apps/web/src/app/vault/core/views/collection-admin.view.ts index 6e842023d33..10f894505c9 100644 --- a/apps/web/src/app/vault/core/views/collection-admin.view.ts +++ b/apps/web/src/app/vault/core/views/collection-admin.view.ts @@ -41,61 +41,44 @@ export class CollectionAdminView extends CollectionView { /** * Returns true if the user can edit a collection (including user and group access) from the Admin Console. */ - override canEdit(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean { + override canEdit(org: Organization): boolean { return ( - org?.canEditAnyCollection(flexibleCollectionsV1Enabled) || - (flexibleCollectionsV1Enabled && this.unmanaged && org?.canEditUnmanagedCollections()) || - super.canEdit(org, flexibleCollectionsV1Enabled) + org?.canEditAnyCollection || + (this.unmanaged && org?.canEditUnmanagedCollections) || + super.canEdit(org) ); } /** * Returns true if the user can delete a collection from the Admin Console. */ - override canDelete(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean { - return ( - org?.canDeleteAnyCollection(flexibleCollectionsV1Enabled) || - super.canDelete(org, flexibleCollectionsV1Enabled) - ); + override canDelete(org: Organization): boolean { + return org?.canDeleteAnyCollection || super.canDelete(org); } /** * Whether the user can modify user access to this collection */ - canEditUserAccess(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean { - const allowAdminAccessToAllCollectionItems = - !flexibleCollectionsV1Enabled || org.allowAdminAccessToAllCollectionItems; - + canEditUserAccess(org: Organization): boolean { return ( - (org.permissions.manageUsers && allowAdminAccessToAllCollectionItems) || - this.canEdit(org, flexibleCollectionsV1Enabled) + (org.permissions.manageUsers && org.allowAdminAccessToAllCollectionItems) || this.canEdit(org) ); } /** * Whether the user can modify group access to this collection */ - canEditGroupAccess(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean { - const allowAdminAccessToAllCollectionItems = - !flexibleCollectionsV1Enabled || org.allowAdminAccessToAllCollectionItems; - + canEditGroupAccess(org: Organization): boolean { return ( - (org.permissions.manageGroups && allowAdminAccessToAllCollectionItems) || - this.canEdit(org, flexibleCollectionsV1Enabled) + (org.permissions.manageGroups && org.allowAdminAccessToAllCollectionItems) || + this.canEdit(org) ); } /** * Returns true if the user can view collection info and access in a read-only state from the Admin Console */ - override canViewCollectionInfo( - org: Organization | undefined, - flexibleCollectionsV1Enabled: boolean, - ): boolean { - if (!flexibleCollectionsV1Enabled) { - return false; - } - + override canViewCollectionInfo(org: Organization | undefined): boolean { if (this.isUnassignedCollection) { return false; } diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index c0de8c6bd22..617628a0b37 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -54,10 +54,6 @@ export class BulkDeleteDialogComponent { collections: CollectionView[]; unassignedCiphers: string[]; - private flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollectionsV1, - ); - private restrictProviderAccess$ = this.configService.getFeatureFlag$( FeatureFlag.RestrictProviderAccess, ); @@ -96,13 +92,9 @@ export class BulkDeleteDialogComponent { deletePromises.push(this.deleteCiphersAdmin(this.unassignedCiphers)); } if (this.cipherIds.length) { - const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$); const restrictProviderAccess = await firstValueFrom(this.restrictProviderAccess$); - if ( - !this.organization || - !this.organization.canEditAllCiphers(flexibleCollectionsV1Enabled, restrictProviderAccess) - ) { + if (!this.organization || !this.organization.canEditAllCiphers(restrictProviderAccess)) { deletePromises.push(this.deleteCiphers()); } else { deletePromises.push(this.deleteCiphersAdmin(this.cipherIds)); @@ -134,12 +126,8 @@ export class BulkDeleteDialogComponent { }; private async deleteCiphers(): Promise { - const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$); const restrictProviderAccess = await firstValueFrom(this.restrictProviderAccess$); - const asAdmin = this.organization?.canEditAllCiphers( - flexibleCollectionsV1Enabled, - restrictProviderAccess, - ); + const asAdmin = this.organization?.canEditAllCiphers(restrictProviderAccess); if (this.permanent) { await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin); } else { @@ -157,12 +145,9 @@ export class BulkDeleteDialogComponent { } private async deleteCollections(): Promise { - const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$); // From org vault if (this.organization) { - if ( - this.collections.some((c) => !c.canDelete(this.organization, flexibleCollectionsV1Enabled)) - ) { + if (this.collections.some((c) => !c.canDelete(this.organization))) { this.platformUtilsService.showToast( "error", this.i18nService.t("errorOccurred"), @@ -179,7 +164,7 @@ export class BulkDeleteDialogComponent { const deletePromises: Promise[] = []; for (const organization of this.organizations) { const orgCollections = this.collections.filter((o) => o.organizationId === organization.id); - if (orgCollections.some((c) => !c.canDelete(organization, flexibleCollectionsV1Enabled))) { + if (orgCollections.some((c) => !c.canDelete(organization))) { this.platformUtilsService.showToast( "error", this.i18nService.t("errorOccurred"), diff --git a/apps/web/src/app/vault/individual-vault/collections.component.html b/apps/web/src/app/vault/individual-vault/collections.component.html index d9c2145f0b5..e4029ef8669 100644 --- a/apps/web/src/app/vault/individual-vault/collections.component.html +++ b/apps/web/src/app/vault/individual-vault/collections.component.html @@ -32,13 +32,7 @@ [(ngModel)]="$any(c).checked" name="Collection[{{ i }}].Checked" appStopProp - [disabled]=" - !c.canEditItems( - this.organization, - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess - ) - " + [disabled]="!c.canEditItems(this.organization, this.restrictProviderAccess)" /> {{ c.name }} diff --git a/apps/web/src/app/vault/individual-vault/collections.component.ts b/apps/web/src/app/vault/individual-vault/collections.component.ts index af9c3476bd5..9795f879776 100644 --- a/apps/web/src/app/vault/individual-vault/collections.component.ts +++ b/apps/web/src/app/vault/individual-vault/collections.component.ts @@ -50,13 +50,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On } check(c: CollectionView, select?: boolean) { - if ( - !c.canEditItems( - this.organization, - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) - ) { + if (!c.canEditItems(this.organization, this.restrictProviderAccess)) { return; } (c as any).checked = select == null ? !(c as any).checked : select; diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index ad07d2847e6..403dbd2f675 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -84,17 +84,12 @@ export class VaultHeaderComponent implements OnInit { /** Emits an event when the delete collection button is clicked in the header */ @Output() onDeleteCollection = new EventEmitter(); - private flexibleCollectionsV1Enabled = false; - constructor( private i18nService: I18nService, private configService: ConfigService, ) {} async ngOnInit() { - this.flexibleCollectionsV1Enabled = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1), - ); this.extensionRefreshEnabled = await firstValueFrom( this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), ); @@ -180,7 +175,7 @@ export class VaultHeaderComponent implements OnInit { const organization = this.organizations.find( (o) => o.id === this.collection?.node.organizationId, ); - return this.collection.node.canEdit(organization, this.flexibleCollectionsV1Enabled); + return this.collection.node.canEdit(organization); } async editCollection(tab: CollectionDialogTabType): Promise { @@ -198,7 +193,7 @@ export class VaultHeaderComponent implements OnInit { (o) => o.id === this.collection?.node.organizationId, ); - return this.collection.node.canDelete(organization, this.flexibleCollectionsV1Enabled); + return this.collection.node.canDelete(organization); } deleteCollection() { 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 183c4f65afd..b19a4509c15 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.html +++ b/apps/web/src/app/vault/individual-vault/vault.component.html @@ -56,7 +56,6 @@ [showAdminActions]="false" [showBulkAddToCollections]="vaultBulkManagementActionEnabled$ | async" (onEvent)="onVaultItemsEvent($event)" - [flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled$ | async" [vaultBulkManagementActionEnabled]="vaultBulkManagementActionEnabled$ | async" > diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 1b9d0e1b629..469fad05a83 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -158,9 +158,6 @@ export class VaultComponent implements OnInit, OnDestroy { protected selectedCollection: TreeNode | undefined; protected canCreateCollections = false; protected currentSearchText$: Observable; - protected flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollectionsV1, - ); protected vaultBulkManagementActionEnabled$ = this.configService.getFeatureFlag$( FeatureFlag.VaultBulkManagementAction, ); @@ -552,7 +549,7 @@ export class VaultComponent implements OnInit, OnDestroy { } async shareCipher(cipher: CipherView) { - if ((await this.flexibleCollectionsV1Enabled()) && cipher.organizationId != null) { + if (cipher.organizationId != null) { // You cannot move ciphers between organizations this.showMissingPermissionsError(); return; @@ -712,8 +709,7 @@ export class VaultComponent implements OnInit, OnDestroy { async deleteCollection(collection: CollectionView): Promise { const organization = await this.organizationService.get(collection.organizationId); - const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$); - if (!collection.canDelete(organization, flexibleCollectionsV1Enabled)) { + if (!collection.canDelete(organization)) { this.showMissingPermissionsError(); return; } @@ -811,7 +807,7 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - if ((await this.flexibleCollectionsV1Enabled()) && !c.edit) { + if (!c.edit) { this.showMissingPermissionsError(); return; } @@ -834,7 +830,7 @@ export class VaultComponent implements OnInit, OnDestroy { } async bulkRestore(ciphers: CipherView[]) { - if ((await this.flexibleCollectionsV1Enabled()) && ciphers.some((c) => !c.edit)) { + if (ciphers.some((c) => !c.edit)) { this.showMissingPermissionsError(); return; } @@ -887,7 +883,7 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - if ((await this.flexibleCollectionsV1Enabled()) && !c.edit) { + if (!c.edit) { this.showMissingPermissionsError(); return; } @@ -936,19 +932,12 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - const flexibleCollectionsV1Enabled = await this.flexibleCollectionsV1Enabled(); - const canDeleteCollections = collections == null || - collections.every((c) => - c.canDelete( - organizations.find((o) => o.id == c.organizationId), - flexibleCollectionsV1Enabled, - ), - ); + collections.every((c) => c.canDelete(organizations.find((o) => o.id == c.organizationId))); const canDeleteCiphers = ciphers == null || ciphers.every((c) => c.edit); - if (flexibleCollectionsV1Enabled && (!canDeleteCollections || !canDeleteCiphers)) { + if (!canDeleteCollections || !canDeleteCiphers) { this.showMissingPermissionsError(); return; } @@ -1052,10 +1041,7 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - if ( - (await this.flexibleCollectionsV1Enabled()) && - ciphers.some((c) => c.organizationId != null) - ) { + if (ciphers.some((c) => c.organizationId != null)) { // You cannot move ciphers between organizations this.showMissingPermissionsError(); return; @@ -1099,10 +1085,8 @@ export class VaultComponent implements OnInit, OnDestroy { return true; } - const flexibleCollectionsV1Enabled = await this.flexibleCollectionsV1Enabled(); - const organization = this.allOrganizations.find((o) => o.id === cipher.organizationId); - return organization.canEditAllCiphers(flexibleCollectionsV1Enabled, false); + return organization.canEditAllCiphers(false); } private go(queryParams: any = null) { @@ -1131,10 +1115,6 @@ export class VaultComponent implements OnInit, OnDestroy { message: this.i18nService.t("missingPermissions"), }); } - - private flexibleCollectionsV1Enabled() { - return firstValueFrom(this.flexibleCollectionsV1Enabled$); - } } /** diff --git a/apps/web/src/app/vault/org-vault/add-edit.component.ts b/apps/web/src/app/vault/org-vault/add-edit.component.ts index c0a83ed74cb..8fd15cf20e8 100644 --- a/apps/web/src/app/vault/org-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/org-vault/add-edit.component.ts @@ -82,12 +82,7 @@ export class AddEditComponent extends BaseAddEditComponent { } protected loadCollections() { - if ( - !this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) - ) { + if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) { return super.loadCollections(); } return Promise.resolve(this.collections); @@ -98,10 +93,7 @@ export class AddEditComponent extends BaseAddEditComponent { const firstCipherCheck = await super.loadCipher(); if ( - !this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) && + !this.organization.canEditAllCiphers(this.restrictProviderAccess) && firstCipherCheck != null ) { return firstCipherCheck; @@ -116,24 +108,14 @@ export class AddEditComponent extends BaseAddEditComponent { } protected encryptCipher() { - if ( - !this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) - ) { + if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) { return super.encryptCipher(); } return this.cipherService.encrypt(this.cipher, null, null, this.originalCipher); } protected async deleteCipher() { - if ( - !this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) - ) { + if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) { return super.deleteCipher(); } return this.cipher.isDeleted diff --git a/apps/web/src/app/vault/org-vault/attachments.component.ts b/apps/web/src/app/vault/org-vault/attachments.component.ts index 30189e80215..71e7842913b 100644 --- a/apps/web/src/app/vault/org-vault/attachments.component.ts +++ b/apps/web/src/app/vault/org-vault/attachments.component.ts @@ -28,7 +28,6 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On viewOnly = false; organization: Organization; - private flexibleCollectionsV1Enabled = false; private restrictProviderAccess = false; constructor( @@ -60,9 +59,6 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On async ngOnInit() { await super.ngOnInit(); - this.flexibleCollectionsV1Enabled = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1), - ); this.restrictProviderAccess = await firstValueFrom( this.configService.getFeatureFlag$(FeatureFlag.RestrictProviderAccess), ); @@ -70,10 +66,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On protected async reupload(attachment: AttachmentView) { if ( - this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) && + this.organization.canEditAllCiphers(this.restrictProviderAccess) && this.showFixOldAttachments(attachment) ) { await super.reuploadCipherAttachment(attachment, true); @@ -81,12 +74,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On } protected async loadCipher() { - if ( - !this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) - ) { + if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) { return await super.loadCipher(); } const response = await this.apiService.getCipherAdmin(this.cipherId); @@ -97,20 +85,12 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On return this.cipherService.saveAttachmentWithServer( this.cipherDomain, file, - this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ), + this.organization.canEditAllCiphers(this.restrictProviderAccess), ); } protected deleteCipherAttachment(attachmentId: string) { - if ( - !this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) - ) { + if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) { return super.deleteCipherAttachment(attachmentId); } return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId); @@ -118,11 +98,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On protected showFixOldAttachments(attachment: AttachmentView) { return ( - attachment.key == null && - this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) + attachment.key == null && this.organization.canEditAllCiphers(this.restrictProviderAccess) ); } } diff --git a/apps/web/src/app/vault/org-vault/collections.component.ts b/apps/web/src/app/vault/org-vault/collections.component.ts index 557b048a7be..4ee052e32fe 100644 --- a/apps/web/src/app/vault/org-vault/collections.component.ts +++ b/apps/web/src/app/vault/org-vault/collections.component.ts @@ -61,10 +61,7 @@ export class CollectionsComponent extends BaseCollectionsComponent { protected async loadCipher() { // if cipher is unassigned use apiService. We can see this by looking at this.collectionIds if ( - !this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) && + !this.organization.canEditAllCiphers(this.restrictProviderAccess) && this.collectionIds.length !== 0 ) { return await super.loadCipher(); @@ -89,10 +86,7 @@ export class CollectionsComponent extends BaseCollectionsComponent { protected saveCollections() { if ( - this.organization.canEditAllCiphers( - this.flexibleCollectionsV1Enabled, - this.restrictProviderAccess, - ) || + this.organization.canEditAllCiphers(this.restrictProviderAccess) || this.collectionIds.length === 0 ) { const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds); diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts index 31764fcf058..56fb2e4cec8 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts @@ -87,7 +87,6 @@ export class VaultHeaderComponent implements OnInit { protected CollectionDialogTabType = CollectionDialogTabType; protected organizations$ = this.organizationService.organizations$; - protected flexibleCollectionsV1Enabled = false; protected restrictProviderAccessFlag = false; constructor( @@ -100,9 +99,6 @@ export class VaultHeaderComponent implements OnInit { ) {} async ngOnInit() { - this.flexibleCollectionsV1Enabled = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1), - ); this.restrictProviderAccessFlag = await this.configService.getFeatureFlag( FeatureFlag.RestrictProviderAccess, ); @@ -195,7 +191,7 @@ export class VaultHeaderComponent implements OnInit { } // Otherwise, check if we can edit the specified collection - return this.collection.node.canEdit(this.organization, this.flexibleCollectionsV1Enabled); + return this.collection.node.canEdit(this.organization); } addCipher() { @@ -225,14 +221,11 @@ export class VaultHeaderComponent implements OnInit { } // Otherwise, check if we can delete the specified collection - return this.collection.node.canDelete(this.organization, this.flexibleCollectionsV1Enabled); + return this.collection.node.canDelete(this.organization); } get canViewCollectionInfo(): boolean { - return this.collection.node.canViewCollectionInfo( - this.organization, - this.flexibleCollectionsV1Enabled, - ); + return this.collection.node.canViewCollectionInfo(this.organization); } get canCreateCollection(): boolean { diff --git a/apps/web/src/app/vault/org-vault/vault.component.html b/apps/web/src/app/vault/org-vault/vault.component.html index 881b0948c01..b27c0234a58 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.html +++ b/apps/web/src/app/vault/org-vault/vault.component.html @@ -68,39 +68,12 @@ [showBulkEditCollectionAccess]="true" [showBulkAddToCollections]="true" [viewingOrgVault]="true" - [flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled" [addAccessStatus]="addAccessStatus$ | async" [addAccessToggle]="showAddAccessToggle" [restrictProviderAccess]="restrictProviderAccessEnabled" > - -
    - -

    {{ "noPermissionToViewAllCollectionItems" | i18n }}

    -
    -
    - -

    {{ "noItemsInList" | i18n }}

    - -
    - + {{ "noItemsInList" | i18n }} - + {{ "autoFillOnPageLoad" | i18n }} diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts index 6f73ffabefb..601380f98a1 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts @@ -32,6 +32,7 @@ describe("AutofillOptionsComponent", () => { autofillSettingsService = mock(); autofillSettingsService.autofillOnPageLoadDefault$ = new BehaviorSubject(false); + autofillSettingsService.autofillOnPageLoad$ = new BehaviorSubject(true); await TestBed.configureTestingModule({ imports: [AutofillOptionsComponent], @@ -145,6 +146,22 @@ describe("AutofillOptionsComponent", () => { expect(component["autofillOptions"][0].label).toEqual("defaultLabel yes"); }); + it("hides the autofill on page load field when the setting is disabled", () => { + fixture.detectChanges(); + let control = fixture.nativeElement.querySelector( + "bit-select[formControlName='autofillOnPageLoad']", + ); + expect(control).toBeTruthy(); + + (autofillSettingsService.autofillOnPageLoad$ as BehaviorSubject).next(false); + + fixture.detectChanges(); + control = fixture.nativeElement.querySelector( + "bit-select[formControlName='autofillOnPageLoad']", + ); + expect(control).toBeFalsy(); + }); + it("announces the addition of a new URI input", fakeAsync(() => { fixture.detectChanges(); diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts index 389eda4c189..80de50c4421 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts @@ -70,6 +70,7 @@ export class AutofillOptionsComponent implements OnInit { } protected defaultMatchDetection$ = this.domainSettingsService.defaultUriMatchStrategy$; + protected autofillOnPageLoadEnabled$ = this.autofillSettingsService.autofillOnPageLoad$; protected autofillOptions: { label: string; value: boolean | null }[] = [ { label: this.i18nService.t("default"), value: null }, diff --git a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.html b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.html index 470b2881aba..c9650eb5539 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.html +++ b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.html @@ -1,6 +1,6 @@ - {{ "websiteUri" | i18n }} + {{ uriLabel }} + [appA11yTitle]="'learnMoreAboutAuthenticators' | i18n" + > + +

    {{ (canCaptureTotp ? "totpHelperWithCapture" : "totpHelper") | i18n }}

    diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts index 91c8ad258c1..020c2d18bd8 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts @@ -14,6 +14,7 @@ import { CardComponent, FormFieldModule, IconButtonModule, + LinkModule, PopoverModule, SectionComponent, SectionHeaderComponent, @@ -43,6 +44,7 @@ import { AutofillOptionsComponent } from "../autofill-options/autofill-options.c NgIf, PopoverModule, AutofillOptionsComponent, + LinkModule, ], }) export class LoginDetailsSectionComponent implements OnInit { From fe9d44af6d90a3591ec4be4e5f4d808229bd1bf2 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Tue, 13 Aug 2024 10:16:53 -0700 Subject: [PATCH 52/59] [PM-10721] Autofill section styles fixes (#10454) * [PM-10721] Remove autofill section title hyphen * [PM-10721] Use h6 styling for header * [PM-10721] Fix spacing for match detection select --- apps/browser/src/_locales/en/messages.json | 2 +- apps/web/src/locales/en/messages.json | 2 +- .../autofill-options/autofill-options.component.html | 2 +- .../components/autofill-options/uri-option.component.html | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 3ee0d1b73d2..c65535e2e50 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3881,7 +3881,7 @@ "message": "Authenticator key" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Autofill options" }, "websiteUri": { "message": "Website (URI)" diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 5733b6cb02f..37e88fab026 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -52,7 +52,7 @@ "message": "Authenticator key" }, "autofillOptions": { - "message": "Auto-fill options" + "message": "Autofill options" }, "websiteUri": { "message": "Website (URI)" diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html index 6c651527c11..6f7dd35be9e 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html @@ -1,6 +1,6 @@ -

    +

    {{ "autofillOptions" | i18n }}

    diff --git a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.html b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.html index c9650eb5539..a55716083de 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.html +++ b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.html @@ -1,5 +1,5 @@ - + {{ uriLabel }} - + {{ "matchDetection" | i18n }} Date: Tue, 13 Aug 2024 10:32:02 -0700 Subject: [PATCH 53/59] [AC-1041] My Vault filter on first login fix (#10301) * [AC-1041] Ensure organizationTree$ updates whenever the policy observables emit * [AC-1041] Ensure enforcePersonalOwnership updates whenever the policy observable emits * [AC-1041] Do not attempt to pre-select null filter values or read-only collections --- .../vault/vault-select.component.ts | 28 ++++++++++++++----- .../services/vault-filter.service.spec.ts | 24 ++++++++++------ .../services/vault-filter.service.ts | 22 ++++++++++----- .../vault/individual-vault/vault.component.ts | 18 ++++++++---- 4 files changed, 64 insertions(+), 28 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault/vault-select.component.ts b/apps/browser/src/vault/popup/components/vault/vault-select.component.ts index de6a33724d1..6780cd57929 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-select.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-select.component.ts @@ -5,17 +5,28 @@ import { Component, ElementRef, EventEmitter, + HostListener, + OnDestroy, OnInit, Output, TemplateRef, ViewChild, ViewContainerRef, - HostListener, - OnDestroy, } from "@angular/core"; -import { BehaviorSubject, concatMap, map, merge, Observable, Subject, takeUntil } from "rxjs"; +import { + BehaviorSubject, + combineLatest, + concatMap, + map, + merge, + Observable, + Subject, + takeUntil, +} from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -88,6 +99,7 @@ export class VaultSelectComponent implements OnInit, OnDestroy { private viewContainerRef: ViewContainerRef, private platformUtilsService: PlatformUtilsService, private organizationService: OrganizationService, + private policyService: PolicyService, ) {} @HostListener("document:keydown.escape", ["$event"]) @@ -103,11 +115,13 @@ export class VaultSelectComponent implements OnInit, OnDestroy { .pipe(takeUntil(this._destroy)) .pipe(map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name")))); - this.organizations$ + combineLatest([ + this.organizations$, + this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), + ]) .pipe( - concatMap(async (organizations) => { - this.enforcePersonalOwnership = - await this.vaultFilterService.checkForPersonalOwnershipPolicy(); + concatMap(async ([organizations, enforcePersonalOwnership]) => { + this.enforcePersonalOwnership = enforcePersonalOwnership; if (this.shouldShow(organizations)) { if (this.enforcePersonalOwnership && !this.vaultFilterService.vaultFilter.myVaultOnly) { diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts index e5938b51979..63bf3d5e4c2 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts @@ -36,6 +36,8 @@ describe("vault filter service", () => { let organizations: ReplaySubject; let folderViews: ReplaySubject; let collectionViews: ReplaySubject; + let personalOwnershipPolicy: ReplaySubject; + let singleOrgPolicy: ReplaySubject; let stateProvider: FakeStateProvider; const mockUserId = Utils.newGuid() as UserId; @@ -56,10 +58,18 @@ describe("vault filter service", () => { organizations = new ReplaySubject(1); folderViews = new ReplaySubject(1); collectionViews = new ReplaySubject(1); + personalOwnershipPolicy = new ReplaySubject(1); + singleOrgPolicy = new ReplaySubject(1); organizationService.memberOrganizations$ = organizations; folderService.folderViews$ = folderViews; collectionService.decryptedCollections$ = collectionViews; + policyService.policyAppliesToActiveUser$ + .calledWith(PolicyType.PersonalOwnership) + .mockReturnValue(personalOwnershipPolicy); + policyService.policyAppliesToActiveUser$ + .calledWith(PolicyType.SingleOrg) + .mockReturnValue(singleOrgPolicy); vaultFilterService = new VaultFilterService( organizationService, @@ -100,6 +110,8 @@ describe("vault filter service", () => { beforeEach(() => { const storedOrgs = [createOrganization("1", "org1"), createOrganization("2", "org2")]; organizations.next(storedOrgs); + personalOwnershipPolicy.next(false); + singleOrgPolicy.next(false); }); it("returns a nested tree", async () => { @@ -111,9 +123,7 @@ describe("vault filter service", () => { }); it("hides My Vault if personal ownership policy is enabled", async () => { - policyService.policyAppliesToUser - .calledWith(PolicyType.PersonalOwnership) - .mockResolvedValue(true); + personalOwnershipPolicy.next(true); const tree = await firstValueFrom(vaultFilterService.organizationTree$); @@ -122,7 +132,7 @@ describe("vault filter service", () => { }); it("returns 1 organization and My Vault if single organization policy is enabled", async () => { - policyService.policyAppliesToUser.calledWith(PolicyType.SingleOrg).mockResolvedValue(true); + singleOrgPolicy.next(true); const tree = await firstValueFrom(vaultFilterService.organizationTree$); @@ -132,10 +142,8 @@ describe("vault filter service", () => { }); it("returns 1 organization if both single organization and personal ownership policies are enabled", async () => { - policyService.policyAppliesToUser.calledWith(PolicyType.SingleOrg).mockResolvedValue(true); - policyService.policyAppliesToUser - .calledWith(PolicyType.PersonalOwnership) - .mockResolvedValue(true); + singleOrgPolicy.next(true); + personalOwnershipPolicy.next(true); const tree = await firstValueFrom(vaultFilterService.organizationTree$); diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts index 36cde762a00..ac20f86d0ee 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts @@ -1,6 +1,7 @@ import { Injectable } from "@angular/core"; import { BehaviorSubject, + combineLatest, combineLatestWith, firstValueFrom, map, @@ -39,10 +40,15 @@ const NestingDelimiter = "/"; @Injectable() export class VaultFilterService implements VaultFilterServiceAbstraction { - organizationTree$: Observable> = - this.organizationService.memberOrganizations$.pipe( - switchMap((orgs) => this.buildOrganizationTree(orgs)), - ); + organizationTree$: Observable> = combineLatest([ + this.organizationService.memberOrganizations$, + this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg), + this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), + ]).pipe( + switchMap(([orgs, singleOrgPolicy, personalOwnershipPolicy]) => + this.buildOrganizationTree(orgs, singleOrgPolicy, personalOwnershipPolicy), + ), + ); protected _organizationFilter = new BehaviorSubject(null); @@ -125,14 +131,16 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { } protected async buildOrganizationTree( - orgs?: Organization[], + orgs: Organization[], + singleOrgPolicy: boolean, + personalOwnershipPolicy: boolean, ): Promise> { const headNode = this.getOrganizationFilterHead(); - if (!(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership))) { + if (!personalOwnershipPolicy) { const myVaultNode = this.getOrganizationFilterMyVault(); headNode.children.push(myVaultNode); } - if (await this.policyService.policyAppliesToUser(PolicyType.SingleOrg)) { + if (singleOrgPolicy) { orgs = orgs.slice(0, 1); } if (orgs) { diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 469fad05a83..77fd63a65f7 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -586,18 +586,24 @@ export class VaultComponent implements OnInit, OnDestroy { async addCipher(cipherType?: CipherType) { const component = await this.editCipher(null); component.type = cipherType || this.activeFilter.cipherType; - if (this.activeFilter.organizationId !== "MyVault") { + if ( + this.activeFilter.organizationId !== "MyVault" && + this.activeFilter.organizationId != null + ) { component.organizationId = this.activeFilter.organizationId; component.collections = ( await firstValueFrom(this.vaultFilterService.filteredCollections$) ).filter((c) => !c.readOnly && c.id != null); } const selectedColId = this.activeFilter.collectionId; - if (selectedColId !== "AllCollections") { - component.organizationId = component.collections.find( - (collection) => collection.id === selectedColId, - )?.organizationId; - component.collectionIds = [selectedColId]; + if (selectedColId !== "AllCollections" && selectedColId != null) { + const selectedCollection = ( + await firstValueFrom(this.vaultFilterService.filteredCollections$) + ).find((c) => c.id === selectedColId); + component.organizationId = selectedCollection?.organizationId; + if (!selectedCollection.readOnly) { + component.collectionIds = [selectedColId]; + } } component.folderId = this.activeFilter.folderId; } From c3d7da06b81decfc4e1dc2c6068a20e774a436fa Mon Sep 17 00:00:00 2001 From: Matt Bishop Date: Tue, 13 Aug 2024 15:10:19 -0400 Subject: [PATCH 54/59] Use cancellation check with latest test reporter (#10507) --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a528b0d9dcd..909bb93f683 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -76,8 +76,8 @@ jobs: run: npm test -- --coverage --maxWorkers=3 - name: Report test results - uses: dorny/test-reporter@eaa763f6ffc21c7a37837f56cd5f9737f27fc6c8 # v1.8.0 - if: ${{ needs.check-test-secrets.outputs.available == 'true' }} && always() + uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1 + if: ${{ needs.check-test-secrets.outputs.available == 'true' && !cancelled() }} with: name: Test Results path: "junit.xml" From a6176aaf2ccfb5c05af5b1fe9e40c26fdf4b1ee7 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:18:04 -0500 Subject: [PATCH 55/59] [PM-10799] Login UI Width (#10506) * reset `maxWidth` attribute during page change * set default value for `maxWidth` if null or undefined is passed * implement `maxWidth` for extension implementation of Anon Layout --- .../extension-anon-layout-wrapper.component.html | 1 + .../extension-anon-layout-wrapper.component.ts | 1 + .../src/angular/anon-layout/anon-layout-wrapper.component.ts | 1 + libs/auth/src/angular/anon-layout/anon-layout.component.ts | 4 ++++ 4 files changed, 7 insertions(+) diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html index e7082f40196..d5273fd9fb2 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html @@ -20,6 +20,7 @@ [showReadonlyHostname]="showReadonlyHostname" [hideLogo]="true" [decreaseTopPadding]="true" + [maxWidth]="maxWidth" > diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts index a16ca5862cf..7a5b156a506 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts @@ -189,6 +189,7 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy { this.showAcctSwitcher = null; this.showBackButton = null; this.showLogo = null; + this.maxWidth = null; } ngOnDestroy() { diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts index a71f9101c93..84a9b4bcf86 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts @@ -129,6 +129,7 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { this.pageSubtitle = null; this.pageIcon = null; this.showReadonlyHostname = null; + this.maxWidth = null; } ngOnDestroy() { diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.ts b/libs/auth/src/angular/anon-layout/anon-layout.component.ts index 19dafa732ab..fc3026dad34 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.ts @@ -76,6 +76,10 @@ export class AnonLayoutComponent implements OnInit, OnChanges { const theme = await firstValueFrom(this.themeStateService.selectedTheme$); await this.updateIcon(theme); } + + if (changes.maxWidth) { + this.maxWidth = changes.maxWidth.currentValue ?? "md"; + } } private async updateIcon(theme: string) { From a13e99ebe9c5ba4fa394408922c4e6d574c96284 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:19:13 -0400 Subject: [PATCH 56/59] Auth/PM-10601 - Tech Debt Cleanup - Refactor Lock Component and User Verification to use PinService (#10408) * PM-10601 - PinSvc new unlock check first draft * PM-10601 - PinSvc - add new method for determining if pin decryption is available. * PM-10601 - Add more docs on PinSvc * PM-10601 - Update Lock Comp & User Verification service + tests to use new isPinDecryptionAvailable method --- .../src/auth/components/lock.component.ts | 5 +- .../abstractions/pin.service.abstraction.ts | 8 +++ .../pin/pin.service.implementation.ts | 28 +++++++++ .../common/services/pin/pin.service.spec.ts | 60 +++++++++++++++++++ .../user-verification.service.spec.ts | 6 ++ .../user-verification.service.ts | 20 ++++--- 6 files changed, 115 insertions(+), 12 deletions(-) diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts index b7ebf991e31..50eded416b2 100644 --- a/libs/angular/src/auth/components/lock.component.ts +++ b/libs/angular/src/auth/components/lock.component.ts @@ -323,10 +323,7 @@ export class LockComponent implements OnInit, OnDestroy { private async load(userId: UserId) { this.pinLockType = await this.pinService.getPinLockType(userId); - const ephemeralPinSet = await this.pinService.getPinKeyEncryptedUserKeyEphemeral(userId); - - this.pinEnabled = - (this.pinLockType === "EPHEMERAL" && !!ephemeralPinSet) || this.pinLockType === "PERSISTENT"; + this.pinEnabled = await this.pinService.isPinDecryptionAvailable(userId); this.masterPasswordEnabled = await this.userVerificationService.hasMasterPassword(); diff --git a/libs/auth/src/common/abstractions/pin.service.abstraction.ts b/libs/auth/src/common/abstractions/pin.service.abstraction.ts index 9090cbe391b..00ccf934f61 100644 --- a/libs/auth/src/common/abstractions/pin.service.abstraction.ts +++ b/libs/auth/src/common/abstractions/pin.service.abstraction.ts @@ -113,9 +113,17 @@ export abstract class PinServiceAbstraction { /** * Declares whether or not the user has a PIN set (either persistent or ephemeral). + * Note: for ephemeral, this does not check if we actual have an ephemeral PIN-encrypted UserKey stored in memory. + * Decryption might not be possible even if this returns true. Use {@link isPinDecryptionAvailable} if decryption is required. */ abstract isPinSet: (userId: UserId) => Promise; + /** + * Checks if PIN-encrypted keys are stored for the user. + * Used for unlock / user verification scenarios where we will need to decrypt the UserKey with the PIN. + */ + abstract isPinDecryptionAvailable: (userId: UserId) => Promise; + /** * Decrypts the UserKey with the provided PIN. * diff --git a/libs/auth/src/common/services/pin/pin.service.implementation.ts b/libs/auth/src/common/services/pin/pin.service.implementation.ts index ac2493a8c48..39bb80e0b73 100644 --- a/libs/auth/src/common/services/pin/pin.service.implementation.ts +++ b/libs/auth/src/common/services/pin/pin.service.implementation.ts @@ -292,6 +292,34 @@ export class PinService implements PinServiceAbstraction { return (await this.getPinLockType(userId)) !== "DISABLED"; } + async isPinDecryptionAvailable(userId: UserId): Promise { + this.validateUserId(userId, "Cannot determine if decryption of user key via PIN is available."); + + const pinLockType = await this.getPinLockType(userId); + + switch (pinLockType) { + case "DISABLED": + return false; + case "PERSISTENT": + // The above getPinLockType call ensures that we have either a PinKeyEncryptedUserKey or OldPinKeyEncryptedMasterKey set. + return true; + case "EPHEMERAL": { + // The above getPinLockType call ensures that we have a UserKeyEncryptedPin set. + // However, we must additively check to ensure that we have a set PinKeyEncryptedUserKeyEphemeral b/c otherwise + // we cannot take a PIN, derive a PIN key, and decrypt the ephemeral UserKey. + const pinKeyEncryptedUserKeyEphemeral = + await this.getPinKeyEncryptedUserKeyEphemeral(userId); + return Boolean(pinKeyEncryptedUserKeyEphemeral); + } + + default: { + // Compile-time check for exhaustive switch + const _exhaustiveCheck: never = pinLockType; + throw new Error(`Unexpected pinLockType: ${_exhaustiveCheck}`); + } + } + } + async decryptUserKeyWithPin(pin: string, userId: UserId): Promise { this.validateUserId(userId, "Cannot decrypt user key with PIN."); diff --git a/libs/auth/src/common/services/pin/pin.service.spec.ts b/libs/auth/src/common/services/pin/pin.service.spec.ts index 81009993d2e..6befec06994 100644 --- a/libs/auth/src/common/services/pin/pin.service.spec.ts +++ b/libs/auth/src/common/services/pin/pin.service.spec.ts @@ -416,6 +416,66 @@ describe("PinService", () => { }); }); + describe("isPinDecryptionAvailable()", () => { + it("should return false if pinLockType is DISABLED", async () => { + // Arrange + sut.getPinLockType = jest.fn().mockResolvedValue("DISABLED"); + + // Act + const result = await sut.isPinDecryptionAvailable(mockUserId); + + // Assert + expect(result).toBe(false); + }); + + it("should return true if pinLockType is PERSISTENT", async () => { + // Arrange + sut.getPinLockType = jest.fn().mockResolvedValue("PERSISTENT"); + + // Act + const result = await sut.isPinDecryptionAvailable(mockUserId); + + // Assert + expect(result).toBe(true); + }); + + it("should return true if pinLockType is EPHEMERAL and we have an ephemeral PIN key encrypted user key", async () => { + // Arrange + sut.getPinLockType = jest.fn().mockResolvedValue("EPHEMERAL"); + sut.getPinKeyEncryptedUserKeyEphemeral = jest + .fn() + .mockResolvedValue(pinKeyEncryptedUserKeyEphemeral); + + // Act + const result = await sut.isPinDecryptionAvailable(mockUserId); + + // Assert + expect(result).toBe(true); + }); + + it("should return false if pinLockType is EPHEMERAL and we do not have an ephemeral PIN key encrypted user key", async () => { + // Arrange + sut.getPinLockType = jest.fn().mockResolvedValue("EPHEMERAL"); + sut.getPinKeyEncryptedUserKeyEphemeral = jest.fn().mockResolvedValue(null); + + // Act + const result = await sut.isPinDecryptionAvailable(mockUserId); + + // Assert + expect(result).toBe(false); + }); + + it("should throw an error if an unexpected pinLockType is returned", async () => { + // Arrange + sut.getPinLockType = jest.fn().mockResolvedValue("UNKNOWN"); + + // Act & Assert + await expect(sut.isPinDecryptionAvailable(mockUserId)).rejects.toThrow( + "Unexpected pinLockType: UNKNOWN", + ); + }); + }); + describe("decryptUserKeyWithPin()", () => { async function setupDecryptUserKeyWithPinMocks( pinLockType: PinLockType, diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts index 653c7a13b33..73a97cbc8bb 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts @@ -410,6 +410,12 @@ describe("UserVerificationService", () => { function setPinAvailability(type: PinLockType) { pinService.getPinLockType.mockResolvedValue(type); + + if (type === "EPHEMERAL" || type === "PERSISTENT") { + pinService.isPinDecryptionAvailable.mockResolvedValue(true); + } else if (type === "DISABLED") { + pinService.isPinDecryptionAvailable.mockResolvedValue(false); + } } function disableBiometricsAvailability() { diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 50fe7b3add5..3b133891c95 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -57,13 +57,17 @@ export class UserVerificationService implements UserVerificationServiceAbstracti ): Promise { const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; if (verificationType === "client") { - const [userHasMasterPassword, pinLockType, biometricsLockSet, biometricsUserKeyStored] = - await Promise.all([ - this.hasMasterPasswordAndMasterKeyHash(userId), - this.pinService.getPinLockType(userId), - this.vaultTimeoutSettingsService.isBiometricLockSet(userId), - this.cryptoService.hasUserKeyStored(KeySuffixOptions.Biometric, userId), - ]); + const [ + userHasMasterPassword, + isPinDecryptionAvailable, + biometricsLockSet, + biometricsUserKeyStored, + ] = await Promise.all([ + this.hasMasterPasswordAndMasterKeyHash(userId), + this.pinService.isPinDecryptionAvailable(userId), + this.vaultTimeoutSettingsService.isBiometricLockSet(userId), + this.cryptoService.hasUserKeyStored(KeySuffixOptions.Biometric, userId), + ]); // note: we do not need to check this.platformUtilsService.supportsBiometric() because // we can just use the logic below which works for both desktop & the browser extension. @@ -71,7 +75,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti return { client: { masterPassword: userHasMasterPassword, - pin: pinLockType !== "DISABLED", + pin: isPinDecryptionAvailable, biometrics: biometricsLockSet && (biometricsUserKeyStored || !this.platformUtilsService.supportsSecureStorage()), From a1d94233e3674f63a339c3cf0a463f00e6a573b8 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Tue, 13 Aug 2024 15:51:34 -0700 Subject: [PATCH 57/59] [PM-10761] Fix broken text for screen readers (#10490) --- .../login-details-section.component.html | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html index 97c226eab90..12171936ef9 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html @@ -28,9 +28,14 @@ {{ "securePasswordGenerated" | i18n }} - {{ "useGeneratorHelpTextPartOne" | i18n }} - - {{ "useGeneratorHelpTextPartTwo" | i18n }} + + {{ "useGeneratorHelpTextPartOne" | i18n }} {{ "useGeneratorHelpTextPartTwo" | i18n }} + +