From 06acdefa914e3c6c84403aea2fd815e2e51e7c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Gon=C3=A7alves?= Date: Tue, 16 Apr 2024 17:37:03 +0100 Subject: [PATCH 01/86] [PM-5273] Migrate state in CipherService (#8314) * PM-5273 Initial migration work for localData * PM-5273 Encrypted and Decrypted ciphers migration to state provider * pm-5273 Update references * pm5273 Ensure prototype on cipher * PM-5273 Add CipherId * PM-5273 Remove migrated methods and updated references * pm-5273 Fix versions * PM-5273 Added missing options * Conflict resolution * Revert "Conflict resolution" This reverts commit 0c0c2039edd4ee442456b6c5a4863a92b9a50e16. * PM-5273 Fix PR comments * Pm-5273 Fix comments * PM-5273 Changed decryptedCiphers to use ActiveUserState * PM-5273 Fix tests * PM-5273 Fix pr comments --- .../notification.background.spec.ts | 2 +- .../background/notification.background.ts | 4 +- .../background/overlay.background.spec.ts | 6 +- .../autofill/background/overlay.background.ts | 2 +- .../browser/src/background/main.background.ts | 2 +- apps/browser/src/popup/app.component.ts | 4 +- .../popup/generator/generator.component.ts | 9 +- .../cipher-service.factory.ts | 2 + .../folder-service.factory.ts | 10 +- .../components/vault/add-edit.component.ts | 2 +- apps/cli/src/bw.ts | 2 +- .../src/app/tools/generator.component.spec.ts | 5 + apps/web/src/app/core/state/state.service.ts | 14 -- .../src/services/jslib-services.module.ts | 4 +- .../vault/components/add-edit.component.ts | 6 +- .../platform/abstractions/state.service.ts | 18 -- .../src/platform/models/domain/account.ts | 16 +- .../src/platform/services/state.service.ts | 138 ----------- .../src/platform/state/state-definitions.ts | 5 + libs/common/src/state-migrations/migrate.ts | 7 +- ...e-cipher-service-to-state-provider.spec.ts | 170 +++++++++++++ ...7-move-cipher-service-to-state-provider.ts | 79 ++++++ .../src/vault/abstractions/cipher.service.ts | 8 + .../src/vault/models/data/cipher.data.ts | 6 + .../src/vault/services/cipher.service.spec.ts | 9 + .../src/vault/services/cipher.service.ts | 227 ++++++++++++------ .../services/folder/folder.service.spec.ts | 11 +- .../vault/services/folder/folder.service.ts | 10 +- .../vault/services/key-state/ciphers.state.ts | 52 ++++ 29 files changed, 525 insertions(+), 305 deletions(-) create mode 100644 libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.ts create mode 100644 libs/common/src/vault/services/key-state/ciphers.state.ts diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 93750ece07..45f095aee9 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -720,7 +720,7 @@ describe("NotificationBackground", () => { ); tabSendMessageSpy = jest.spyOn(BrowserApi, "tabSendMessage").mockImplementation(); editItemSpy = jest.spyOn(notificationBackground as any, "editItem"); - setAddEditCipherInfoSpy = jest.spyOn(stateService, "setAddEditCipherInfo"); + setAddEditCipherInfoSpy = jest.spyOn(cipherService, "setAddEditCipherInfo"); openAddEditVaultItemPopoutSpy = jest.spyOn( notificationBackground as any, "openAddEditVaultItemPopout", diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 74e6147505..9b65e4db0b 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -600,14 +600,14 @@ export default class NotificationBackground { } /** - * Sets the add/edit cipher info in the state service + * Sets the add/edit cipher info in the cipher service * and opens the add/edit vault item popout. * * @param cipherView - The cipher to edit * @param senderTab - The tab that the message was sent from */ private async editItem(cipherView: CipherView, senderTab: chrome.tabs.Tab) { - await this.stateService.setAddEditCipherInfo({ + await this.cipherService.setAddEditCipherInfo({ cipher: cipherView, collectionIds: cipherView.collectionIds, }); diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 2599c1825e..e65397a62b 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -592,7 +592,7 @@ describe("OverlayBackground", () => { beforeEach(() => { sender = mock({ tab: { id: 1 } }); jest - .spyOn(overlayBackground["stateService"], "setAddEditCipherInfo") + .spyOn(overlayBackground["cipherService"], "setAddEditCipherInfo") .mockImplementation(); jest.spyOn(overlayBackground as any, "openAddEditVaultItemPopout").mockImplementation(); }); @@ -600,7 +600,7 @@ describe("OverlayBackground", () => { it("will not open the add edit popout window if the message does not have a login cipher provided", () => { sendExtensionRuntimeMessage({ command: "autofillOverlayAddNewVaultItem" }, sender); - expect(overlayBackground["stateService"].setAddEditCipherInfo).not.toHaveBeenCalled(); + expect(overlayBackground["cipherService"].setAddEditCipherInfo).not.toHaveBeenCalled(); expect(overlayBackground["openAddEditVaultItemPopout"]).not.toHaveBeenCalled(); }); @@ -621,7 +621,7 @@ describe("OverlayBackground", () => { ); await flushPromises(); - expect(overlayBackground["stateService"].setAddEditCipherInfo).toHaveBeenCalled(); + expect(overlayBackground["cipherService"].setAddEditCipherInfo).toHaveBeenCalled(); expect(BrowserApi.sendMessage).toHaveBeenCalledWith( "inlineAutofillMenuRefreshAddEditCipher", ); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 50fb80ef1b..551263525e 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -636,7 +636,7 @@ class OverlayBackground implements OverlayBackgroundInterface { cipherView.type = CipherType.Login; cipherView.login = loginView; - await this.stateService.setAddEditCipherInfo({ + await this.cipherService.setAddEditCipherInfo({ cipher: cipherView, collectionIds: cipherView.collectionIds, }); diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 105e7e2a38..642510b4de 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -663,12 +663,12 @@ export default class MainBackground { this.encryptService, this.cipherFileUploadService, this.configService, + this.stateProvider, ); this.folderService = new FolderService( this.cryptoService, this.i18nService, this.cipherService, - this.stateService, this.stateProvider, ); this.folderApiService = new FolderApiService(this.folderService, this.apiService); diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index b0fdaec4fc..c224e652f6 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -7,6 +7,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; import { BrowserApi } from "../platform/browser/browser-api"; @@ -42,6 +43,7 @@ export class AppComponent implements OnInit, OnDestroy { private stateService: BrowserStateService, private browserSendStateService: BrowserSendStateService, private vaultBrowserStateService: VaultBrowserStateService, + private cipherService: CipherService, private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, private platformUtilsService: ForegroundPlatformUtilsService, @@ -161,7 +163,7 @@ export class AppComponent implements OnInit, OnDestroy { await this.clearComponentStates(); } if (url.startsWith("/tabs/")) { - await this.stateService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null); } (window as any).previousPopupUrl = url; diff --git a/apps/browser/src/tools/popup/generator/generator.component.ts b/apps/browser/src/tools/popup/generator/generator.component.ts index c683b6f4a6..0c11c28f27 100644 --- a/apps/browser/src/tools/popup/generator/generator.component.ts +++ b/apps/browser/src/tools/popup/generator/generator.component.ts @@ -1,6 +1,7 @@ import { Location } from "@angular/common"; import { Component } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom } from "rxjs"; import { GeneratorComponent as BaseGeneratorComponent } from "@bitwarden/angular/tools/generator/components/generator.component"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -9,6 +10,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info"; @@ -19,6 +21,7 @@ import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher export class GeneratorComponent extends BaseGeneratorComponent { private addEditCipherInfo: AddEditCipherInfo; private cipherState: CipherView; + private cipherService: CipherService; constructor( passwordGenerationService: PasswordGenerationServiceAbstraction, @@ -26,6 +29,7 @@ export class GeneratorComponent extends BaseGeneratorComponent { platformUtilsService: PlatformUtilsService, i18nService: I18nService, stateService: StateService, + cipherService: CipherService, route: ActivatedRoute, logService: LogService, private location: Location, @@ -40,10 +44,11 @@ export class GeneratorComponent extends BaseGeneratorComponent { route, window, ); + this.cipherService = cipherService; } async ngOnInit() { - this.addEditCipherInfo = await this.stateService.getAddEditCipherInfo(); + this.addEditCipherInfo = await firstValueFrom(this.cipherService.addEditCipherInfo$); if (this.addEditCipherInfo != null) { this.cipherState = this.addEditCipherInfo.cipher; } @@ -64,7 +69,7 @@ export class GeneratorComponent extends BaseGeneratorComponent { this.addEditCipherInfo.cipher = this.cipherState; // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.stateService.setAddEditCipherInfo(this.addEditCipherInfo); + this.cipherService.setAddEditCipherInfo(this.addEditCipherInfo); this.close(); } diff --git a/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts b/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts index 8ffeca72bc..57366ea8c0 100644 --- a/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts +++ b/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts @@ -42,6 +42,7 @@ import { i18nServiceFactory, I18nServiceInitOptions, } from "../../../platform/background/service-factories/i18n-service.factory"; +import { stateProviderFactory } from "../../../platform/background/service-factories/state-provider.factory"; import { stateServiceFactory, StateServiceInitOptions, @@ -81,6 +82,7 @@ export function cipherServiceFactory( await encryptServiceFactory(cache, opts), await cipherFileUploadServiceFactory(cache, opts), await configServiceFactory(cache, opts), + await stateProviderFactory(cache, opts), ), ); } diff --git a/apps/browser/src/vault/background/service_factories/folder-service.factory.ts b/apps/browser/src/vault/background/service_factories/folder-service.factory.ts index 72847a0536..0593dc904c 100644 --- a/apps/browser/src/vault/background/service_factories/folder-service.factory.ts +++ b/apps/browser/src/vault/background/service_factories/folder-service.factory.ts @@ -14,11 +14,10 @@ import { i18nServiceFactory, I18nServiceInitOptions, } from "../../../platform/background/service-factories/i18n-service.factory"; -import { stateProviderFactory } from "../../../platform/background/service-factories/state-provider.factory"; import { - stateServiceFactory as stateServiceFactory, - StateServiceInitOptions, -} from "../../../platform/background/service-factories/state-service.factory"; + stateProviderFactory, + StateProviderInitOptions, +} from "../../../platform/background/service-factories/state-provider.factory"; import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory"; @@ -28,7 +27,7 @@ export type FolderServiceInitOptions = FolderServiceFactoryOptions & CryptoServiceInitOptions & CipherServiceInitOptions & I18nServiceInitOptions & - StateServiceInitOptions; + StateProviderInitOptions; export function folderServiceFactory( cache: { folderService?: AbstractFolderService } & CachedServices, @@ -43,7 +42,6 @@ export function folderServiceFactory( await cryptoServiceFactory(cache, opts), await i18nServiceFactory(cache, opts), await cipherServiceFactory(cache, opts), - await stateServiceFactory(cache, opts), await stateProviderFactory(cache, opts), ), ); diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts index b27a986231..a566b054c0 100644 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts @@ -304,7 +304,7 @@ export class AddEditComponent extends BaseAddEditComponent { } private saveCipherState() { - return this.stateService.setAddEditCipherInfo({ + return this.cipherService.setAddEditCipherInfo({ cipher: this.cipher, collectionIds: this.collections == null diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 4228eba965..f02d7da49c 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -544,13 +544,13 @@ export class Main { this.encryptService, this.cipherFileUploadService, this.configService, + this.stateProvider, ); this.folderService = new FolderService( this.cryptoService, this.i18nService, this.cipherService, - this.stateService, this.stateProvider, ); diff --git a/apps/desktop/src/app/tools/generator.component.spec.ts b/apps/desktop/src/app/tools/generator.component.spec.ts index 53f919a596..51b5bf93a2 100644 --- a/apps/desktop/src/app/tools/generator.component.spec.ts +++ b/apps/desktop/src/app/tools/generator.component.spec.ts @@ -10,6 +10,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { GeneratorComponent } from "./generator.component"; @@ -54,6 +55,10 @@ describe("GeneratorComponent", () => { provide: LogService, useValue: mock(), }, + { + provide: CipherService, + useValue: mock(), + }, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); diff --git a/apps/web/src/app/core/state/state.service.ts b/apps/web/src/app/core/state/state.service.ts index 54e456d34c..1ae62d8591 100644 --- a/apps/web/src/app/core/state/state.service.ts +++ b/apps/web/src/app/core/state/state.service.ts @@ -18,7 +18,6 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service"; -import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Account } from "./account"; import { GlobalState } from "./global-state"; @@ -57,19 +56,6 @@ export class StateService extends BaseStateService { await super.addAccount(account); } - async getEncryptedCiphers(options?: StorageOptions): Promise<{ [id: string]: CipherData }> { - options = this.reconcileOptions(options, await this.defaultInMemoryOptions()); - return await super.getEncryptedCiphers(options); - } - - async setEncryptedCiphers( - value: { [id: string]: CipherData }, - options?: StorageOptions, - ): Promise { - options = this.reconcileOptions(options, await this.defaultInMemoryOptions()); - return await super.setEncryptedCiphers(value, options); - } - override async getLastSync(options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultInMemoryOptions()); return await super.getLastSync(options); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index b311000fb8..dbb94f6753 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -411,6 +411,7 @@ const safeProviders: SafeProvider[] = [ encryptService: EncryptService, fileUploadService: CipherFileUploadServiceAbstraction, configService: ConfigService, + stateProvider: StateProvider, ) => new CipherService( cryptoService, @@ -423,6 +424,7 @@ const safeProviders: SafeProvider[] = [ encryptService, fileUploadService, configService, + stateProvider, ), deps: [ CryptoServiceAbstraction, @@ -435,6 +437,7 @@ const safeProviders: SafeProvider[] = [ EncryptService, CipherFileUploadServiceAbstraction, ConfigService, + StateProvider, ], }), safeProvider({ @@ -444,7 +447,6 @@ const safeProviders: SafeProvider[] = [ CryptoServiceAbstraction, I18nServiceAbstraction, CipherServiceAbstraction, - StateServiceAbstraction, StateProvider, ], }), diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index ab09d14c86..d29c74b42d 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -1,6 +1,6 @@ import { DatePipe } from "@angular/common"; import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { concatMap, Observable, Subject, takeUntil } from "rxjs"; +import { concatMap, firstValueFrom, Observable, Subject, takeUntil } from "rxjs"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; @@ -687,7 +687,7 @@ export class AddEditComponent implements OnInit, OnDestroy { } async loadAddEditCipherInfo(): Promise { - const addEditCipherInfo: any = await this.stateService.getAddEditCipherInfo(); + const addEditCipherInfo: any = await firstValueFrom(this.cipherService.addEditCipherInfo$); const loadedSavedInfo = addEditCipherInfo != null; if (loadedSavedInfo) { @@ -700,7 +700,7 @@ export class AddEditComponent implements OnInit, OnDestroy { } } - await this.stateService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null); return loadedSavedInfo; } diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index fd76cad6d1..2348c8844a 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -6,10 +6,6 @@ import { GeneratorOptions } from "../../tools/generator/generator-options"; import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password"; import { UsernameGeneratorOptions } from "../../tools/generator/username"; import { UserId } from "../../types/guid"; -import { CipherData } from "../../vault/models/data/cipher.data"; -import { LocalData } from "../../vault/models/data/local.data"; -import { CipherView } from "../../vault/models/view/cipher.view"; -import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info"; import { KdfType } from "../enums"; import { Account } from "../models/domain/account"; import { EncString } from "../models/domain/enc-string"; @@ -38,8 +34,6 @@ export abstract class StateService { clean: (options?: StorageOptions) => Promise; init: (initOptions?: InitOptions) => Promise; - getAddEditCipherInfo: (options?: StorageOptions) => Promise; - setAddEditCipherInfo: (value: AddEditCipherInfo, options?: StorageOptions) => Promise; /** * Gets the user's auto key */ @@ -104,8 +98,6 @@ export abstract class StateService { * @deprecated For migration purposes only, use setUserKeyBiometric instead */ setCryptoMasterKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise; - getDecryptedCiphers: (options?: StorageOptions) => Promise; - setDecryptedCiphers: (value: CipherView[], options?: StorageOptions) => Promise; getDecryptedPasswordGenerationHistory: ( options?: StorageOptions, ) => Promise; @@ -134,11 +126,6 @@ export abstract class StateService { value: boolean, options?: StorageOptions, ) => Promise; - getEncryptedCiphers: (options?: StorageOptions) => Promise<{ [id: string]: CipherData }>; - setEncryptedCiphers: ( - value: { [id: string]: CipherData }, - options?: StorageOptions, - ) => Promise; getEncryptedPasswordGenerationHistory: ( options?: StorageOptions, ) => Promise; @@ -165,11 +152,6 @@ export abstract class StateService { setLastActive: (value: number, options?: StorageOptions) => Promise; getLastSync: (options?: StorageOptions) => Promise; setLastSync: (value: string, options?: StorageOptions) => Promise; - getLocalData: (options?: StorageOptions) => Promise<{ [cipherId: string]: LocalData }>; - setLocalData: ( - value: { [cipherId: string]: LocalData }, - options?: StorageOptions, - ) => Promise; getMinimizeOnCopyToClipboard: (options?: StorageOptions) => Promise; setMinimizeOnCopyToClipboard: (value: boolean, options?: StorageOptions) => Promise; getOrganizationInvitation: (options?: StorageOptions) => Promise; diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index 759a903514..ae7780ada4 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -8,9 +8,6 @@ import { } from "../../../tools/generator/password"; import { UsernameGeneratorOptions } from "../../../tools/generator/username/username-generation-options"; import { DeepJsonify } from "../../../types/deep-jsonify"; -import { CipherData } from "../../../vault/models/data/cipher.data"; -import { CipherView } from "../../../vault/models/view/cipher.view"; -import { AddEditCipherInfo } from "../../../vault/types/add-edit-cipher-info"; import { KdfType } from "../../enums"; import { Utils } from "../../misc/utils"; @@ -61,28 +58,17 @@ export class DataEncryptionPair { } export class AccountData { - ciphers?: DataEncryptionPair = new DataEncryptionPair< - CipherData, - CipherView - >(); - localData?: any; passwordGenerationHistory?: EncryptionPair< GeneratedPasswordHistory[], GeneratedPasswordHistory[] > = new EncryptionPair(); - addEditCipherInfo?: AddEditCipherInfo; static fromJSON(obj: DeepJsonify): AccountData { if (obj == null) { return null; } - return Object.assign(new AccountData(), obj, { - addEditCipherInfo: { - cipher: CipherView.fromJSON(obj?.addEditCipherInfo?.cipher), - collectionIds: obj?.addEditCipherInfo?.collectionIds, - }, - }); + return Object.assign(new AccountData(), obj); } } diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 3d512175a8..9edc9ed1e3 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -9,10 +9,6 @@ import { GeneratorOptions } from "../../tools/generator/generator-options"; import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password"; import { UsernameGeneratorOptions } from "../../tools/generator/username"; import { UserId } from "../../types/guid"; -import { CipherData } from "../../vault/models/data/cipher.data"; -import { LocalData } from "../../vault/models/data/local.data"; -import { CipherView } from "../../vault/models/view/cipher.view"; -import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info"; import { EnvironmentService } from "../abstractions/environment.service"; import { LogService } from "../abstractions/log.service"; import { @@ -221,34 +217,6 @@ export class StateService< return currentUser as UserId; } - async getAddEditCipherInfo(options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - // ensure prototype on cipher - const raw = account?.data?.addEditCipherInfo; - return raw == null - ? null - : { - cipher: - raw?.cipher.toJSON != null - ? raw.cipher - : CipherView.fromJSON(raw?.cipher as Jsonify), - collectionIds: raw?.collectionIds, - }; - } - - async setAddEditCipherInfo(value: AddEditCipherInfo, options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - account.data.addEditCipherInfo = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - } - /** * user key when using the "never" option of vault timeout */ @@ -465,24 +433,6 @@ export class StateService< await this.saveSecureStorageKey(partialKeys.biometricKey, value, options); } - @withPrototypeForArrayMembers(CipherView, CipherView.fromJSON) - async getDecryptedCiphers(options?: StorageOptions): Promise { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) - )?.data?.ciphers?.decrypted; - } - - async setDecryptedCiphers(value: CipherView[], options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - account.data.ciphers.decrypted = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - } - @withPrototypeForArrayMembers(GeneratedPasswordHistory) async getDecryptedPasswordGenerationHistory( options?: StorageOptions, @@ -621,27 +571,6 @@ export class StateService< ); } - @withPrototypeForObjectValues(CipherData) - async getEncryptedCiphers(options?: StorageOptions): Promise<{ [id: string]: CipherData }> { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) - )?.data?.ciphers?.encrypted; - } - - async setEncryptedCiphers( - value: { [id: string]: CipherData }, - options?: StorageOptions, - ): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()), - ); - account.data.ciphers.encrypted = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()), - ); - } - /** * @deprecated Use UserKey instead */ @@ -805,26 +734,6 @@ export class StateService< ); } - async getLocalData(options?: StorageOptions): Promise<{ [cipherId: string]: LocalData }> { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) - )?.data?.localData; - } - - async setLocalData( - value: { [cipherId: string]: LocalData }, - options?: StorageOptions, - ): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()), - ); - account.data.localData = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()), - ); - } - async getMinimizeOnCopyToClipboard(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) @@ -1510,50 +1419,3 @@ function withPrototypeForArrayMembers( }; }; } - -function withPrototypeForObjectValues( - valuesConstructor: new (...args: any[]) => T, - valuesConverter: (input: any) => T = (i) => i, -): ( - target: any, - propertyKey: string | symbol, - descriptor: PropertyDescriptor, -) => { value: (...args: any[]) => Promise<{ [key: string]: T }> } { - return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { - const originalMethod = descriptor.value; - - return { - value: function (...args: any[]) { - const originalResult: Promise<{ [key: string]: T }> = originalMethod.apply(this, args); - - if (!Utils.isPromise(originalResult)) { - throw new Error( - `Error applying prototype to stored value -- result is not a promise for method ${String( - propertyKey, - )}`, - ); - } - - return originalResult.then((result) => { - if (result == null) { - return null; - } else { - for (const [key, val] of Object.entries(result)) { - result[key] = - val == null || val.constructor.name === valuesConstructor.prototype.constructor.name - ? valuesConverter(val) - : valuesConverter( - Object.create( - valuesConstructor.prototype, - Object.getOwnPropertyDescriptors(val), - ), - ); - } - - return result as { [key: string]: T }; - } - }); - }, - }; - }; -} diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 518e5c51d6..18df252062 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -135,3 +135,8 @@ export const VAULT_SETTINGS_DISK = new StateDefinition("vaultSettings", "disk", }); export const VAULT_BROWSER_MEMORY = new StateDefinition("vaultBrowser", "memory"); export const VAULT_SEARCH_MEMORY = new StateDefinition("vaultSearch", "memory"); +export const CIPHERS_DISK = new StateDefinition("ciphers", "disk", { web: "memory" }); +export const CIPHERS_DISK_LOCAL = new StateDefinition("ciphersLocal", "disk", { + web: "disk-local", +}); +export const CIPHERS_MEMORY = new StateDefinition("ciphersMemory", "memory"); diff --git a/libs/common/src/state-migrations/migrate.ts b/libs/common/src/state-migrations/migrate.ts index 77b949126f..000f85519e 100644 --- a/libs/common/src/state-migrations/migrate.ts +++ b/libs/common/src/state-migrations/migrate.ts @@ -53,6 +53,7 @@ import { DeviceTrustCryptoServiceStateProviderMigrator } from "./migrations/53-m import { SendMigrator } from "./migrations/54-move-encrypted-sends"; import { MoveMasterKeyStateToProviderMigrator } from "./migrations/55-move-master-key-state-to-provider"; import { AuthRequestMigrator } from "./migrations/56-move-auth-requests"; +import { CipherServiceMigrator } from "./migrations/57-move-cipher-service-to-state-provider"; import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key"; import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account"; import { MoveStateVersionMigrator } from "./migrations/8-move-state-version"; @@ -60,8 +61,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting import { MinVersionMigrator } from "./migrations/min-version"; export const MIN_VERSION = 3; -export const CURRENT_VERSION = 56; - +export const CURRENT_VERSION = 57; export type MinVersion = typeof MIN_VERSION; export function createMigrationBuilder() { @@ -119,7 +119,8 @@ export function createMigrationBuilder() { .with(DeviceTrustCryptoServiceStateProviderMigrator, 52, 53) .with(SendMigrator, 53, 54) .with(MoveMasterKeyStateToProviderMigrator, 54, 55) - .with(AuthRequestMigrator, 55, CURRENT_VERSION); + .with(AuthRequestMigrator, 55, 56) + .with(CipherServiceMigrator, 56, CURRENT_VERSION); } export async function currentVersion( diff --git a/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.spec.ts b/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.spec.ts new file mode 100644 index 0000000000..499cff1c89 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.spec.ts @@ -0,0 +1,170 @@ +import { MockProxy, any } from "jest-mock-extended"; + +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { + CIPHERS_DISK, + CIPHERS_DISK_LOCAL, + CipherServiceMigrator, +} from "./57-move-cipher-service-to-state-provider"; + +function exampleJSON() { + return { + global: { + otherStuff: "otherStuff1", + }, + authenticatedAccounts: ["user1", "user2"], + user1: { + data: { + localData: { + "6865ba55-7966-4d63-b743-b12000d49631": { + lastUsedDate: 1708950970632, + }, + "f895f099-6739-4cca-9d61-b12200d04bfa": { + lastUsedDate: 1709031916943, + }, + }, + ciphers: { + "cipher-id-10": { + id: "cipher-id-10", + }, + "cipher-id-11": { + id: "cipher-id-11", + }, + }, + }, + }, + user2: { + data: { + otherStuff: "otherStuff5", + }, + }, + }; +} + +function rollbackJSON() { + return { + user_user1_ciphersLocal_localData: { + "6865ba55-7966-4d63-b743-b12000d49631": { + lastUsedDate: 1708950970632, + }, + "f895f099-6739-4cca-9d61-b12200d04bfa": { + lastUsedDate: 1709031916943, + }, + }, + user_user1_ciphers_ciphers: { + "cipher-id-10": { + id: "cipher-id-10", + }, + "cipher-id-11": { + id: "cipher-id-11", + }, + }, + global: { + otherStuff: "otherStuff1", + }, + authenticatedAccounts: ["user1", "user2"], + user1: { + data: {}, + }, + user2: { + data: { + localData: { + otherStuff: "otherStuff3", + }, + ciphers: { + otherStuff: "otherStuff4", + }, + otherStuff: "otherStuff5", + }, + }, + }; +} + +describe("CipherServiceMigrator", () => { + let helper: MockProxy; + let sut: CipherServiceMigrator; + + describe("migrate", () => { + beforeEach(() => { + helper = mockMigrationHelper(exampleJSON(), 56); + sut = new CipherServiceMigrator(56, 57); + }); + + it("should remove local data and ciphers from all accounts", async () => { + await sut.migrate(helper); + expect(helper.set).toHaveBeenCalledWith("user1", { + data: {}, + }); + }); + + it("should migrate localData and ciphers to state provider for accounts that have the data", async () => { + await sut.migrate(helper); + + expect(helper.setToUser).toHaveBeenCalledWith("user1", CIPHERS_DISK_LOCAL, { + "6865ba55-7966-4d63-b743-b12000d49631": { + lastUsedDate: 1708950970632, + }, + "f895f099-6739-4cca-9d61-b12200d04bfa": { + lastUsedDate: 1709031916943, + }, + }); + expect(helper.setToUser).toHaveBeenCalledWith("user1", CIPHERS_DISK, { + "cipher-id-10": { + id: "cipher-id-10", + }, + "cipher-id-11": { + id: "cipher-id-11", + }, + }); + + expect(helper.setToUser).not.toHaveBeenCalledWith("user2", CIPHERS_DISK_LOCAL, any()); + expect(helper.setToUser).not.toHaveBeenCalledWith("user2", CIPHERS_DISK, any()); + }); + }); + + describe("rollback", () => { + beforeEach(() => { + helper = mockMigrationHelper(rollbackJSON(), 57); + sut = new CipherServiceMigrator(56, 57); + }); + + it.each(["user1", "user2"])("should null out new values", async (userId) => { + await sut.rollback(helper); + expect(helper.setToUser).toHaveBeenCalledWith(userId, CIPHERS_DISK_LOCAL, null); + expect(helper.setToUser).toHaveBeenCalledWith(userId, CIPHERS_DISK, null); + }); + + it("should add back localData and ciphers to all accounts", async () => { + await sut.rollback(helper); + + expect(helper.set).toHaveBeenCalledWith("user1", { + data: { + localData: { + "6865ba55-7966-4d63-b743-b12000d49631": { + lastUsedDate: 1708950970632, + }, + "f895f099-6739-4cca-9d61-b12200d04bfa": { + lastUsedDate: 1709031916943, + }, + }, + ciphers: { + "cipher-id-10": { + id: "cipher-id-10", + }, + "cipher-id-11": { + id: "cipher-id-11", + }, + }, + }, + }); + }); + + it("should not add data back if data wasn't migrated or acct doesn't exist", async () => { + await sut.rollback(helper); + + expect(helper.set).not.toHaveBeenCalledWith("user2", any()); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.ts b/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.ts new file mode 100644 index 0000000000..e71d889bb7 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.ts @@ -0,0 +1,79 @@ +import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; +import { Migrator } from "../migrator"; + +type ExpectedAccountType = { + data: { + localData?: unknown; + ciphers?: unknown; + }; +}; + +export const CIPHERS_DISK_LOCAL: KeyDefinitionLike = { + key: "localData", + stateDefinition: { + name: "ciphersLocal", + }, +}; + +export const CIPHERS_DISK: KeyDefinitionLike = { + key: "ciphers", + stateDefinition: { + name: "ciphers", + }, +}; + +export class CipherServiceMigrator extends Migrator<56, 57> { + async migrate(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + async function migrateAccount(userId: string, account: ExpectedAccountType): Promise { + let updatedAccount = false; + + //Migrate localData + const localData = account?.data?.localData; + if (localData != null) { + await helper.setToUser(userId, CIPHERS_DISK_LOCAL, localData); + delete account.data.localData; + updatedAccount = true; + } + + //Migrate ciphers + const ciphers = account?.data?.ciphers; + if (ciphers != null) { + await helper.setToUser(userId, CIPHERS_DISK, ciphers); + delete account.data.ciphers; + updatedAccount = true; + } + + if (updatedAccount) { + await helper.set(userId, account); + } + } + + await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]); + } + + async rollback(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + async function rollbackAccount(userId: string, account: ExpectedAccountType): Promise { + //rollback localData + const localData = await helper.getFromUser(userId, CIPHERS_DISK_LOCAL); + + if (account.data && localData != null) { + account.data.localData = localData; + await helper.set(userId, account); + } + await helper.setToUser(userId, CIPHERS_DISK_LOCAL, null); + + //rollback ciphers + const ciphers = await helper.getFromUser(userId, CIPHERS_DISK); + + if (account.data && ciphers != null) { + account.data.ciphers = ciphers; + await helper.set(userId, account); + } + await helper.setToUser(userId, CIPHERS_DISK, null); + } + + await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]); + } +} diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index a8a0a25e9b..501fd87665 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -1,3 +1,5 @@ +import { Observable } from "rxjs"; + import { UriMatchStrategySetting } from "../../models/domain/domain-service"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { CipherId, CollectionId, OrganizationId } from "../../types/guid"; @@ -7,8 +9,13 @@ import { Cipher } from "../models/domain/cipher"; import { Field } from "../models/domain/field"; import { CipherView } from "../models/view/cipher.view"; import { FieldView } from "../models/view/field.view"; +import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; export abstract class CipherService { + /** + * An observable monitoring the add/edit cipher info saved to memory. + */ + addEditCipherInfo$: Observable; clearCache: (userId?: string) => Promise; encrypt: ( model: CipherView, @@ -102,4 +109,5 @@ export abstract class CipherService { asAdmin?: boolean, ) => Promise; getKeyForCipherKeyDecryption: (cipher: Cipher) => Promise; + setAddEditCipherInfo: (value: AddEditCipherInfo) => Promise; } diff --git a/libs/common/src/vault/models/data/cipher.data.ts b/libs/common/src/vault/models/data/cipher.data.ts index 1452ffe7ee..f8db7186d6 100644 --- a/libs/common/src/vault/models/data/cipher.data.ts +++ b/libs/common/src/vault/models/data/cipher.data.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CipherType } from "../../enums/cipher-type"; import { CipherResponse } from "../response/cipher.response"; @@ -84,4 +86,8 @@ export class CipherData { this.passwordHistory = response.passwordHistory.map((ph) => new PasswordHistoryData(ph)); } } + + static fromJSON(obj: Jsonify) { + return Object.assign(new CipherData(), obj); + } } diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index c374724781..28c4bfc653 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; +import { FakeStateProvider } from "../../../spec/fake-state-provider"; import { makeStaticByteArray } from "../../../spec/utils"; import { ApiService } from "../../abstractions/api.service"; import { SearchService } from "../../abstractions/search.service"; @@ -12,10 +14,12 @@ import { CryptoService } from "../../platform/abstractions/crypto.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { StateService } from "../../platform/abstractions/state.service"; +import { Utils } from "../../platform/misc/utils"; import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer"; import { EncString } from "../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../platform/services/container.service"; +import { UserId } from "../../types/guid"; import { CipherKey, OrgKey } from "../../types/key"; import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service"; import { FieldType } from "../enums"; @@ -97,6 +101,8 @@ const cipherData: CipherData = { }, ], }; +const mockUserId = Utils.newGuid() as UserId; +let accountService: FakeAccountService; describe("Cipher Service", () => { const cryptoService = mock(); @@ -109,6 +115,8 @@ describe("Cipher Service", () => { const searchService = mock(); const encryptService = mock(); const configService = mock(); + accountService = mockAccountServiceWith(mockUserId); + const stateProvider = new FakeStateProvider(accountService); let cipherService: CipherService; let cipherObj: Cipher; @@ -130,6 +138,7 @@ describe("Cipher Service", () => { encryptService, cipherFileUploadService, configService, + stateProvider, ); cipherObj = new Cipher(cipherData); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index dffbf5cbbe..e8544d7f98 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1,4 +1,4 @@ -import { firstValueFrom } from "rxjs"; +import { Observable, firstValueFrom } from "rxjs"; import { SemVer } from "semver"; import { ApiService } from "../../abstractions/api.service"; @@ -21,13 +21,15 @@ import Domain from "../../platform/models/domain/domain-base"; import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer"; import { EncString } from "../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; +import { ActiveUserState, StateProvider } from "../../platform/state"; import { CipherId, CollectionId, OrganizationId } from "../../types/guid"; -import { OrgKey, UserKey } from "../../types/key"; +import { UserKey, OrgKey } from "../../types/key"; import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service"; import { FieldType } from "../enums"; import { CipherType } from "../enums/cipher-type"; import { CipherData } from "../models/data/cipher.data"; +import { LocalData } from "../models/data/local.data"; import { Attachment } from "../models/domain/attachment"; import { Card } from "../models/domain/card"; import { Cipher } from "../models/domain/cipher"; @@ -54,6 +56,14 @@ import { AttachmentView } from "../models/view/attachment.view"; import { CipherView } from "../models/view/cipher.view"; import { FieldView } from "../models/view/field.view"; import { PasswordHistoryView } from "../models/view/password-history.view"; +import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; + +import { + ENCRYPTED_CIPHERS, + LOCAL_DATA_KEY, + ADD_EDIT_CIPHER_INFO_KEY, + DECRYPTED_CIPHERS, +} from "./key-state/ciphers.state"; const CIPHER_KEY_ENC_MIN_SERVER_VER = new SemVer("2024.2.0"); @@ -62,6 +72,16 @@ export class CipherService implements CipherServiceAbstraction { this.sortCiphersByLastUsed, ); + localData$: Observable>; + ciphers$: Observable>; + cipherViews$: Observable>; + addEditCipherInfo$: Observable; + + private localDataState: ActiveUserState>; + private encryptedCiphersState: ActiveUserState>; + private decryptedCiphersState: ActiveUserState>; + private addEditCipherInfoState: ActiveUserState; + constructor( private cryptoService: CryptoService, private domainSettingsService: DomainSettingsService, @@ -73,11 +93,17 @@ export class CipherService implements CipherServiceAbstraction { private encryptService: EncryptService, private cipherFileUploadService: CipherFileUploadService, private configService: ConfigService, - ) {} + private stateProvider: StateProvider, + ) { + this.localDataState = this.stateProvider.getActive(LOCAL_DATA_KEY); + this.encryptedCiphersState = this.stateProvider.getActive(ENCRYPTED_CIPHERS); + this.decryptedCiphersState = this.stateProvider.getActive(DECRYPTED_CIPHERS); + this.addEditCipherInfoState = this.stateProvider.getActive(ADD_EDIT_CIPHER_INFO_KEY); - async getDecryptedCipherCache(): Promise { - const decryptedCiphers = await this.stateService.getDecryptedCiphers(); - return decryptedCiphers; + this.localData$ = this.localDataState.state$; + this.ciphers$ = this.encryptedCiphersState.state$; + this.cipherViews$ = this.decryptedCiphersState.state$; + this.addEditCipherInfo$ = this.addEditCipherInfoState.state$; } async setDecryptedCipherCache(value: CipherView[]) { @@ -85,7 +111,7 @@ export class CipherService implements CipherServiceAbstraction { // if we cache it then we may accidentially return it when it's not right, we'd rather try decryption again. // We still want to set null though, that is the indicator that the cache isn't valid and we should do decryption. if (value == null || value.length !== 0) { - await this.stateService.setDecryptedCiphers(value); + await this.setDecryptedCiphers(value); } if (this.searchService != null) { if (value == null) { @@ -96,6 +122,14 @@ export class CipherService implements CipherServiceAbstraction { } } + private async setDecryptedCiphers(value: CipherView[]) { + const cipherViews: { [id: string]: CipherView } = {}; + value?.forEach((c) => { + cipherViews[c.id] = c; + }); + await this.decryptedCiphersState.update(() => cipherViews); + } + async clearCache(userId?: string): Promise { await this.clearDecryptedCiphersState(userId); } @@ -268,24 +302,27 @@ export class CipherService implements CipherServiceAbstraction { } async get(id: string): Promise { - const ciphers = await this.stateService.getEncryptedCiphers(); + const ciphers = await firstValueFrom(this.ciphers$); // eslint-disable-next-line if (ciphers == null || !ciphers.hasOwnProperty(id)) { return null; } - const localData = await this.stateService.getLocalData(); - return new Cipher(ciphers[id], localData ? localData[id] : null); + const localData = await firstValueFrom(this.localData$); + const cipherId = id as CipherId; + + return new Cipher(ciphers[cipherId], localData ? localData[cipherId] : null); } async getAll(): Promise { - const localData = await this.stateService.getLocalData(); - const ciphers = await this.stateService.getEncryptedCiphers(); + const localData = await firstValueFrom(this.localData$); + const ciphers = await firstValueFrom(this.ciphers$); const response: Cipher[] = []; for (const id in ciphers) { // eslint-disable-next-line if (ciphers.hasOwnProperty(id)) { - response.push(new Cipher(ciphers[id], localData ? localData[id] : null)); + const cipherId = id as CipherId; + response.push(new Cipher(ciphers[cipherId], localData ? localData[cipherId] : null)); } } return response; @@ -293,12 +330,23 @@ export class CipherService implements CipherServiceAbstraction { @sequentialize(() => "getAllDecrypted") async getAllDecrypted(): Promise { - if ((await this.getDecryptedCipherCache()) != null) { + let decCiphers = await this.getDecryptedCiphers(); + if (decCiphers != null && decCiphers.length !== 0) { await this.reindexCiphers(); - return await this.getDecryptedCipherCache(); + return await this.getDecryptedCiphers(); } - const ciphers = await this.getAll(); + decCiphers = await this.decryptCiphers(await this.getAll()); + + await this.setDecryptedCipherCache(decCiphers); + return decCiphers; + } + + private async getDecryptedCiphers() { + return Object.values(await firstValueFrom(this.cipherViews$)); + } + + private async decryptCiphers(ciphers: Cipher[]) { const orgKeys = await this.cryptoService.getOrgKeys(); const userKey = await this.cryptoService.getUserKeyWithLegacySupport(); if (Object.keys(orgKeys).length === 0 && userKey == null) { @@ -326,7 +374,6 @@ export class CipherService implements CipherServiceAbstraction { .flat() .sort(this.getLocaleSortingFunction()); - await this.setDecryptedCipherCache(decCiphers); return decCiphers; } @@ -336,7 +383,7 @@ export class CipherService implements CipherServiceAbstraction { this.searchService != null && ((await firstValueFrom(this.searchService.indexedEntityId$)) ?? userId) !== userId; if (reindexRequired) { - await this.searchService.indexCiphers(await this.getDecryptedCipherCache(), userId); + await this.searchService.indexCiphers(await this.getDecryptedCiphers(), userId); } } @@ -448,22 +495,24 @@ export class CipherService implements CipherServiceAbstraction { } async updateLastUsedDate(id: string): Promise { - let ciphersLocalData = await this.stateService.getLocalData(); + let ciphersLocalData = await firstValueFrom(this.localData$); + if (!ciphersLocalData) { ciphersLocalData = {}; } - if (ciphersLocalData[id]) { - ciphersLocalData[id].lastUsedDate = new Date().getTime(); + const cipherId = id as CipherId; + if (ciphersLocalData[cipherId]) { + ciphersLocalData[cipherId].lastUsedDate = new Date().getTime(); } else { - ciphersLocalData[id] = { + ciphersLocalData[cipherId] = { lastUsedDate: new Date().getTime(), }; } - await this.stateService.setLocalData(ciphersLocalData); + await this.localDataState.update(() => ciphersLocalData); - const decryptedCipherCache = await this.stateService.getDecryptedCiphers(); + const decryptedCipherCache = await this.getDecryptedCiphers(); if (!decryptedCipherCache) { return; } @@ -471,30 +520,32 @@ export class CipherService implements CipherServiceAbstraction { for (let i = 0; i < decryptedCipherCache.length; i++) { const cached = decryptedCipherCache[i]; if (cached.id === id) { - cached.localData = ciphersLocalData[id]; + cached.localData = ciphersLocalData[id as CipherId]; break; } } - await this.stateService.setDecryptedCiphers(decryptedCipherCache); + await this.setDecryptedCiphers(decryptedCipherCache); } async updateLastLaunchedDate(id: string): Promise { - let ciphersLocalData = await this.stateService.getLocalData(); + let ciphersLocalData = await firstValueFrom(this.localData$); + if (!ciphersLocalData) { ciphersLocalData = {}; } - if (ciphersLocalData[id]) { - ciphersLocalData[id].lastLaunched = new Date().getTime(); + const cipherId = id as CipherId; + if (ciphersLocalData[cipherId]) { + ciphersLocalData[cipherId].lastLaunched = new Date().getTime(); } else { - ciphersLocalData[id] = { + ciphersLocalData[cipherId] = { lastUsedDate: new Date().getTime(), }; } - await this.stateService.setLocalData(ciphersLocalData); + await this.localDataState.update(() => ciphersLocalData); - const decryptedCipherCache = await this.stateService.getDecryptedCiphers(); + const decryptedCipherCache = await this.getDecryptedCiphers(); if (!decryptedCipherCache) { return; } @@ -502,11 +553,11 @@ export class CipherService implements CipherServiceAbstraction { for (let i = 0; i < decryptedCipherCache.length; i++) { const cached = decryptedCipherCache[i]; if (cached.id === id) { - cached.localData = ciphersLocalData[id]; + cached.localData = ciphersLocalData[id as CipherId]; break; } } - await this.stateService.setDecryptedCiphers(decryptedCipherCache); + await this.setDecryptedCiphers(decryptedCipherCache); } async saveNeverDomain(domain: string): Promise { @@ -711,7 +762,7 @@ export class CipherService implements CipherServiceAbstraction { await this.apiService.send("POST", "/ciphers/bulk-collections", request, true, false); // Update the local state - const ciphers = await this.stateService.getEncryptedCiphers(); + const ciphers = await firstValueFrom(this.ciphers$); for (const id of cipherIds) { const cipher = ciphers[id]; @@ -728,30 +779,29 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); + await this.encryptedCiphersState.update(() => ciphers); } async upsert(cipher: CipherData | CipherData[]): Promise { - let ciphers = await this.stateService.getEncryptedCiphers(); - if (ciphers == null) { - ciphers = {}; - } - - if (cipher instanceof CipherData) { - const c = cipher as CipherData; - ciphers[c.id] = c; - } else { - (cipher as CipherData[]).forEach((c) => { - ciphers[c.id] = c; - }); - } - - await this.replace(ciphers); + const ciphers = cipher instanceof CipherData ? [cipher] : cipher; + await this.updateEncryptedCipherState((current) => { + ciphers.forEach((c) => current[c.id as CipherId]); + return current; + }); } async replace(ciphers: { [id: string]: CipherData }): Promise { + await this.updateEncryptedCipherState(() => ciphers); + } + + private async updateEncryptedCipherState( + update: (current: Record) => Record, + ) { await this.clearDecryptedCiphersState(); - await this.stateService.setEncryptedCiphers(ciphers); + await this.encryptedCiphersState.update((current) => { + const result = update(current ?? {}); + return result; + }); } async clear(userId?: string): Promise { @@ -762,7 +812,7 @@ export class CipherService implements CipherServiceAbstraction { async moveManyWithServer(ids: string[], folderId: string): Promise { await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId)); - let ciphers = await this.stateService.getEncryptedCiphers(); + let ciphers = await firstValueFrom(this.ciphers$); if (ciphers == null) { ciphers = {}; } @@ -770,33 +820,34 @@ export class CipherService implements CipherServiceAbstraction { ids.forEach((id) => { // eslint-disable-next-line if (ciphers.hasOwnProperty(id)) { - ciphers[id].folderId = folderId; + ciphers[id as CipherId].folderId = folderId; } }); await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); + await this.encryptedCiphersState.update(() => ciphers); } async delete(id: string | string[]): Promise { - const ciphers = await this.stateService.getEncryptedCiphers(); + const ciphers = await firstValueFrom(this.ciphers$); if (ciphers == null) { return; } if (typeof id === "string") { - if (ciphers[id] == null) { + const cipherId = id as CipherId; + if (ciphers[cipherId] == null) { return; } - delete ciphers[id]; + delete ciphers[cipherId]; } else { - (id as string[]).forEach((i) => { + (id as CipherId[]).forEach((i) => { delete ciphers[i]; }); } await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); + await this.encryptedCiphersState.update(() => ciphers); } async deleteWithServer(id: string, asAdmin = false): Promise { @@ -820,21 +871,26 @@ export class CipherService implements CipherServiceAbstraction { } async deleteAttachment(id: string, attachmentId: string): Promise { - const ciphers = await this.stateService.getEncryptedCiphers(); - + let ciphers = await firstValueFrom(this.ciphers$); + const cipherId = id as CipherId; // eslint-disable-next-line - if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[id].attachments == null) { + if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[cipherId].attachments == null) { return; } - for (let i = 0; i < ciphers[id].attachments.length; i++) { - if (ciphers[id].attachments[i].id === attachmentId) { - ciphers[id].attachments.splice(i, 1); + for (let i = 0; i < ciphers[cipherId].attachments.length; i++) { + if (ciphers[cipherId].attachments[i].id === attachmentId) { + ciphers[cipherId].attachments.splice(i, 1); } } await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); + await this.encryptedCiphersState.update(() => { + if (ciphers == null) { + ciphers = {}; + } + return ciphers; + }); } async deleteAttachmentWithServer(id: string, attachmentId: string): Promise { @@ -917,12 +973,12 @@ export class CipherService implements CipherServiceAbstraction { } async softDelete(id: string | string[]): Promise { - const ciphers = await this.stateService.getEncryptedCiphers(); + let ciphers = await firstValueFrom(this.ciphers$); if (ciphers == null) { return; } - const setDeletedDate = (cipherId: string) => { + const setDeletedDate = (cipherId: CipherId) => { if (ciphers[cipherId] == null) { return; } @@ -930,13 +986,18 @@ export class CipherService implements CipherServiceAbstraction { }; if (typeof id === "string") { - setDeletedDate(id); + setDeletedDate(id as CipherId); } else { (id as string[]).forEach(setDeletedDate); } await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); + await this.encryptedCiphersState.update(() => { + if (ciphers == null) { + ciphers = {}; + } + return ciphers; + }); } async softDeleteWithServer(id: string, asAdmin = false): Promise { @@ -963,17 +1024,18 @@ export class CipherService implements CipherServiceAbstraction { async restore( cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[], ) { - const ciphers = await this.stateService.getEncryptedCiphers(); + let ciphers = await firstValueFrom(this.ciphers$); if (ciphers == null) { return; } const clearDeletedDate = (c: { id: string; revisionDate: string }) => { - if (ciphers[c.id] == null) { + const cipherId = c.id as CipherId; + if (ciphers[cipherId] == null) { return; } - ciphers[c.id].deletedDate = null; - ciphers[c.id].revisionDate = c.revisionDate; + ciphers[cipherId].deletedDate = null; + ciphers[cipherId].revisionDate = c.revisionDate; }; if (cipher.constructor.name === Array.name) { @@ -983,7 +1045,12 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); + await this.encryptedCiphersState.update(() => { + if (ciphers == null) { + ciphers = {}; + } + return ciphers; + }); } async restoreWithServer(id: string, asAdmin = false): Promise { @@ -1025,6 +1092,10 @@ export class CipherService implements CipherServiceAbstraction { ); } + async setAddEditCipherInfo(value: AddEditCipherInfo) { + await this.addEditCipherInfoState.update(() => value); + } + // Helpers // In the case of a cipher that is being shared with an organization, we want to decrypt the @@ -1350,11 +1421,11 @@ export class CipherService implements CipherServiceAbstraction { } private async clearEncryptedCiphersState(userId?: string) { - await this.stateService.setEncryptedCiphers(null, { userId: userId }); + await this.encryptedCiphersState.update(() => ({})); } private async clearDecryptedCiphersState(userId?: string) { - await this.stateService.setDecryptedCiphers(null, { userId: userId }); + await this.setDecryptedCiphers(null); this.clearSortedCiphers(); } diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 88595720e2..8c3be9abe8 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -8,7 +8,6 @@ import { FakeStateProvider } from "../../../../spec/fake-state-provider"; import { CryptoService } from "../../../platform/abstractions/crypto.service"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; -import { StateService } from "../../../platform/abstractions/state.service"; import { Utils } from "../../../platform/misc/utils"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -27,7 +26,6 @@ describe("Folder Service", () => { let encryptService: MockProxy; let i18nService: MockProxy; let cipherService: MockProxy; - let stateService: MockProxy; let stateProvider: FakeStateProvider; const mockUserId = Utils.newGuid() as UserId; @@ -39,7 +37,6 @@ describe("Folder Service", () => { encryptService = mock(); i18nService = mock(); cipherService = mock(); - stateService = mock(); accountService = mockAccountServiceWith(mockUserId); stateProvider = new FakeStateProvider(accountService); @@ -52,13 +49,7 @@ describe("Folder Service", () => { ); encryptService.decryptToUtf8.mockResolvedValue("DEC"); - folderService = new FolderService( - cryptoService, - i18nService, - cipherService, - stateService, - stateProvider, - ); + folderService = new FolderService(cryptoService, i18nService, cipherService, stateProvider); folderState = stateProvider.activeUser.getFake(FOLDER_ENCRYPTED_FOLDERS); diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index afe3b01c68..584567aee8 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -2,17 +2,16 @@ import { Observable, firstValueFrom, map } from "rxjs"; import { CryptoService } from "../../../platform/abstractions/crypto.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; -import { StateService } from "../../../platform/abstractions/state.service"; import { Utils } from "../../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { ActiveUserState, DerivedState, StateProvider } from "../../../platform/state"; import { UserId } from "../../../types/guid"; import { CipherService } from "../../../vault/abstractions/cipher.service"; import { InternalFolderService as InternalFolderServiceAbstraction } from "../../../vault/abstractions/folder/folder.service.abstraction"; -import { CipherData } from "../../../vault/models/data/cipher.data"; import { FolderData } from "../../../vault/models/data/folder.data"; import { Folder } from "../../../vault/models/domain/folder"; import { FolderView } from "../../../vault/models/view/folder.view"; +import { Cipher } from "../../models/domain/cipher"; import { FOLDER_DECRYPTED_FOLDERS, FOLDER_ENCRYPTED_FOLDERS } from "../key-state/folder.state"; export class FolderService implements InternalFolderServiceAbstraction { @@ -26,7 +25,6 @@ export class FolderService implements InternalFolderServiceAbstraction { private cryptoService: CryptoService, private i18nService: I18nService, private cipherService: CipherService, - private stateService: StateService, private stateProvider: StateProvider, ) { this.encryptedFoldersState = this.stateProvider.getActive(FOLDER_ENCRYPTED_FOLDERS); @@ -144,9 +142,9 @@ export class FolderService implements InternalFolderServiceAbstraction { }); // Items in a deleted folder are re-assigned to "No Folder" - const ciphers = await this.stateService.getEncryptedCiphers(); + const ciphers = await this.cipherService.getAll(); if (ciphers != null) { - const updates: CipherData[] = []; + const updates: Cipher[] = []; for (const cId in ciphers) { if (ciphers[cId].folderId === id) { ciphers[cId].folderId = null; @@ -156,7 +154,7 @@ export class FolderService implements InternalFolderServiceAbstraction { if (updates.length > 0) { // 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.cipherService.upsert(updates); + this.cipherService.upsert(updates.map((c) => c.toCipherData())); } } } diff --git a/libs/common/src/vault/services/key-state/ciphers.state.ts b/libs/common/src/vault/services/key-state/ciphers.state.ts new file mode 100644 index 0000000000..71da4c2333 --- /dev/null +++ b/libs/common/src/vault/services/key-state/ciphers.state.ts @@ -0,0 +1,52 @@ +import { Jsonify } from "type-fest"; + +import { + CIPHERS_DISK, + CIPHERS_DISK_LOCAL, + CIPHERS_MEMORY, + KeyDefinition, +} from "../../../platform/state"; +import { CipherId } from "../../../types/guid"; +import { CipherData } from "../../models/data/cipher.data"; +import { LocalData } from "../../models/data/local.data"; +import { CipherView } from "../../models/view/cipher.view"; +import { AddEditCipherInfo } from "../../types/add-edit-cipher-info"; + +export const ENCRYPTED_CIPHERS = KeyDefinition.record(CIPHERS_DISK, "ciphers", { + deserializer: (obj: Jsonify) => CipherData.fromJSON(obj), +}); + +export const DECRYPTED_CIPHERS = KeyDefinition.record( + CIPHERS_MEMORY, + "decryptedCiphers", + { + deserializer: (cipher: Jsonify) => CipherView.fromJSON(cipher), + }, +); + +export const LOCAL_DATA_KEY = new KeyDefinition>( + CIPHERS_DISK_LOCAL, + "localData", + { + deserializer: (localData) => localData, + }, +); + +export const ADD_EDIT_CIPHER_INFO_KEY = new KeyDefinition( + CIPHERS_MEMORY, + "addEditCipherInfo", + { + deserializer: (addEditCipherInfo: AddEditCipherInfo) => { + if (addEditCipherInfo == null) { + return null; + } + + const cipher = + addEditCipherInfo?.cipher.toJSON != null + ? addEditCipherInfo.cipher + : CipherView.fromJSON(addEditCipherInfo?.cipher as Jsonify); + + return { cipher, collectionIds: addEditCipherInfo.collectionIds }; + }, + }, +); From cbb7e1840d54882fa67530b04adb0600b852ff79 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Tue, 16 Apr 2024 19:39:31 +0200 Subject: [PATCH 02/86] [PM-2570] [PM-4649] Update change master password UI (#8416) * Update the change master password dialog on browser Change text to remove the mention of the bitwarden.com web vault Change icon to show it's external link Changes based on Figma attached to PM-2570 * Update the change master password dialog on desktop Change text to remove the mention of the bitwarden.com web vault Changes based on Figma attached to PM-2570 and to replicate what is done on browser --------- Co-authored-by: Daniel James Smith --- apps/browser/src/_locales/en/messages.json | 12 ++++++------ .../src/popup/settings/settings.component.html | 2 +- .../browser/src/popup/settings/settings.component.ts | 5 +++-- apps/desktop/src/locales/en/messages.json | 7 +++++-- apps/desktop/src/main/menu/menu.account.ts | 8 ++++---- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 4108db3996..5e941083df 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, diff --git a/apps/browser/src/popup/settings/settings.component.html b/apps/browser/src/popup/settings/settings.component.html index f099528918..98c218b0db 100644 --- a/apps/browser/src/popup/settings/settings.component.html +++ b/apps/browser/src/popup/settings/settings.component.html @@ -153,7 +153,7 @@ *ngIf="showChangeMasterPass" >
{{ "changeMasterPassword" | i18n }}
- + + + + + diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog.component.ts new file mode 100644 index 0000000000..41d0ad7e7a --- /dev/null +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog.component.ts @@ -0,0 +1,110 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject, ViewChild } from "@angular/core"; +import { FormGroup } from "@angular/forms"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction"; +import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { DialogService } from "@bitwarden/components"; + +import { PaymentComponent } from "./payment.component"; +import { TaxInfoComponent } from "./tax-info.component"; + +export interface AdjustPaymentDialogData { + organizationId: string; + currentType: PaymentMethodType; +} + +export enum AdjustPaymentDialogResult { + Adjusted = "adjusted", + Cancelled = "cancelled", +} + +@Component({ + templateUrl: "adjust-payment-dialog.component.html", +}) +export class AdjustPaymentDialogComponent { + @ViewChild(PaymentComponent, { static: true }) paymentComponent: PaymentComponent; + @ViewChild(TaxInfoComponent, { static: true }) taxInfoComponent: TaxInfoComponent; + + organizationId: string; + currentType: PaymentMethodType; + paymentMethodType = PaymentMethodType; + + protected DialogResult = AdjustPaymentDialogResult; + protected formGroup = new FormGroup({}); + + constructor( + private dialogRef: DialogRef, + @Inject(DIALOG_DATA) protected data: AdjustPaymentDialogData, + private apiService: ApiService, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private logService: LogService, + private organizationApiService: OrganizationApiServiceAbstraction, + private paymentMethodWarningService: PaymentMethodWarningService, + ) { + this.organizationId = data.organizationId; + this.currentType = data.currentType; + } + + submit = async () => { + const request = new PaymentRequest(); + const response = this.paymentComponent.createPaymentToken().then((result) => { + request.paymentToken = result[0]; + request.paymentMethodType = result[1]; + request.postalCode = this.taxInfoComponent.taxInfo.postalCode; + request.country = this.taxInfoComponent.taxInfo.country; + if (this.organizationId == null) { + return this.apiService.postAccountPayment(request); + } else { + request.taxId = this.taxInfoComponent.taxInfo.taxId; + request.state = this.taxInfoComponent.taxInfo.state; + request.line1 = this.taxInfoComponent.taxInfo.line1; + request.line2 = this.taxInfoComponent.taxInfo.line2; + request.city = this.taxInfoComponent.taxInfo.city; + request.state = this.taxInfoComponent.taxInfo.state; + return this.organizationApiService.updatePayment(this.organizationId, request); + } + }); + await response; + if (this.organizationId) { + await this.paymentMethodWarningService.removeSubscriptionRisk(this.organizationId); + } + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("updatedPaymentMethod"), + ); + this.dialogRef.close(AdjustPaymentDialogResult.Adjusted); + }; + + changeCountry() { + if (this.taxInfoComponent.taxInfo.country === "US") { + this.paymentComponent.hideBank = !this.organizationId; + } else { + this.paymentComponent.hideBank = true; + if (this.paymentComponent.method === PaymentMethodType.BankAccount) { + this.paymentComponent.method = PaymentMethodType.Card; + this.paymentComponent.changeMethod(); + } + } + } +} + +/** + * Strongly typed helper to open a AdjustPaymentDialog + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ +export function openAdjustPaymentDialog( + dialogService: DialogService, + config: DialogConfig, +) { + return dialogService.open(AdjustPaymentDialogComponent, config); +} diff --git a/apps/web/src/app/billing/shared/adjust-payment.component.html b/apps/web/src/app/billing/shared/adjust-payment.component.html deleted file mode 100644 index 724e7a44c2..0000000000 --- a/apps/web/src/app/billing/shared/adjust-payment.component.html +++ /dev/null @@ -1,19 +0,0 @@ -
-
- -

- {{ (currentType != null ? "changePaymentMethod" : "addPaymentMethod") | i18n }} -

- - - - -
-
diff --git a/apps/web/src/app/billing/shared/adjust-payment.component.ts b/apps/web/src/app/billing/shared/adjust-payment.component.ts deleted file mode 100644 index 7452344141..0000000000 --- a/apps/web/src/app/billing/shared/adjust-payment.component.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Component, EventEmitter, Input, Output, ViewChild } from "@angular/core"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - -import { PaymentComponent } from "./payment.component"; -import { TaxInfoComponent } from "./tax-info.component"; - -@Component({ - selector: "app-adjust-payment", - templateUrl: "adjust-payment.component.html", -}) -export class AdjustPaymentComponent { - @ViewChild(PaymentComponent, { static: true }) paymentComponent: PaymentComponent; - @ViewChild(TaxInfoComponent, { static: true }) taxInfoComponent: TaxInfoComponent; - - @Input() currentType?: PaymentMethodType; - @Input() organizationId: string; - @Output() onAdjusted = new EventEmitter(); - @Output() onCanceled = new EventEmitter(); - - paymentMethodType = PaymentMethodType; - formPromise: Promise; - - constructor( - private apiService: ApiService, - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private logService: LogService, - private organizationApiService: OrganizationApiServiceAbstraction, - private paymentMethodWarningService: PaymentMethodWarningService, - ) {} - - async submit() { - try { - const request = new PaymentRequest(); - this.formPromise = this.paymentComponent.createPaymentToken().then((result) => { - request.paymentToken = result[0]; - request.paymentMethodType = result[1]; - request.postalCode = this.taxInfoComponent.taxInfo.postalCode; - request.country = this.taxInfoComponent.taxInfo.country; - if (this.organizationId == null) { - return this.apiService.postAccountPayment(request); - } else { - request.taxId = this.taxInfoComponent.taxInfo.taxId; - request.state = this.taxInfoComponent.taxInfo.state; - request.line1 = this.taxInfoComponent.taxInfo.line1; - request.line2 = this.taxInfoComponent.taxInfo.line2; - request.city = this.taxInfoComponent.taxInfo.city; - request.state = this.taxInfoComponent.taxInfo.state; - return this.organizationApiService.updatePayment(this.organizationId, request); - } - }); - await this.formPromise; - if (this.organizationId) { - await this.paymentMethodWarningService.removeSubscriptionRisk(this.organizationId); - } - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("updatedPaymentMethod"), - ); - this.onAdjusted.emit(); - } catch (e) { - this.logService.error(e); - } - } - - cancel() { - this.onCanceled.emit(); - } - - changeCountry() { - if (this.taxInfoComponent.taxInfo.country === "US") { - this.paymentComponent.hideBank = !this.organizationId; - } else { - this.paymentComponent.hideBank = true; - if (this.paymentComponent.method === PaymentMethodType.BankAccount) { - this.paymentComponent.method = PaymentMethodType.Card; - this.paymentComponent.changeMethod(); - } - } - } -} diff --git a/apps/web/src/app/billing/shared/billing-shared.module.ts b/apps/web/src/app/billing/shared/billing-shared.module.ts index 2f773870aa..65a651b73d 100644 --- a/apps/web/src/app/billing/shared/billing-shared.module.ts +++ b/apps/web/src/app/billing/shared/billing-shared.module.ts @@ -4,7 +4,7 @@ import { HeaderModule } from "../../layouts/header/header.module"; import { SharedModule } from "../../shared"; import { AddCreditComponent } from "./add-credit.component"; -import { AdjustPaymentComponent } from "./adjust-payment.component"; +import { AdjustPaymentDialogComponent } from "./adjust-payment-dialog.component"; import { AdjustStorageComponent } from "./adjust-storage.component"; import { BillingHistoryComponent } from "./billing-history.component"; import { OffboardingSurveyComponent } from "./offboarding-survey.component"; @@ -18,7 +18,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; imports: [SharedModule, PaymentComponent, TaxInfoComponent, HeaderModule], declarations: [ AddCreditComponent, - AdjustPaymentComponent, + AdjustPaymentDialogComponent, AdjustStorageComponent, BillingHistoryComponent, PaymentMethodComponent, diff --git a/apps/web/src/app/billing/shared/payment-method.component.html b/apps/web/src/app/billing/shared/payment-method.component.html index cfe98178b0..5f78294fa6 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.html +++ b/apps/web/src/app/billing/shared/payment-method.component.html @@ -15,7 +15,7 @@
- +

{{ "paymentMethod" | i18n }}

@@ -102,23 +102,9 @@ {{ paymentSource.description }}

- - -

{{ "paymentChargedWithUnpaidSubscription" | i18n }}

{{ "taxInformation" | i18n }}

diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index d2b65968c3..fee97cb912 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, FormControl, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; +import { lastValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; @@ -14,6 +15,10 @@ 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 { + AdjustPaymentDialogResult, + openAdjustPaymentDialog, +} from "./adjust-payment-dialog.component"; import { TaxInfoComponent } from "./tax-info.component"; @Component({ @@ -25,7 +30,6 @@ export class PaymentMethodComponent implements OnInit { loading = false; firstLoaded = false; - showAdjustPayment = false; showAddCredit = false; billing: BillingPaymentResponse; org: OrganizationSubscriptionResponse; @@ -120,18 +124,18 @@ export class PaymentMethodComponent implements OnInit { } } - changePayment() { - this.showAdjustPayment = true; - } - - closePayment(load: boolean) { - this.showAdjustPayment = false; - if (load) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.load(); + changePayment = async () => { + const dialogRef = openAdjustPaymentDialog(this.dialogService, { + data: { + organizationId: this.organizationId, + currentType: this.paymentSource !== null ? this.paymentSource.type : null, + }, + }); + const result = await lastValueFrom(dialogRef.closed); + if (result === AdjustPaymentDialogResult.Adjusted) { + await this.load(); } - } + }; async verifyBank() { if (this.loading || !this.forOrganization) { From 5d3541dd6379f422f0f740c3702367b58c8898f8 Mon Sep 17 00:00:00 2001 From: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> Date: Wed, 17 Apr 2024 01:09:05 +0530 Subject: [PATCH 07/86] [PM-5013] migrate change plan component (#8407) * change plan component migration * change plan component migration --------- Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com> --- .../organizations/change-plan.component.html | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/web/src/app/billing/organizations/change-plan.component.html b/apps/web/src/app/billing/organizations/change-plan.component.html index b9a15be5ea..a25dde4fd3 100644 --- a/apps/web/src/app/billing/organizations/change-plan.component.html +++ b/apps/web/src/app/billing/organizations/change-plan.component.html @@ -1,10 +1,18 @@ -
-
- -

{{ "changeBillingPlan" | i18n }}

-

{{ "changeBillingPlanUpgrade" | i18n }}

+
+
+ +

{{ "changeBillingPlan" | i18n }}

+

{{ "changeBillingPlanUpgrade" | i18n }}

Date: Wed, 17 Apr 2024 01:09:53 +0530 Subject: [PATCH 08/86] [PM-5021] billing history component migration (#8042) * billing history component migration * billing history component migration * billing history component migration * billing history component migration * billing history component migration --------- Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com> --- .../billing-history-view.component.html | 11 +- .../shared/billing-history.component.html | 135 +++++++++--------- 2 files changed, 76 insertions(+), 70 deletions(-) diff --git a/apps/web/src/app/billing/individual/billing-history-view.component.html b/apps/web/src/app/billing/individual/billing-history-view.component.html index 1032558f5f..2491fc42c7 100644 --- a/apps/web/src/app/billing/individual/billing-history-view.component.html +++ b/apps/web/src/app/billing/individual/billing-history-view.component.html @@ -1,13 +1,12 @@ -
-

+
+

{{ "billingHistory" | i18n }} -

+

- {{ "loading" | i18n }} + {{ "loading" | i18n }} diff --git a/apps/web/src/app/billing/shared/billing-history.component.html b/apps/web/src/app/billing/shared/billing-history.component.html index 56a8a990d4..1719a59076 100644 --- a/apps/web/src/app/billing/shared/billing-history.component.html +++ b/apps/web/src/app/billing/shared/billing-history.component.html @@ -1,65 +1,72 @@ -

{{ "invoices" | i18n }}

-

{{ "noInvoices" | i18n }}

- - - - - + + + + + + + + + +

{{ "transactions" | i18n }}

+

+ {{ "noTransactions" | i18n }} +

+ + +
+ + + + - - - - -
{{ i.date | date: "mediumDate" }} - +

{{ "invoices" | i18n }}

+

{{ "noInvoices" | i18n }}

+ + +
{{ i.date | date: "mediumDate" }} + + + + {{ "invoiceNumber" | i18n: i.number }} + {{ i.amount | currency: "$" }} + + + {{ "paid" | i18n }} + + + + {{ "unpaid" | i18n }} + +
{{ t.createdDate | date: "mediumDate" }} + + {{ "chargeNoun" | i18n }} + + {{ "refundNoun" | i18n }} + + + {{ t.details }} + - - - {{ "invoiceNumber" | i18n: i.number }} - {{ i.amount | currency: "$" }} - - - {{ "paid" | i18n }} - - - - {{ "unpaid" | i18n }} - -
-

{{ "transactions" | i18n }}

-

{{ "noTransactions" | i18n }}

- - - - - - - - - -
{{ t.createdDate | date: "mediumDate" }} - - {{ "chargeNoun" | i18n }} - - {{ "refundNoun" | i18n }} - - - {{ t.details }} - - {{ t.amount | currency: "$" }} -
-* {{ "chargesStatement" | i18n: "BITWARDEN" }} + {{ t.amount | currency: "$" }} + + + + + * {{ "chargesStatement" | i18n: "BITWARDEN" }} + From 9ecf384176c21fb43117a5b324e37f9d218451f0 Mon Sep 17 00:00:00 2001 From: KiruthigaManivannan <162679756+KiruthigaManivannan@users.noreply.github.com> Date: Wed, 17 Apr 2024 01:10:10 +0530 Subject: [PATCH 09/86] [PM-5020] Adjust Storage component migration (#8301) * Migrated Add Storage component * PM-5020 Addressed review comments for Adjust Storage component * PM-5020 Changes done in dialog css * PM-5020 Latest review comments addressed * PM-5020 Add storage submit action changes done * PM-5020 Moved the paragraph to top of dialog content --- .../user-subscription.component.html | 13 +- .../individual/user-subscription.component.ts | 32 ++-- ...nization-subscription-cloud.component.html | 25 +-- ...ganization-subscription-cloud.component.ts | 35 ++-- .../shared/adjust-storage.component.html | 72 ++++---- .../shared/adjust-storage.component.ts | 163 ++++++++++-------- 6 files changed, 180 insertions(+), 160 deletions(-) diff --git a/apps/web/src/app/billing/individual/user-subscription.component.html b/apps/web/src/app/billing/individual/user-subscription.component.html index 874983df84..380116e81b 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.html +++ b/apps/web/src/app/billing/individual/user-subscription.component.html @@ -170,8 +170,8 @@
-
-
-
diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index 7d8c3a0f18..fa21317c18 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -12,6 +12,10 @@ 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 { + AdjustStorageDialogResult, + openAdjustStorageDialog, +} from "../shared/adjust-storage.component"; import { OffboardingSurveyDialogResultType, openOffboardingSurvey, @@ -24,7 +28,6 @@ export class UserSubscriptionComponent implements OnInit { loading = false; firstLoaded = false; adjustStorageAdd = true; - showAdjustStorage = false; showUpdateLicense = false; sub: SubscriptionResponse; selfHosted = false; @@ -144,19 +147,20 @@ export class UserSubscriptionComponent implements OnInit { } } - adjustStorage(add: boolean) { - this.adjustStorageAdd = add; - this.showAdjustStorage = true; - } - - closeStorage(load: boolean) { - this.showAdjustStorage = false; - if (load) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.load(); - } - } + adjustStorage = (add: boolean) => { + return async () => { + const dialogRef = openAdjustStorageDialog(this.dialogService, { + data: { + storageGbPrice: 4, + add: add, + }, + }); + const result = await lastValueFrom(dialogRef.closed); + if (result === AdjustStorageDialogResult.Adjusted) { + await this.load(); + } + }; + }; get subscriptionMarkedForCancel() { return ( diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index b4fac65854..16641c0d52 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -175,23 +175,24 @@
-
- -
-
diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index 2173d4c0ca..9326359bd8 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -18,6 +18,10 @@ 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 { + AdjustStorageDialogResult, + openAdjustStorageDialog, +} from "../shared/adjust-storage.component"; import { OffboardingSurveyDialogResultType, openOffboardingSurvey, @@ -36,8 +40,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy userOrg: Organization; showChangePlan = false; showDownloadLicense = false; - adjustStorageAdd = true; - showAdjustStorage = false; hasBillingSyncToken: boolean; showAdjustSecretsManager = false; showSecretsManagerSubscribe = false; @@ -361,19 +363,22 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy this.load(); } - adjustStorage(add: boolean) { - this.adjustStorageAdd = add; - this.showAdjustStorage = true; - } - - closeStorage(load: boolean) { - this.showAdjustStorage = false; - if (load) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.load(); - } - } + adjustStorage = (add: boolean) => { + return async () => { + const dialogRef = openAdjustStorageDialog(this.dialogService, { + data: { + storageGbPrice: this.storageGbPrice, + add: add, + organizationId: this.organizationId, + interval: this.billingInterval, + }, + }); + const result = await lastValueFrom(dialogRef.closed); + if (result === AdjustStorageDialogResult.Adjusted) { + await this.load(); + } + }; + }; removeSponsorship = async () => { const confirmed = await this.dialogService.openSimpleDialog({ diff --git a/apps/web/src/app/billing/shared/adjust-storage.component.html b/apps/web/src/app/billing/shared/adjust-storage.component.html index aa6daca335..a597a3ae5e 100644 --- a/apps/web/src/app/billing/shared/adjust-storage.component.html +++ b/apps/web/src/app/billing/shared/adjust-storage.component.html @@ -1,43 +1,35 @@ -
-
- -

{{ (add ? "addStorage" : "removeStorage") | i18n }}

-
-
- - + + + +

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

+
+ + {{ (add ? "gbStorageAdd" : "gbStorageRemove") | i18n }} + + + {{ "total" | i18n }}: + {{ formGroup.get("storageAdjustment").value || 0 }} GB × + {{ storageGbPrice | currency: "$" }} = {{ adjustedStorageTotal | currency: "$" }} /{{ + interval | i18n + }} + +
-
-
- {{ "total" | i18n }}: {{ storageAdjustment || 0 }} GB × - {{ storageGbPrice | currency: "$" }} = {{ adjustedStorageTotal | currency: "$" }} /{{ - interval | i18n - }} -
- - - - {{ (add ? "storageAddNote" : "storageRemoveNote") | i18n }} - -
+ + + + + + diff --git a/apps/web/src/app/billing/shared/adjust-storage.component.ts b/apps/web/src/app/billing/shared/adjust-storage.component.ts index 25462c2829..fcdbc3437d 100644 --- a/apps/web/src/app/billing/shared/adjust-storage.component.ts +++ b/apps/web/src/app/billing/shared/adjust-storage.component.ts @@ -1,4 +1,6 @@ -import { Component, EventEmitter, Input, Output, ViewChild } from "@angular/core"; +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject, ViewChild } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -8,27 +10,45 @@ import { StorageRequest } from "@bitwarden/common/models/request/storage.request import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { DialogService } from "@bitwarden/components"; import { PaymentComponent } from "./payment.component"; +export interface AdjustStorageDialogData { + storageGbPrice: number; + add: boolean; + organizationId?: string; + interval?: string; +} + +export enum AdjustStorageDialogResult { + Adjusted = "adjusted", + Cancelled = "cancelled", +} + @Component({ - selector: "app-adjust-storage", templateUrl: "adjust-storage.component.html", }) export class AdjustStorageComponent { - @Input() storageGbPrice = 0; - @Input() add = true; - @Input() organizationId: string; - @Input() interval = "year"; - @Output() onAdjusted = new EventEmitter(); - @Output() onCanceled = new EventEmitter(); + storageGbPrice: number; + add: boolean; + organizationId: string; + interval: string; @ViewChild(PaymentComponent, { static: true }) paymentComponent: PaymentComponent; - storageAdjustment = 0; - formPromise: Promise; + protected DialogResult = AdjustStorageDialogResult; + protected formGroup = new FormGroup({ + storageAdjustment: new FormControl(0, [ + Validators.required, + Validators.min(0), + Validators.max(99), + ]), + }); constructor( + private dialogRef: DialogRef, + @Inject(DIALOG_DATA) protected data: AdjustStorageDialogData, private apiService: ApiService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, @@ -36,69 +56,74 @@ export class AdjustStorageComponent { private activatedRoute: ActivatedRoute, private logService: LogService, private organizationApiService: OrganizationApiServiceAbstraction, - ) {} + ) { + this.storageGbPrice = data.storageGbPrice; + this.add = data.add; + this.organizationId = data.organizationId; + this.interval = data.interval || "year"; + } - async submit() { - try { - const request = new StorageRequest(); - request.storageGbAdjustment = this.storageAdjustment; - if (!this.add) { - request.storageGbAdjustment *= -1; - } - - let paymentFailed = false; - const action = async () => { - let response: Promise; - if (this.organizationId == null) { - response = this.formPromise = this.apiService.postAccountStorage(request); - } else { - response = this.formPromise = this.organizationApiService.updateStorage( - this.organizationId, - request, - ); - } - const result = await response; - if (result != null && result.paymentIntentClientSecret != null) { - try { - await this.paymentComponent.handleStripeCardPayment( - result.paymentIntentClientSecret, - null, - ); - } catch { - paymentFailed = true; - } - } - }; - this.formPromise = action(); - await this.formPromise; - this.onAdjusted.emit(this.storageAdjustment); - if (paymentFailed) { - this.platformUtilsService.showToast( - "warning", - null, - this.i18nService.t("couldNotChargeCardPayInvoice"), - { timeout: 10000 }, - ); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["../billing"], { relativeTo: this.activatedRoute }); - } else { - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()), - ); - } - } catch (e) { - this.logService.error(e); + submit = async () => { + const request = new StorageRequest(); + request.storageGbAdjustment = this.formGroup.value.storageAdjustment; + if (!this.add) { + request.storageGbAdjustment *= -1; } - } - cancel() { - this.onCanceled.emit(); - } + let paymentFailed = false; + const action = async () => { + let response: Promise; + if (this.organizationId == null) { + response = this.apiService.postAccountStorage(request); + } else { + response = this.organizationApiService.updateStorage(this.organizationId, request); + } + const result = await response; + if (result != null && result.paymentIntentClientSecret != null) { + try { + await this.paymentComponent.handleStripeCardPayment( + result.paymentIntentClientSecret, + null, + ); + } catch { + paymentFailed = true; + } + } + }; + await action(); + this.dialogRef.close(AdjustStorageDialogResult.Adjusted); + if (paymentFailed) { + this.platformUtilsService.showToast( + "warning", + null, + this.i18nService.t("couldNotChargeCardPayInvoice"), + { timeout: 10000 }, + ); + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["../billing"], { relativeTo: this.activatedRoute }); + } else { + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()), + ); + } + }; get adjustedStorageTotal(): number { - return this.storageGbPrice * this.storageAdjustment; + return this.storageGbPrice * this.formGroup.value.storageAdjustment; } } + +/** + * Strongly typed helper to open an AdjustStorageDialog + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ +export function openAdjustStorageDialog( + dialogService: DialogService, + config: DialogConfig, +) { + return dialogService.open(AdjustStorageComponent, config); +} From 65f1bd2e3a258a42dc2a956e0f936452d610d268 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Tue, 16 Apr 2024 16:35:53 -0500 Subject: [PATCH 10/86] [PM-7527] Get MV3 build artifacts in main branch with clear messaging that that the build is not to be released (#8771) * [PM-7527] Get MV3 build artifacts in main branch with clear messaging that that the build is not to be released * Update .github/workflows/build-browser.yml Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> --------- Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> --- .github/workflows/build-browser.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 585a888ae1..4fb72a47de 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -189,12 +189,12 @@ jobs: path: browser-source/apps/browser/dist/dist-chrome.zip if-no-files-found: error - # - name: Upload Chrome MV3 artifact - # uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - # with: - # name: dist-chrome-MV3-${{ env._BUILD_NUMBER }}.zip - # path: browser-source/apps/browser/dist/dist-chrome-mv3.zip - # if-no-files-found: error + - name: Upload Chrome MV3 artifact (DO NOT USE FOR PROD) + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: DO-NOT-USE-FOR-PROD-dist-chrome-MV3-${{ env._BUILD_NUMBER }}.zip + path: browser-source/apps/browser/dist/dist-chrome-mv3.zip + if-no-files-found: error - name: Upload Firefox artifact uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 From f45eec1a4f42329efc909f54778b559f7f1ae4fc Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:31:48 +1000 Subject: [PATCH 11/86] [AC-2169] Group modal - limit admin access - members tab (#8650) * Restrict user from adding themselves to existing group --- .../manage/group-add-edit.component.html | 7 +- .../manage/group-add-edit.component.ts | 118 +++++++++++++----- apps/web/src/locales/en/messages.json | 3 + libs/common/src/abstractions/api.service.ts | 1 - libs/common/src/services/api.service.ts | 10 -- 5 files changed, 98 insertions(+), 41 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html index c6bfb94557..3afb816e14 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html @@ -31,7 +31,12 @@ -

{{ "editGroupMembersDesc" | i18n }}

+

+ {{ "editGroupMembersDesc" | i18n }} + + {{ "restrictedGroupAccessDesc" | i18n }} + +

= []; group: GroupView; groupForm = this.formBuilder.group({ @@ -110,6 +125,10 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { return this.params.organizationId; } + protected get editMode(): boolean { + return this.groupId != null; + } + private destroy$ = new Subject(); private get orgCollections$() { @@ -134,7 +153,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { ); } - private get orgMembers$() { + private get orgMembers$(): Observable> { return from(this.organizationUserService.getAllUsers(this.organizationId)).pipe( map((response) => response.data.map((m) => ({ @@ -145,34 +164,55 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { listName: m.name?.length > 0 ? `${m.name} (${m.email})` : m.email, labelName: m.name || m.email, status: m.status, + userId: m.userId as UserId, })), ), ); } - private get groupDetails$() { - if (!this.editMode) { - return of(undefined); - } - - return combineLatest([ - this.groupService.get(this.organizationId, this.groupId), - this.apiService.getGroupUsers(this.organizationId, this.groupId), - ]).pipe( - map(([groupView, users]) => { - groupView.members = users; - return groupView; - }), - catchError((e: unknown) => { - if (e instanceof ErrorResponse) { - this.logService.error(e.message); - } else { - this.logService.error(e.toString()); - } + private groupDetails$: Observable = of(this.editMode).pipe( + concatMap((editMode) => { + if (!editMode) { return of(undefined); - }), - ); - } + } + + return combineLatest([ + this.groupService.get(this.organizationId, this.groupId), + this.apiService.getGroupUsers(this.organizationId, this.groupId), + ]).pipe( + map(([groupView, users]) => { + groupView.members = users; + return groupView; + }), + catchError((e: unknown) => { + if (e instanceof ErrorResponse) { + this.logService.error(e.message); + } else { + this.logService.error(e.toString()); + } + return of(undefined); + }), + ); + }), + shareReplay({ refCount: false }), + ); + + restrictGroupAccess$ = combineLatest([ + this.organizationService.get$(this.organizationId), + this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1), + this.groupDetails$, + ]).pipe( + map( + ([organization, flexibleCollectionsV1Enabled, group]) => + // Feature flag conditionals + flexibleCollectionsV1Enabled && + organization.flexibleCollections && + // Business logic conditionals + !organization.allowAdminAccessToAllCollectionItems && + group !== undefined, + ), + shareReplay({ refCount: true, bufferSize: 1 }), + ); constructor( @Inject(DIALOG_DATA) private params: GroupAddEditDialogParams, @@ -188,17 +228,25 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { private changeDetectorRef: ChangeDetectorRef, private dialogService: DialogService, private organizationService: OrganizationService, + private configService: ConfigService, + private accountService: AccountService, ) { this.tabIndex = params.initialTab ?? GroupAddEditTabType.Info; } ngOnInit() { - this.editMode = this.loading = this.groupId != null; + this.loading = true; this.title = this.i18nService.t(this.editMode ? "editGroup" : "newGroup"); - combineLatest([this.orgCollections$, this.orgMembers$, this.groupDetails$]) + combineLatest([ + this.orgCollections$, + this.orgMembers$, + this.groupDetails$, + this.restrictGroupAccess$, + this.accountService.activeAccount$, + ]) .pipe(takeUntil(this.destroy$)) - .subscribe(([collections, members, group]) => { + .subscribe(([collections, members, group, restrictGroupAccess, activeAccount]) => { this.collections = collections; this.members = members; this.group = group; @@ -224,6 +272,18 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { }); } + // If the current user is not already in the group and cannot add themselves, remove them from the list + if (restrictGroupAccess) { + const organizationUserId = this.members.find((m) => m.userId === activeAccount.id).id; + const isAlreadyInGroup = this.groupForm.value.members.some( + (m) => m.id === organizationUserId, + ); + + if (!isAlreadyInGroup) { + this.members = this.members.filter((m) => m.id !== organizationUserId); + } + } + this.loading = false; }); } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index f28bff066a..a3fc234cab 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7905,5 +7905,8 @@ }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." } } diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 6962a44268..44a58403ed 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -297,7 +297,6 @@ export abstract class ApiService { ) => Promise; getGroupUsers: (organizationId: string, id: string) => Promise; - putGroupUsers: (organizationId: string, id: string, request: string[]) => Promise; deleteGroupUser: (organizationId: string, id: string, organizationUserId: string) => Promise; getSync: () => Promise; diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index c7a8f3f091..90671c4f04 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -866,16 +866,6 @@ export class ApiService implements ApiServiceAbstraction { return r; } - async putGroupUsers(organizationId: string, id: string, request: string[]): Promise { - await this.send( - "PUT", - "/organizations/" + organizationId + "/groups/" + id + "/users", - request, - true, - false, - ); - } - deleteGroupUser(organizationId: string, id: string, organizationUserId: string): Promise { return this.send( "DELETE", From 4db383850fd9f11ec531c18ee2118de8830a21e3 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:03:48 +1000 Subject: [PATCH 12/86] [AC-2172] Member modal - limit admin access (#8343) * limit admin permissions to assign members to collections that the admin doesn't have can manage permissions for --- .../member-dialog/member-dialog.component.ts | 97 ++++++++++++++----- .../vault/core/collection-admin.service.ts | 3 + .../models/response/collection.response.ts | 11 +-- 3 files changed, 81 insertions(+), 30 deletions(-) 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 752122de00..771b8cc505 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 @@ -31,6 +31,7 @@ import { CollectionView } from "@bitwarden/common/vault/models/view/collection.v import { DialogService } from "@bitwarden/components"; import { CollectionAdminService } from "../../../../../vault/core/collection-admin.service"; +import { CollectionAdminView } from "../../../../../vault/core/views/collection-admin.view"; import { CollectionAccessSelectionView, GroupService, @@ -206,25 +207,52 @@ export class MemberDialogComponent implements OnDestroy { collections: this.collectionAdminService.getAll(this.params.organizationId), userDetails: userDetails$, groups: groups$, + flexibleCollectionsV1Enabled: this.configService.getFeatureFlag$( + FeatureFlag.FlexibleCollectionsV1, + false, + ), }) .pipe(takeUntil(this.destroy$)) - .subscribe(({ organization, collections, userDetails, groups }) => { - this.setFormValidators(organization); + .subscribe( + ({ organization, collections, userDetails, groups, flexibleCollectionsV1Enabled }) => { + this.setFormValidators(organization); - this.collectionAccessItems = [].concat( - collections.map((c) => mapCollectionToAccessItemView(c)), - ); + // Groups tab: populate available groups + this.groupAccessItems = [].concat( + groups.map((g) => mapGroupToAccessItemView(g)), + ); - 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, + 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 (this.params.organizationUserId) { - this.loadOrganizationUser(userDetails, groups, collections); - } + if (userDetails != null) { + this.loadOrganizationUser( + userDetails, + groups, + collections, + organization, + flexibleCollectionsV1Enabled, + ); + } - this.loading = false; - }); + this.loading = false; + }, + ); } private setFormValidators(organization: Organization) { @@ -246,7 +274,9 @@ export class MemberDialogComponent implements OnDestroy { private loadOrganizationUser( userDetails: OrganizationUserAdminView, groups: GroupView[], - collections: CollectionView[], + collections: CollectionAdminView[], + organization: Organization, + flexibleCollectionsV1Enabled: boolean, ) { if (!userDetails) { throw new Error("Could not find user to edit."); @@ -295,13 +325,22 @@ 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, accessSelection, group), + mapCollectionToAccessItemView( + collection, + organization, + flexibleCollectionsV1Enabled, + accessSelection, + group, + ), ), ); - const accessSelections = mapToAccessSelections(userDetails); + // Set current collections and groups the user has access to (excluding collections the current user doesn't have + // permissions to change - they are included as readonly via the CollectionAccessItems) + const accessSelections = mapToAccessSelections(userDetails, this.collectionAccessItems); const groupAccessSelections = mapToGroupAccessSelections(userDetails.groups); this.formGroup.removeControl("emails"); @@ -573,6 +612,8 @@ export class MemberDialogComponent implements OnDestroy { function mapCollectionToAccessItemView( collection: CollectionView, + organization: Organization, + flexibleCollectionsV1Enabled: boolean, accessSelection?: CollectionAccessSelectionView, group?: GroupView, ): AccessItemView { @@ -581,7 +622,8 @@ function mapCollectionToAccessItemView( id: group ? `${collection.id}-${group.id}` : collection.id, labelName: collection.name, listName: collection.name, - readonly: group !== undefined, + readonly: + group !== undefined || !collection.canEdit(organization, flexibleCollectionsV1Enabled), readonlyPermission: accessSelection ? convertToPermission(accessSelection) : undefined, viaGroupName: group?.name, }; @@ -596,16 +638,23 @@ function mapGroupToAccessItemView(group: GroupView): AccessItemView { }; } -function mapToAccessSelections(user: OrganizationUserAdminView): AccessItemValue[] { +function mapToAccessSelections( + user: OrganizationUserAdminView, + items: AccessItemView[], +): AccessItemValue[] { if (user == undefined) { return []; } - return [].concat( - user.collections.map((selection) => ({ - id: selection.id, - type: AccessItemType.Collection, - permission: convertToPermission(selection), - })), + + return ( + user.collections + // The FormControl value only represents editable collection access - exclude readonly access selections + .filter((selection) => !items.find((item) => item.id == selection.id).readonly) + .map((selection) => ({ + id: selection.id, + type: AccessItemType.Collection, + permission: convertToPermission(selection), + })) ); } diff --git a/apps/web/src/app/vault/core/collection-admin.service.ts b/apps/web/src/app/vault/core/collection-admin.service.ts index 74f825e1ac..7f78ab214a 100644 --- a/apps/web/src/app/vault/core/collection-admin.service.ts +++ b/apps/web/src/app/vault/core/collection-admin.service.ts @@ -124,6 +124,9 @@ export class CollectionAdminService { view.groups = c.groups; view.users = c.users; view.assigned = c.assigned; + view.readOnly = c.readOnly; + view.hidePasswords = c.hidePasswords; + view.manage = c.manage; } return view; diff --git a/libs/common/src/vault/models/response/collection.response.ts b/libs/common/src/vault/models/response/collection.response.ts index f16fe547e0..ac4781df71 100644 --- a/libs/common/src/vault/models/response/collection.response.ts +++ b/libs/common/src/vault/models/response/collection.response.ts @@ -21,6 +21,10 @@ export class CollectionDetailsResponse extends CollectionResponse { readOnly: boolean; manage: boolean; hidePasswords: boolean; + + /** + * Flag indicating the user has been explicitly assigned to this Collection + */ assigned: boolean; constructor(response: any) { @@ -35,15 +39,10 @@ export class CollectionDetailsResponse extends CollectionResponse { } } -export class CollectionAccessDetailsResponse extends CollectionResponse { +export class CollectionAccessDetailsResponse extends CollectionDetailsResponse { groups: SelectionReadOnlyResponse[] = []; users: SelectionReadOnlyResponse[] = []; - /** - * Flag indicating the user has been explicitly assigned to this Collection - */ - assigned: boolean; - constructor(response: any) { super(response); this.assigned = this.getResponseProperty("Assigned") || false; From a72b7f3d214f6fc5d2b7b09e65a55c2fc3023521 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:07:26 +0100 Subject: [PATCH 13/86] [AC-1218] Add ability to delete Provider Portals (#8685) * initial commit * add changes from running prettier * resolve the linx issue * resolve the lint issue * resolving lint error * correct the redirect issue * resolve pr commit * Add a feature flag * move the new component to adminconsole * resolve some pr comments * move the endpoint from ApiService to providerApiService * move provider endpoints to the provider-api class * change the header * resolve some pr comments --- apps/cli/src/bw.ts | 5 + ...ify-recover-delete-provider.component.html | 34 ++++++ ...erify-recover-delete-provider.component.ts | 61 ++++++++++ .../organization-plans.component.ts | 4 +- apps/web/src/app/oss-routing.module.ts | 7 ++ .../src/app/shared/loose-components.module.ts | 3 + apps/web/src/locales/en/messages.json | 36 ++++++ .../providers/providers.module.ts | 2 + .../providers/settings/account.component.html | 105 ++++++++++-------- .../providers/settings/account.component.ts | 73 +++++++++++- .../providers/setup/setup.component.ts | 8 +- .../src/services/jslib-services.module.ts | 7 ++ libs/common/src/abstractions/api.service.ts | 7 -- .../provider-api.service.abstraction.ts | 15 +++ .../provider-verify-recover-delete.request.ts | 7 ++ .../services/provider/provider-api.service.ts | 47 ++++++++ libs/common/src/enums/feature-flag.enum.ts | 1 + libs/common/src/services/api.service.ts | 20 ---- 18 files changed, 359 insertions(+), 83 deletions(-) create mode 100644 apps/web/src/app/admin-console/providers/verify-recover-delete-provider.component.html create mode 100644 apps/web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts create mode 100644 libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts create mode 100644 libs/common/src/admin-console/models/request/provider/provider-verify-recover-delete.request.ts create mode 100644 libs/common/src/admin-console/services/provider/provider-api.service.ts diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index f02d7da49c..7fbefc10e3 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -18,11 +18,13 @@ import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service"; import { OrganizationUserServiceImplementation } from "@bitwarden/common/admin-console/services/organization-user/organization-user.service.implementation"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; +import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service"; import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service"; @@ -232,6 +234,7 @@ export class Main { stateEventRunnerService: StateEventRunnerService; biometricStateService: BiometricStateService; billingAccountProfileStateService: BillingAccountProfileStateService; + providerApiService: ProviderApiServiceAbstraction; constructor() { let p = null; @@ -692,6 +695,8 @@ export class Main { this.eventUploadService, this.authService, ); + + this.providerApiService = new ProviderApiService(this.apiService); } async run() { diff --git a/apps/web/src/app/admin-console/providers/verify-recover-delete-provider.component.html b/apps/web/src/app/admin-console/providers/verify-recover-delete-provider.component.html new file mode 100644 index 0000000000..a287a537a4 --- /dev/null +++ b/apps/web/src/app/admin-console/providers/verify-recover-delete-provider.component.html @@ -0,0 +1,34 @@ +
+
+
+

{{ "deleteProvider" | i18n }}

+
+
+ {{ "deleteProviderWarning" | i18n }} +

+ {{ name }} +

+

{{ "deleteProviderRecoverConfirmDesc" | i18n }}

+
+
+ + + {{ "cancel" | i18n }} + +
+
+
+
+
+
diff --git a/apps/web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts b/apps/web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts new file mode 100644 index 0000000000..0550820cda --- /dev/null +++ b/apps/web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts @@ -0,0 +1,61 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; +import { ProviderVerifyRecoverDeleteRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-verify-recover-delete.request"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +@Component({ + selector: "app-verify-recover-delete-provider", + templateUrl: "verify-recover-delete-provider.component.html", +}) +// eslint-disable-next-line rxjs-angular/prefer-takeuntil +export class VerifyRecoverDeleteProviderComponent implements OnInit { + name: string; + formPromise: Promise; + + private providerId: string; + private token: string; + + constructor( + private router: Router, + private providerApiService: ProviderApiServiceAbstraction, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, + private route: ActivatedRoute, + private logService: LogService, + ) {} + + async ngOnInit() { + const qParams = await firstValueFrom(this.route.queryParams); + if (qParams.providerId != null && qParams.token != null && qParams.name != null) { + this.providerId = qParams.providerId; + this.token = qParams.token; + this.name = qParams.name; + } else { + await this.router.navigate(["/"]); + } + } + + async submit() { + try { + const request = new ProviderVerifyRecoverDeleteRequest(this.token); + this.formPromise = this.providerApiService.providerRecoverDeleteToken( + this.providerId, + request, + ); + await this.formPromise; + this.platformUtilsService.showToast( + "success", + this.i18nService.t("providerDeleted"), + this.i18nService.t("providerDeletedDesc"), + ); + await this.router.navigate(["/"]); + } catch (e) { + this.logService.error(e); + } + } +} 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 f2fb296522..30691ce87d 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -15,6 +15,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/organization-create.request"; @@ -147,6 +148,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private messagingService: MessagingService, private formBuilder: FormBuilder, private organizationApiService: OrganizationApiServiceAbstraction, + private providerApiService: ProviderApiServiceAbstraction, ) { this.selfHosted = platformUtilsService.isSelfHost(); } @@ -182,7 +184,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { if (this.hasProvider) { this.formGroup.controls.businessOwned.setValue(true); this.changedOwnedBusiness(); - this.provider = await this.apiService.getProvider(this.providerId); + this.provider = await this.providerApiService.getProvider(this.providerId); const providerDefaultPlan = this.passwordManagerPlans.find( (plan) => plan.type === PlanType.TeamsAnnually, ); diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index e5c2f353c0..066ed5db10 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -13,6 +13,7 @@ import { flagEnabled, Flags } from "../utils/flags"; import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/sponsorships/accept-family-sponsorship.component"; import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component"; +import { VerifyRecoverDeleteProviderComponent } from "./admin-console/providers/verify-recover-delete-provider.component"; import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component"; import { SponsoredFamiliesComponent } from "./admin-console/settings/sponsored-families.component"; import { AcceptOrganizationComponent } from "./auth/accept-organization.component"; @@ -156,6 +157,12 @@ const routes: Routes = [ canActivate: [UnauthGuard], data: { titleId: "deleteAccount" }, }, + { + path: "verify-recover-delete-provider", + component: VerifyRecoverDeleteProviderComponent, + canActivate: [UnauthGuard], + data: { titleId: "deleteAccount" }, + }, { path: "send/:sendId/:key", component: AccessComponent, diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index 586f207962..8f6a1eaedc 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -13,6 +13,7 @@ import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } f import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../admin-console/organizations/tools/unsecured-websites-report.component"; import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../admin-console/organizations/tools/weak-passwords-report.component"; import { ProvidersComponent } from "../admin-console/providers/providers.component"; +import { VerifyRecoverDeleteProviderComponent } from "../admin-console/providers/verify-recover-delete-provider.component"; import { SponsoredFamiliesComponent } from "../admin-console/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../admin-console/settings/sponsoring-org-row.component"; import { AcceptOrganizationComponent } from "../auth/accept-organization.component"; @@ -184,6 +185,7 @@ import { SharedModule } from "./shared.module"; VerifyEmailComponent, VerifyEmailTokenComponent, VerifyRecoverDeleteComponent, + VerifyRecoverDeleteProviderComponent, LowKdfComponent, ], exports: [ @@ -261,6 +263,7 @@ import { SharedModule } from "./shared.module"; VerifyEmailComponent, VerifyEmailTokenComponent, VerifyRecoverDeleteComponent, + VerifyRecoverDeleteProviderComponent, LowKdfComponent, HeaderModule, DangerZoneComponent, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index a3fc234cab..c8dfa14c8b 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7908,5 +7908,41 @@ }, "restrictedGroupAccessDesc": { "message": "You cannot add yourself to a group." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 81cc7c2919..0d75973712 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -8,6 +8,7 @@ import { OrganizationPlansComponent, TaxInfoComponent } from "@bitwarden/web-vau import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; +import { DangerZoneComponent } from "../../../../../../apps/web/src/app/auth/settings/account/danger-zone.component"; import { ManageClientOrganizationSubscriptionComponent } from "../../billing/providers/clients/manage-client-organization-subscription.component"; import { ManageClientOrganizationsComponent } from "../../billing/providers/clients/manage-client-organizations.component"; @@ -40,6 +41,7 @@ import { SetupComponent } from "./setup/setup.component"; ProvidersLayoutComponent, PaymentMethodWarningsModule, TaxInfoComponent, + DangerZoneComponent, ], declarations: [ AcceptProviderComponent, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.html index ea634e5ebc..10f6d14425 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.html @@ -1,51 +1,58 @@ - -
- - {{ "loading" | i18n }} -
-
-
-
-
- - -
-
- - -
-
-
- -
+ +
+ + {{ "loading" | i18n }}
- - +
+
+
+
+ + +
+
+ + +
+
+
+ +
+
+ +
+ + + + +
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts index 079e68fe21..83038d1bfc 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts @@ -1,13 +1,18 @@ import { Component } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; +import { UserVerificationDialogComponent } from "@bitwarden/auth/angular"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderUpdateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-update.request"; import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { DialogService } from "@bitwarden/components"; @Component({ selector: "provider-account", @@ -23,6 +28,11 @@ export class AccountComponent { private providerId: string; + protected enableDeleteProvider$ = this.configService.getFeatureFlag$( + FeatureFlag.EnableDeleteProvider, + false, + ); + constructor( private apiService: ApiService, private i18nService: I18nService, @@ -30,6 +40,9 @@ export class AccountComponent { private syncService: SyncService, private platformUtilsService: PlatformUtilsService, private logService: LogService, + private dialogService: DialogService, + private configService: ConfigService, + private providerApiService: ProviderApiServiceAbstraction, ) {} async ngOnInit() { @@ -38,7 +51,7 @@ export class AccountComponent { this.route.parent.parent.params.subscribe(async (params) => { this.providerId = params.providerId; try { - this.provider = await this.apiService.getProvider(this.providerId); + this.provider = await this.providerApiService.getProvider(this.providerId); } catch (e) { this.logService.error(`Handled exception: ${e}`); } @@ -53,7 +66,7 @@ export class AccountComponent { request.businessName = this.provider.businessName; request.billingEmail = this.provider.billingEmail; - this.formPromise = this.apiService.putProvider(this.providerId, request).then(() => { + this.formPromise = this.providerApiService.putProvider(this.providerId, request).then(() => { return this.syncService.fullSync(true); }); await this.formPromise; @@ -62,4 +75,60 @@ export class AccountComponent { this.logService.error(`Handled exception: ${e}`); } } + + async deleteProvider() { + const providerClients = await this.apiService.getProviderClients(this.providerId); + if (providerClients.data != null && providerClients.data.length > 0) { + await this.dialogService.openSimpleDialog({ + title: { key: "deleteProviderName", placeholders: [this.provider.name] }, + content: { key: "deleteProviderWarningDesc", placeholders: [this.provider.name] }, + acceptButtonText: { key: "ok" }, + type: "danger", + }); + + return false; + } + + const userVerified = await this.verifyUser(); + if (!userVerified) { + return; + } + + this.formPromise = this.providerApiService.deleteProvider(this.providerId); + try { + await this.formPromise; + this.platformUtilsService.showToast( + "success", + this.i18nService.t("providerDeleted"), + this.i18nService.t("providerDeletedDesc"), + ); + } catch (e) { + this.logService.error(e); + } + this.formPromise = null; + } + + private async verifyUser(): Promise { + const confirmDescription = "deleteProviderConfirmation"; + const result = await UserVerificationDialogComponent.open(this.dialogService, { + title: "deleteProvider", + bodyText: confirmDescription, + confirmButtonOptions: { + text: "deleteProvider", + type: "danger", + }, + }); + + // Handle the result of the dialog based on user action and verification success + if (result.userAction === "cancel") { + // User cancelled the dialog + return false; + } + + // User confirmed the dialog so check verification success + if (!result.verificationSuccess) { + return false; + } + return true; + } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index ed7b42c959..cf9af4f68a 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { first } from "rxjs/operators"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -50,10 +50,10 @@ export class SetupComponent implements OnInit { private i18nService: I18nService, private route: ActivatedRoute, private cryptoService: CryptoService, - private apiService: ApiService, private syncService: SyncService, private validationService: ValidationService, private configService: ConfigService, + private providerApiService: ProviderApiServiceAbstraction, ) {} ngOnInit() { @@ -80,7 +80,7 @@ export class SetupComponent implements OnInit { // Check if provider exists, redirect if it does try { - const provider = await this.apiService.getProvider(this.providerId); + const provider = await this.providerApiService.getProvider(this.providerId); if (provider.name != null) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -128,7 +128,7 @@ export class SetupComponent implements OnInit { } } - const provider = await this.apiService.postProviderSetup(this.providerId, request); + const provider = await this.providerApiService.postProviderSetup(this.providerId, request); this.platformUtilsService.showToast("success", null, this.i18nService.t("providerSetup")); await this.syncService.fullSync(true); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index dbb94f6753..ad0881a4b3 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -38,6 +38,7 @@ import { InternalPolicyService, PolicyService as PolicyServiceAbstraction, } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service"; @@ -47,6 +48,7 @@ import { DefaultOrganizationManagementPreferencesService } from "@bitwarden/comm import { OrganizationUserServiceImplementation } from "@bitwarden/common/admin-console/services/organization-user/organization-user.service.implementation"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; +import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service"; import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service"; import { @@ -1115,6 +1117,11 @@ const safeProviders: SafeProvider[] = [ useClass: LoggingErrorHandler, deps: [], }), + safeProvider({ + provide: ProviderApiServiceAbstraction, + useClass: ProviderApiService, + deps: [ApiServiceAbstraction], + }), ]; function encryptServiceFactory( diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 44a58403ed..9b3160ee19 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -4,8 +4,6 @@ import { OrganizationSponsorshipRedeemRequest } from "../admin-console/models/re import { OrganizationConnectionRequest } from "../admin-console/models/request/organization-connection.request"; import { ProviderAddOrganizationRequest } from "../admin-console/models/request/provider/provider-add-organization.request"; import { ProviderOrganizationCreateRequest } from "../admin-console/models/request/provider/provider-organization-create.request"; -import { ProviderSetupRequest } from "../admin-console/models/request/provider/provider-setup.request"; -import { ProviderUpdateRequest } from "../admin-console/models/request/provider/provider-update.request"; import { ProviderUserAcceptRequest } from "../admin-console/models/request/provider/provider-user-accept.request"; import { ProviderUserBulkConfirmRequest } from "../admin-console/models/request/provider/provider-user-bulk-confirm.request"; import { ProviderUserBulkRequest } from "../admin-console/models/request/provider/provider-user-bulk.request"; @@ -29,7 +27,6 @@ import { ProviderUserResponse, ProviderUserUserDetailsResponse, } from "../admin-console/models/response/provider/provider-user.response"; -import { ProviderResponse } from "../admin-console/models/response/provider/provider.response"; import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response"; import { CreateAuthRequest } from "../auth/models/request/create-auth.request"; import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request"; @@ -372,10 +369,6 @@ export abstract class ApiService { getPlans: () => Promise>; getTaxRates: () => Promise>; - postProviderSetup: (id: string, request: ProviderSetupRequest) => Promise; - getProvider: (id: string) => Promise; - putProvider: (id: string, request: ProviderUpdateRequest) => Promise; - getProviderUsers: (providerId: string) => Promise>; getProviderUser: (providerId: string, id: string) => Promise; postProviderUserInvite: (providerId: string, request: ProviderUserInviteRequest) => Promise; diff --git a/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts new file mode 100644 index 0000000000..3c2170bf9e --- /dev/null +++ b/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts @@ -0,0 +1,15 @@ +import { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request"; +import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request"; +import { ProviderVerifyRecoverDeleteRequest } from "../../models/request/provider/provider-verify-recover-delete.request"; +import { ProviderResponse } from "../../models/response/provider/provider.response"; + +export class ProviderApiServiceAbstraction { + postProviderSetup: (id: string, request: ProviderSetupRequest) => Promise; + getProvider: (id: string) => Promise; + putProvider: (id: string, request: ProviderUpdateRequest) => Promise; + providerRecoverDeleteToken: ( + organizationId: string, + request: ProviderVerifyRecoverDeleteRequest, + ) => Promise; + deleteProvider: (id: string) => Promise; +} diff --git a/libs/common/src/admin-console/models/request/provider/provider-verify-recover-delete.request.ts b/libs/common/src/admin-console/models/request/provider/provider-verify-recover-delete.request.ts new file mode 100644 index 0000000000..528d2dba78 --- /dev/null +++ b/libs/common/src/admin-console/models/request/provider/provider-verify-recover-delete.request.ts @@ -0,0 +1,7 @@ +export class ProviderVerifyRecoverDeleteRequest { + token: string; + + constructor(token: string) { + this.token = token; + } +} diff --git a/libs/common/src/admin-console/services/provider/provider-api.service.ts b/libs/common/src/admin-console/services/provider/provider-api.service.ts new file mode 100644 index 0000000000..2ee921393f --- /dev/null +++ b/libs/common/src/admin-console/services/provider/provider-api.service.ts @@ -0,0 +1,47 @@ +import { ApiService } from "../../../abstractions/api.service"; +import { ProviderApiServiceAbstraction } from "../../abstractions/provider/provider-api.service.abstraction"; +import { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request"; +import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request"; +import { ProviderVerifyRecoverDeleteRequest } from "../../models/request/provider/provider-verify-recover-delete.request"; +import { ProviderResponse } from "../../models/response/provider/provider.response"; + +export class ProviderApiService implements ProviderApiServiceAbstraction { + constructor(private apiService: ApiService) {} + async postProviderSetup(id: string, request: ProviderSetupRequest) { + const r = await this.apiService.send( + "POST", + "/providers/" + id + "/setup", + request, + true, + true, + ); + return new ProviderResponse(r); + } + + async getProvider(id: string) { + const r = await this.apiService.send("GET", "/providers/" + id, null, true, true); + return new ProviderResponse(r); + } + + async putProvider(id: string, request: ProviderUpdateRequest) { + const r = await this.apiService.send("PUT", "/providers/" + id, request, true, true); + return new ProviderResponse(r); + } + + providerRecoverDeleteToken( + providerId: string, + request: ProviderVerifyRecoverDeleteRequest, + ): Promise { + return this.apiService.send( + "POST", + "/providers/" + providerId + "/delete-recover-token", + request, + false, + false, + ); + } + + async deleteProvider(id: string): Promise { + await this.apiService.send("DELETE", "/providers/" + id, null, true, false); + } +} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index b937e6c462..636e9bc4ce 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -10,6 +10,7 @@ export enum FeatureFlag { EnableConsolidatedBilling = "enable-consolidated-billing", AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section", UnassignedItemsBanner = "unassigned-items-banner", + EnableDeleteProvider = "AC-1218-delete-provider", } // Replace this with a type safe lookup of the feature flag values in PM-2282 diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 90671c4f04..e8135f3d6c 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -7,8 +7,6 @@ import { OrganizationSponsorshipRedeemRequest } from "../admin-console/models/re import { OrganizationConnectionRequest } from "../admin-console/models/request/organization-connection.request"; import { ProviderAddOrganizationRequest } from "../admin-console/models/request/provider/provider-add-organization.request"; import { ProviderOrganizationCreateRequest } from "../admin-console/models/request/provider/provider-organization-create.request"; -import { ProviderSetupRequest } from "../admin-console/models/request/provider/provider-setup.request"; -import { ProviderUpdateRequest } from "../admin-console/models/request/provider/provider-update.request"; import { ProviderUserAcceptRequest } from "../admin-console/models/request/provider/provider-user-accept.request"; import { ProviderUserBulkConfirmRequest } from "../admin-console/models/request/provider/provider-user-bulk-confirm.request"; import { ProviderUserBulkRequest } from "../admin-console/models/request/provider/provider-user-bulk.request"; @@ -32,7 +30,6 @@ import { ProviderUserResponse, ProviderUserUserDetailsResponse, } from "../admin-console/models/response/provider/provider-user.response"; -import { ProviderResponse } from "../admin-console/models/response/provider/provider.response"; import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response"; import { TokenService } from "../auth/abstractions/token.service"; import { CreateAuthRequest } from "../auth/models/request/create-auth.request"; @@ -1151,23 +1148,6 @@ export class ApiService implements ApiServiceAbstraction { return this.send("DELETE", "/organizations/connections/" + id, null, true, false); } - // Provider APIs - - async postProviderSetup(id: string, request: ProviderSetupRequest) { - const r = await this.send("POST", "/providers/" + id + "/setup", request, true, true); - return new ProviderResponse(r); - } - - async getProvider(id: string) { - const r = await this.send("GET", "/providers/" + id, null, true, true); - return new ProviderResponse(r); - } - - async putProvider(id: string, request: ProviderUpdateRequest) { - const r = await this.send("PUT", "/providers/" + id, request, true, true); - return new ProviderResponse(r); - } - // Provider User APIs async getProviderUsers( From 0c557c6ab8e606a73219a053bee56481e5ccb39d Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:13:05 -0500 Subject: [PATCH 14/86] Guard Background Only and MV2 Only Actions (#8773) --- .../browser/src/background/main.background.ts | 206 +++++++++--------- .../src/popup/services/services.module.ts | 2 +- 2 files changed, 104 insertions(+), 104 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 642510b4de..69ed4cfa3d 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -342,11 +342,11 @@ export default class MainBackground { private syncTimeout: any; private isSafari: boolean; private nativeMessagingBackground: NativeMessagingBackground; - popupOnlyContext: boolean; - - constructor(public isPrivateMode: boolean = false) { - this.popupOnlyContext = isPrivateMode || BrowserApi.isManifestVersion(3); + constructor( + public isPrivateMode: boolean = false, + public popupOnlyContext: boolean = false, + ) { // Services const lockedCallback = async (userId?: string) => { if (this.notificationsService != null) { @@ -889,82 +889,83 @@ export default class MainBackground { this.isSafari = this.platformUtilsService.isSafari(); // Background - this.runtimeBackground = new RuntimeBackground( - this, - this.autofillService, - this.platformUtilsService as BrowserPlatformUtilsService, - this.i18nService, - this.notificationsService, - this.stateService, - this.autofillSettingsService, - this.systemService, - this.environmentService, - this.messagingService, - this.logService, - this.configService, - this.fido2Service, - ); - this.nativeMessagingBackground = new NativeMessagingBackground( - this.accountService, - this.masterPasswordService, - this.cryptoService, - this.cryptoFunctionService, - this.runtimeBackground, - this.messagingService, - this.appIdService, - this.platformUtilsService, - this.stateService, - this.logService, - this.authService, - this.biometricStateService, - ); - this.commandsBackground = new CommandsBackground( - this, - this.passwordGenerationService, - this.platformUtilsService, - this.vaultTimeoutService, - this.authService, - ); - this.notificationBackground = new NotificationBackground( - this.autofillService, - this.cipherService, - this.authService, - this.policyService, - this.folderService, - this.stateService, - this.userNotificationSettingsService, - this.domainSettingsService, - this.environmentService, - this.logService, - themeStateService, - this.configService, - ); - this.overlayBackground = new OverlayBackground( - this.cipherService, - this.autofillService, - this.authService, - this.environmentService, - this.domainSettingsService, - this.stateService, - this.autofillSettingsService, - this.i18nService, - this.platformUtilsService, - themeStateService, - ); - this.filelessImporterBackground = new FilelessImporterBackground( - this.configService, - this.authService, - this.policyService, - this.notificationBackground, - this.importService, - this.syncService, - ); - this.tabsBackground = new TabsBackground( - this, - this.notificationBackground, - this.overlayBackground, - ); if (!this.popupOnlyContext) { + this.runtimeBackground = new RuntimeBackground( + this, + this.autofillService, + this.platformUtilsService as BrowserPlatformUtilsService, + this.i18nService, + this.notificationsService, + this.stateService, + this.autofillSettingsService, + this.systemService, + this.environmentService, + this.messagingService, + this.logService, + this.configService, + this.fido2Service, + ); + this.nativeMessagingBackground = new NativeMessagingBackground( + this.accountService, + this.masterPasswordService, + this.cryptoService, + this.cryptoFunctionService, + this.runtimeBackground, + this.messagingService, + this.appIdService, + this.platformUtilsService, + this.stateService, + this.logService, + this.authService, + this.biometricStateService, + ); + this.commandsBackground = new CommandsBackground( + this, + this.passwordGenerationService, + this.platformUtilsService, + this.vaultTimeoutService, + this.authService, + ); + this.notificationBackground = new NotificationBackground( + this.autofillService, + this.cipherService, + this.authService, + this.policyService, + this.folderService, + this.stateService, + this.userNotificationSettingsService, + this.domainSettingsService, + this.environmentService, + this.logService, + themeStateService, + this.configService, + ); + this.overlayBackground = new OverlayBackground( + this.cipherService, + this.autofillService, + this.authService, + this.environmentService, + this.domainSettingsService, + this.stateService, + this.autofillSettingsService, + this.i18nService, + this.platformUtilsService, + themeStateService, + ); + this.filelessImporterBackground = new FilelessImporterBackground( + this.configService, + this.authService, + this.policyService, + this.notificationBackground, + this.importService, + this.syncService, + ); + this.tabsBackground = new TabsBackground( + this, + this.notificationBackground, + this.overlayBackground, + ); + const contextMenuClickedHandler = new ContextMenuClickedHandler( (options) => this.platformUtilsService.copyToClipboard(options.text), async (_tab) => { @@ -1006,11 +1007,6 @@ export default class MainBackground { this.notificationsService, this.accountService, ); - this.webRequestBackground = new WebRequestBackground( - this.platformUtilsService, - this.cipherService, - this.authService, - ); this.usernameGenerationService = new UsernameGenerationService( this.cryptoService, @@ -1032,34 +1028,40 @@ export default class MainBackground { this.authService, this.cipherService, ); + + if (BrowserApi.isManifestVersion(2)) { + this.webRequestBackground = new WebRequestBackground( + this.platformUtilsService, + this.cipherService, + this.authService, + ); + } } } async bootstrap() { this.containerService.attachToGlobal(self); - await this.stateService.init(); + await this.stateService.init({ runMigrations: !this.isPrivateMode }); - await this.vaultTimeoutService.init(true); await (this.i18nService as I18nService).init(); - await (this.eventUploadService as EventUploadService).init(true); - await this.runtimeBackground.init(); - await this.notificationBackground.init(); - this.filelessImporterBackground.init(); - await this.commandsBackground.init(); - + (this.eventUploadService as EventUploadService).init(true); this.twoFactorService.init(); - await this.overlayBackground.init(); - - await this.tabsBackground.init(); if (!this.popupOnlyContext) { + await this.vaultTimeoutService.init(true); + await this.runtimeBackground.init(); + await this.notificationBackground.init(); + this.filelessImporterBackground.init(); + await this.commandsBackground.init(); + await this.overlayBackground.init(); + await this.tabsBackground.init(); this.contextMenusBackground?.init(); + await this.idleBackground.init(); + if (BrowserApi.isManifestVersion(2)) { + await this.webRequestBackground.init(); + } } - await this.idleBackground.init(); - await this.webRequestBackground.init(); - - await this.fido2Service.init(); if (this.platformUtilsService.isFirefox() && !this.isPrivateMode) { // Set Private Mode windows to the default icon - they do not share state with the background page @@ -1082,9 +1084,7 @@ export default class MainBackground { if (!this.isPrivateMode) { await this.refreshBadge(); } - // 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.fullSync(true); + await this.fullSync(true); setTimeout(() => this.notificationsService.init(), 2500); resolve(); }, 500); @@ -1205,7 +1205,7 @@ export default class MainBackground { BrowserApi.sendMessage("updateBadge"); } await this.refreshBadge(); - await this.mainContextMenuHandler.noAccess(); + await this.mainContextMenuHandler?.noAccess(); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.notificationsService.updateConnection(false); diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 40daf1b04d..fe70058640 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -120,7 +120,7 @@ const mainBackground: MainBackground = needsBackgroundInit : BrowserApi.getBackgroundPage().bitwardenMain; function createLocalBgService() { - const localBgService = new MainBackground(isPrivateMode); + const localBgService = new MainBackground(isPrivateMode, true); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises localBgService.bootstrap(); From 1cde2dbaefb6c303f58678786faca89dacfd3e0b Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Wed, 17 Apr 2024 09:38:47 -0500 Subject: [PATCH 15/86] [PM-7527] Add `Build Manifest v3` build step to the build-browser.yml Github action (#8777) * [PM-7527] Get MV3 build artifacts in main branch with clear messaging that that the build is not to be released * [PM-7527] Add `Build Manifest v3` build step to the build-browser.yml Github action --- .github/workflows/build-browser.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 4fb72a47de..23f4bd35f1 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -160,9 +160,9 @@ jobs: run: npm run dist working-directory: browser-source/apps/browser - # - name: Build Manifest v3 - # run: npm run dist:mv3 - # working-directory: browser-source/apps/browser + - name: Build Manifest v3 + run: npm run dist:mv3 + working-directory: browser-source/apps/browser - name: Gulp run: gulp ci From 3179867310e2fec5913b73f67e364b69273b590f Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:39:15 -0400 Subject: [PATCH 16/86] [PM-7352] - Browser Send Type Groupings Fix (#8727) * Removed type counts and just calculated counts off the sends * Fixing key spec test case --- .../src/models/browserSendComponentState.ts | 10 ---------- .../popup/send/send-groupings.component.html | 4 ++-- .../popup/send/send-groupings.component.ts | 20 ++----------------- .../browser-send-state.service.spec.ts | 2 -- .../services/browser-send-state.service.ts | 2 +- .../popup/services/key-definitions.spec.ts | 5 ++--- 6 files changed, 7 insertions(+), 36 deletions(-) diff --git a/apps/browser/src/models/browserSendComponentState.ts b/apps/browser/src/models/browserSendComponentState.ts index 9158efc21d..81dd93323b 100644 --- a/apps/browser/src/models/browserSendComponentState.ts +++ b/apps/browser/src/models/browserSendComponentState.ts @@ -1,5 +1,3 @@ -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify"; @@ -7,13 +5,6 @@ import { BrowserComponentState } from "./browserComponentState"; export class BrowserSendComponentState extends BrowserComponentState { sends: SendView[]; - typeCounts: Map; - - toJSON() { - return Utils.merge(this, { - typeCounts: Utils.mapToRecord(this.typeCounts), - }); - } static fromJSON(json: DeepJsonify) { if (json == null) { @@ -22,7 +13,6 @@ export class BrowserSendComponentState extends BrowserComponentState { return Object.assign(new BrowserSendComponentState(), json, { sends: json.sends?.map((s) => SendView.fromJSON(s)), - typeCounts: Utils.recordToMap(json.typeCounts), }); } } diff --git a/apps/browser/src/tools/popup/send/send-groupings.component.html b/apps/browser/src/tools/popup/send/send-groupings.component.html index edeabd6546..213afdfa22 100644 --- a/apps/browser/src/tools/popup/send/send-groupings.component.html +++ b/apps/browser/src/tools/popup/send/send-groupings.component.html @@ -61,7 +61,7 @@
{{ "sendTypeText" | i18n }}
- {{ typeCounts.get(sendType.Text) || 0 }} + {{ getSendCount(sends, sendType.Text) }}
{{ "sendTypeFile" | i18n }}
- {{ typeCounts.get(sendType.File) || 0 }} + {{ getSendCount(sends, sendType.File) }}
diff --git a/apps/browser/src/tools/popup/send/send-groupings.component.ts b/apps/browser/src/tools/popup/send/send-groupings.component.ts index a49773367d..87d03c4b76 100644 --- a/apps/browser/src/tools/popup/send/send-groupings.component.ts +++ b/apps/browser/src/tools/popup/send/send-groupings.component.ts @@ -29,8 +29,6 @@ const ComponentId = "SendComponent"; export class SendGroupingsComponent extends BaseSendComponent { // Header showLeftHeader = true; - // Send Type Calculations - typeCounts = new Map(); // State Handling state: BrowserSendComponentState; private loadedTimeout: number; @@ -65,7 +63,6 @@ export class SendGroupingsComponent extends BaseSendComponent { dialogService, ); super.onSuccessfulLoad = async () => { - this.calculateTypeCounts(); this.selectAll(); }; } @@ -174,17 +171,8 @@ export class SendGroupingsComponent extends BaseSendComponent { return this.hasSearched || (!this.searchPending && this.isSearchable); } - private calculateTypeCounts() { - // Create type counts - const typeCounts = new Map(); - this.sends.forEach((s) => { - if (typeCounts.has(s.type)) { - typeCounts.set(s.type, typeCounts.get(s.type) + 1); - } else { - typeCounts.set(s.type, 1); - } - }); - this.typeCounts = typeCounts; + getSendCount(sends: SendView[], type: SendType): number { + return sends.filter((s) => s.type === type).length; } private async saveState() { @@ -192,7 +180,6 @@ export class SendGroupingsComponent extends BaseSendComponent { scrollY: BrowserPopupUtils.getContentScrollY(window), searchText: this.searchText, sends: this.sends, - typeCounts: this.typeCounts, }); await this.stateService.setBrowserSendComponentState(this.state); } @@ -206,9 +193,6 @@ export class SendGroupingsComponent extends BaseSendComponent { if (this.state.sends != null) { this.sends = this.state.sends; } - if (this.state.typeCounts != null) { - this.typeCounts = this.state.typeCounts; - } return true; } diff --git a/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts b/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts index 3dafc0934a..6f0ae1455a 100644 --- a/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts +++ b/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts @@ -6,7 +6,6 @@ import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider import { awaitAsync } from "@bitwarden/common/../spec/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { UserId } from "@bitwarden/common/types/guid"; import { BrowserComponentState } from "../../../models/browserComponentState"; @@ -33,7 +32,6 @@ describe("Browser Send State Service", () => { const state = new BrowserSendComponentState(); state.scrollY = 0; state.searchText = "test"; - state.typeCounts = new Map().set(SendType.File, 1); await stateService.setBrowserSendComponentState(state); diff --git a/apps/browser/src/tools/popup/services/browser-send-state.service.ts b/apps/browser/src/tools/popup/services/browser-send-state.service.ts index b814ee5bc9..52aeb01a92 100644 --- a/apps/browser/src/tools/popup/services/browser-send-state.service.ts +++ b/apps/browser/src/tools/popup/services/browser-send-state.service.ts @@ -42,7 +42,7 @@ export class BrowserSendStateService { } /** Set the active user's browser send component state - * @param { BrowserSendComponentState } value sets the sends and type counts along with the scroll position and search text for + * @param { BrowserSendComponentState } value sets the sends along with the scroll position and search text for * the send component on the browser */ async setBrowserSendComponentState(value: BrowserSendComponentState): Promise { diff --git a/apps/browser/src/tools/popup/services/key-definitions.spec.ts b/apps/browser/src/tools/popup/services/key-definitions.spec.ts index 3ba574efa3..7517771669 100644 --- a/apps/browser/src/tools/popup/services/key-definitions.spec.ts +++ b/apps/browser/src/tools/popup/services/key-definitions.spec.ts @@ -1,7 +1,5 @@ import { Jsonify } from "type-fest"; -import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; - import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; import { BROWSER_SEND_COMPONENT, BROWSER_SEND_TYPE_COMPONENT } from "./key-definitions"; @@ -12,7 +10,8 @@ describe("Key definitions", () => { const keyDef = BROWSER_SEND_COMPONENT; const expectedState = { - typeCounts: new Map(), + scrollY: 0, + searchText: "test", }; const result = keyDef.deserializer( From 395f2e806e616baffcb7f5f869ddf0ce3acd9381 Mon Sep 17 00:00:00 2001 From: aj-rosado <109146700+aj-rosado@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:06:25 +0100 Subject: [PATCH 17/86] Calling closeAll from dialogService on Send ngOnDestroy (#8763) --- apps/web/src/app/tools/send/send.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/app/tools/send/send.component.ts b/apps/web/src/app/tools/send/send.component.ts index b5edd1433e..755435882a 100644 --- a/apps/web/src/app/tools/send/send.component.ts +++ b/apps/web/src/app/tools/send/send.component.ts @@ -92,6 +92,7 @@ export class SendComponent extends BaseSendComponent { } ngOnDestroy() { + this.dialogService.closeAll(); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } From 28a89ddb8605832d4ff8147dfbc2ccf30c2daf53 Mon Sep 17 00:00:00 2001 From: aj-rosado <109146700+aj-rosado@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:19:06 +0100 Subject: [PATCH 18/86] [PM-7304] Add missing i18n keys for import errors (#8743) * Add static error message when import fails * Adding missing string on CLI for unassigned items * Added missing string on setImportTarget * fixed tests --- apps/browser/src/_locales/en/messages.json | 6 ++++++ apps/cli/src/locales/en/messages.json | 9 +++++++++ apps/desktop/src/locales/en/messages.json | 6 ++++++ apps/web/src/locales/en/messages.json | 6 ++++++ libs/importer/src/services/import.service.spec.ts | 4 ++-- libs/importer/src/services/import.service.ts | 4 ++-- 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 5e941083df..f599df470b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3011,5 +3011,11 @@ }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/cli/src/locales/en/messages.json b/apps/cli/src/locales/en/messages.json index e12b30af2c..8364e0b328 100644 --- a/apps/cli/src/locales/en/messages.json +++ b/apps/cli/src/locales/en/messages.json @@ -49,5 +49,14 @@ }, "unsupportedEncryptedImport": { "message": "Importing encrypted files is currently not supported." + }, + "importUnassignedItemsError": { + "message": "File contains unassigned items." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index a0d34e4075..00eace54e2 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2705,5 +2705,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index c8dfa14c8b..680014f1f0 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7944,5 +7944,11 @@ }, "deleteProviderWarning": { "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index eb21f384b5..7bbcd3287a 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -196,7 +196,7 @@ describe("ImportService", () => { new Object() as FolderView, ); - await expect(setImportTargetMethod).rejects.toThrow("Error assigning target collection"); + await expect(setImportTargetMethod).rejects.toThrow(); }); it("passing importTarget as null on setImportTarget throws error", async () => { @@ -206,7 +206,7 @@ describe("ImportService", () => { new Object() as CollectionView, ); - await expect(setImportTargetMethod).rejects.toThrow("Error assigning target folder"); + await expect(setImportTargetMethod).rejects.toThrow(); }); it("passing importTarget, collectionRelationship has the expected values", async () => { diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index 62961a77c4..f5cab933f3 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -432,7 +432,7 @@ export class ImportService implements ImportServiceAbstraction { if (organizationId) { if (!(importTarget instanceof CollectionView)) { - throw new Error("Error assigning target collection"); + throw new Error(this.i18nService.t("errorAssigningTargetCollection")); } const noCollectionRelationShips: [number, number][] = []; @@ -463,7 +463,7 @@ export class ImportService implements ImportServiceAbstraction { } if (!(importTarget instanceof FolderView)) { - throw new Error("Error assigning target folder"); + throw new Error(this.i18nService.t("errorAssigningTargetFolder")); } const noFolderRelationShips: [number, number][] = []; From f15bffb040afa8b5d40405d042d8baf951ce8193 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Wed, 17 Apr 2024 12:02:21 -0400 Subject: [PATCH 19/86] Handle null values coming from state (#8784) --- libs/common/src/vault/services/cipher.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index e8544d7f98..dce58d3ba5 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1,4 +1,4 @@ -import { Observable, firstValueFrom } from "rxjs"; +import { Observable, firstValueFrom, map } from "rxjs"; import { SemVer } from "semver"; import { ApiService } from "../../abstractions/api.service"; @@ -100,9 +100,9 @@ export class CipherService implements CipherServiceAbstraction { this.decryptedCiphersState = this.stateProvider.getActive(DECRYPTED_CIPHERS); this.addEditCipherInfoState = this.stateProvider.getActive(ADD_EDIT_CIPHER_INFO_KEY); - this.localData$ = this.localDataState.state$; - this.ciphers$ = this.encryptedCiphersState.state$; - this.cipherViews$ = this.decryptedCiphersState.state$; + this.localData$ = this.localDataState.state$.pipe(map((data) => data ?? {})); + this.ciphers$ = this.encryptedCiphersState.state$.pipe(map((ciphers) => ciphers ?? {})); + this.cipherViews$ = this.decryptedCiphersState.state$.pipe(map((views) => views ?? {})); this.addEditCipherInfo$ = this.addEditCipherInfoState.state$; } From c0455583127770a46bb33bf6a889e24a44a25579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Gon=C3=A7alves?= Date: Wed, 17 Apr 2024 17:09:44 +0100 Subject: [PATCH 20/86] PM-7533 Add missing value assignment (#8780) --- libs/common/src/vault/services/cipher.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index dce58d3ba5..7d06b3185f 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -785,7 +785,7 @@ export class CipherService implements CipherServiceAbstraction { async upsert(cipher: CipherData | CipherData[]): Promise { const ciphers = cipher instanceof CipherData ? [cipher] : cipher; await this.updateEncryptedCipherState((current) => { - ciphers.forEach((c) => current[c.id as CipherId]); + ciphers.forEach((c) => (current[c.id as CipherId] = c)); return current; }); } From e44816800220c80b375028e1f73f4518d561a27d Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 18 Apr 2024 03:00:00 +1000 Subject: [PATCH 21/86] [AC-2444] Add deep links to unassigned items banner (#8720) * Add link to Admin Console * update date on self-hosted banner --- apps/browser/src/_locales/en/messages.json | 19 ++++++++--- .../vault/current-tab.component.html | 12 ++++++- .../layouts/header/web-header.component.html | 11 ++++++- apps/web/src/locales/en/messages.json | 20 +++++++---- .../unassigned-items-banner.service.spec.ts | 11 ++++++- .../unassigned-items-banner.service.ts | 33 +++++++++++++++++-- 6 files changed, 90 insertions(+), 16 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index f599df470b..36e3ce65a8 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3006,11 +3006,22 @@ "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.html b/apps/browser/src/vault/popup/components/vault/current-tab.component.html index fc8b4212ba..0b2e16d09d 100644 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.html +++ b/apps/browser/src/vault/popup/components/vault/current-tab.component.html @@ -40,12 +40,22 @@ *ngIf=" (unassignedItemsBannerEnabled$ | async) && (unassignedItemsBannerService.showBanner$ | async) && - (unassignedItemsBannerService.bannerText$ | async) + !(unassignedItemsBannerService.loading$ | async) " type="info" >

{{ unassignedItemsBannerService.bannerText$ | async | i18n }} + {{ "unassignedItemsBannerCTAPartOne" | i18n }} + {{ "adminConsole" | i18n }} + {{ "unassignedItemsBannerCTAPartTwo" | i18n }} {{ unassignedItemsBannerService.bannerText$ | async | i18n }} + {{ "unassignedItemsBannerCTAPartOne" | i18n }} + {{ "adminConsole" | i18n }} + {{ "unassignedItemsBannerCTAPartTwo" | i18n }} { let stateProvider: FakeStateProvider; let apiService: MockProxy; let environmentService: MockProxy; + let organizationService: MockProxy; const sutFactory = () => - new UnassignedItemsBannerService(stateProvider, apiService, environmentService); + new UnassignedItemsBannerService( + stateProvider, + apiService, + environmentService, + organizationService, + ); beforeEach(() => { const fakeAccountService = mockAccountServiceWith("userId" as UserId); @@ -22,6 +29,8 @@ describe("UnassignedItemsBanner", () => { apiService = mock(); environmentService = mock(); environmentService.environment$ = of(null); + organizationService = mock(); + organizationService.organizations$ = of([]); }); it("shows the banner if showBanner local state is true", async () => { diff --git a/libs/angular/src/services/unassigned-items-banner.service.ts b/libs/angular/src/services/unassigned-items-banner.service.ts index 13a745fb82..db93d4c4fc 100644 --- a/libs/angular/src/services/unassigned-items-banner.service.ts +++ b/libs/angular/src/services/unassigned-items-banner.service.ts @@ -1,6 +1,10 @@ import { Injectable } from "@angular/core"; -import { concatMap, map } from "rxjs"; +import { combineLatest, concatMap, map, startWith } from "rxjs"; +import { + OrganizationService, + canAccessOrgAdmin, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { EnvironmentService, Region, @@ -40,18 +44,41 @@ export class UnassignedItemsBannerService { }), ); + private adminConsoleOrg$ = this.organizationService.organizations$.pipe( + map((orgs) => orgs.find((o) => canAccessOrgAdmin(o))), + ); + + adminConsoleUrl$ = combineLatest([ + this.adminConsoleOrg$, + this.environmentService.environment$, + ]).pipe( + map(([org, environment]) => { + if (org == null || environment == null) { + return "#"; + } + + return environment.getWebVaultUrl() + "/#/organizations/" + org.id; + }), + ); + bannerText$ = this.environmentService.environment$.pipe( map((e) => e?.getRegion() == Region.SelfHosted - ? "unassignedItemsBannerSelfHost" - : "unassignedItemsBanner", + ? "unassignedItemsBannerSelfHostNotice" + : "unassignedItemsBannerNotice", ), ); + loading$ = combineLatest([this.adminConsoleUrl$, this.bannerText$]).pipe( + startWith(true), + map(() => false), + ); + constructor( private stateProvider: StateProvider, private apiService: UnassignedItemsBannerApiService, private environmentService: EnvironmentService, + private organizationService: OrganizationService, ) {} async hideBanner() { From 504fe826ea8fce23e9db1cc19605832a5f399030 Mon Sep 17 00:00:00 2001 From: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:50:57 -0400 Subject: [PATCH 22/86] Bumped desktop version to (#8793) --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 0dc23b04b1..4bb0ab2d93 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.4.1", + "version": "2024.4.2", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 0531345131..11b38bd273 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2024.4.1", + "version": "2024.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2024.4.1", + "version": "2024.4.2", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native", diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 6527c21521..a65dab016c 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.4.1", + "version": "2024.4.2", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 096f6653cb..789f93cc83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -233,7 +233,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2024.4.1", + "version": "2024.4.2", "hasInstallScript": true, "license": "GPL-3.0" }, From 83b3fd83e48d0fae7939dab6af8519838e447d9f Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 18 Apr 2024 09:53:40 +1000 Subject: [PATCH 23/86] Delete unused SettingsComponents (#8778) --- .../settings/settings.component.html | 88 ------------------- .../settings/settings.component.ts | 27 ------ .../providers/settings/settings.component.ts | 23 ----- 3 files changed, 138 deletions(-) delete mode 100644 apps/web/src/app/admin-console/organizations/settings/settings.component.html delete mode 100644 apps/web/src/app/admin-console/organizations/settings/settings.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/admin-console/providers/settings/settings.component.ts diff --git a/apps/web/src/app/admin-console/organizations/settings/settings.component.html b/apps/web/src/app/admin-console/organizations/settings/settings.component.html deleted file mode 100644 index 47592df378..0000000000 --- a/apps/web/src/app/admin-console/organizations/settings/settings.component.html +++ /dev/null @@ -1,88 +0,0 @@ -

diff --git a/apps/web/src/app/admin-console/organizations/settings/settings.component.ts b/apps/web/src/app/admin-console/organizations/settings/settings.component.ts deleted file mode 100644 index ab25829d19..0000000000 --- a/apps/web/src/app/admin-console/organizations/settings/settings.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { Observable, switchMap } from "rxjs"; - -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; - -@Component({ - selector: "app-org-settings", - templateUrl: "settings.component.html", -}) -export class SettingsComponent implements OnInit { - organization$: Observable; - FeatureFlag = FeatureFlag; - - constructor( - private route: ActivatedRoute, - private organizationService: OrganizationService, - ) {} - - ngOnInit() { - this.organization$ = this.route.params.pipe( - switchMap((params) => this.organizationService.get$(params.organizationId)), - ); - } -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/settings.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/settings.component.ts deleted file mode 100644 index 2418dbed41..0000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/settings.component.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Component } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; - -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; - -@Component({ - selector: "provider-settings", - templateUrl: "settings.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class SettingsComponent { - constructor( - private route: ActivatedRoute, - private providerService: ProviderService, - ) {} - - ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.params.subscribe(async (params) => { - await this.providerService.get(params.providerId); - }); - } -} From cbaf3462c1a3f6577fe6dcda74c4171d04556371 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:03:51 +0200 Subject: [PATCH 24/86] [PM-7475][deps] Tools: Update electron to v28.3.1 (#8742) * [deps] Tools: Update electron to v28.3.1 * Update version in electron-builder.json --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith --- apps/desktop/electron-builder.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 5fd26f32ba..4f0d05581c 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -24,7 +24,7 @@ "**/node_modules/argon2/package.json", "**/node_modules/argon2/lib/binding/napi-v3/argon2.node" ], - "electronVersion": "28.2.8", + "electronVersion": "28.3.1", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", diff --git a/package-lock.json b/package-lock.json index 789f93cc83..d72ba9cb19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -127,7 +127,7 @@ "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "6.10.0", - "electron": "28.2.8", + "electron": "28.3.1", "electron-builder": "24.13.3", "electron-log": "5.0.1", "electron-reload": "2.0.0-alpha.1", @@ -16924,9 +16924,9 @@ } }, "node_modules/electron": { - "version": "28.2.8", - "resolved": "https://registry.npmjs.org/electron/-/electron-28.2.8.tgz", - "integrity": "sha512-VgXw2OHqPJkobIC7X9eWh3atptjnELaP+zlbF9Oz00ridlaOWmtLPsp6OaXbLw35URpMr0iYesq8okKp7S0k+g==", + "version": "28.3.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-28.3.1.tgz", + "integrity": "sha512-aF9fONuhVDJlctJS7YOw76ynxVAQdfIWmlhRMKits24tDcdSL0eMHUS0wWYiRfGWbQnUKB6V49Rf17o32f4/fg==", "dev": true, "hasInstallScript": true, "dependencies": { diff --git a/package.json b/package.json index 203da2d625..057e737903 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "6.10.0", - "electron": "28.2.8", + "electron": "28.3.1", "electron-builder": "24.13.3", "electron-log": "5.0.1", "electron-reload": "2.0.0-alpha.1", From 1e0ad097577f29a2ad303404a68884fe940fbc42 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 18 Apr 2024 08:36:38 -0400 Subject: [PATCH 25/86] Added create-client-organization.component (#8767) --- apps/web/src/locales/en/messages.json | 21 +++ .../providers/providers-routing.module.ts | 2 +- .../providers/providers.module.ts | 10 +- .../services/web-provider.service.ts | 50 ++++++ .../create-client-organization.component.html | 69 +++++++++ .../create-client-organization.component.ts | 142 ++++++++++++++++++ .../app/billing/providers/clients/index.ts | 3 + ...ent-organization-subscription.component.ts | 8 +- ...manage-client-organizations.component.html | 8 +- .../manage-client-organizations.component.ts | 35 ++++- .../src/services/jslib-services.module.ts | 2 + .../billilng-api.service.abstraction.ts | 16 +- .../create-client-organization.request.ts | 12 ++ .../provider-subscription-update.request.ts | 3 - .../update-client-organization.request.ts | 3 + .../billing/services/billing-api.service.ts | 31 +++- .../services/organization-billing.service.ts | 20 ++- 17 files changed, 409 insertions(+), 26 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html create mode 100644 bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts create mode 100644 bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts create mode 100644 libs/common/src/billing/models/request/create-client-organization.request.ts delete mode 100644 libs/common/src/billing/models/request/provider-subscription-update.request.ts create mode 100644 libs/common/src/billing/models/request/update-client-organization.request.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 32c748d4af..49611145dd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7958,5 +7958,26 @@ }, "errorAssigningTargetFolder": { "message": "Error assigning target folder." + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts index a42b10d88f..3976499268 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts @@ -69,7 +69,7 @@ const routes: Routes = [ { path: "manage-client-organizations", component: ManageClientOrganizationsComponent, - data: { titleId: "manage-client-organizations" }, + data: { titleId: "clients" }, }, { path: "manage", diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 0d75973712..20350fc600 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -4,13 +4,16 @@ import { FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchModule } from "@bitwarden/components"; +import { DangerZoneComponent } from "@bitwarden/web-vault/app/auth/settings/account/danger-zone.component"; import { OrganizationPlansComponent, TaxInfoComponent } from "@bitwarden/web-vault/app/billing"; import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; -import { DangerZoneComponent } from "../../../../../../apps/web/src/app/auth/settings/account/danger-zone.component"; -import { ManageClientOrganizationSubscriptionComponent } from "../../billing/providers/clients/manage-client-organization-subscription.component"; -import { ManageClientOrganizationsComponent } from "../../billing/providers/clients/manage-client-organizations.component"; +import { + CreateClientOrganizationComponent, + ManageClientOrganizationSubscriptionComponent, + ManageClientOrganizationsComponent, +} from "../../billing/providers/clients"; import { AddOrganizationComponent } from "./clients/add-organization.component"; import { ClientsComponent } from "./clients/clients.component"; @@ -56,6 +59,7 @@ import { SetupComponent } from "./setup/setup.component"; SetupComponent, SetupProviderComponent, UserAddEditComponent, + CreateClientOrganizationComponent, ManageClientOrganizationsComponent, ManageClientOrganizationSubscriptionComponent, ], diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts index 4f715fd5c5..4195ffcb05 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts @@ -1,8 +1,15 @@ import { Injectable } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { ProviderAddOrganizationRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-add-organization.request"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; +import { PlanType } from "@bitwarden/common/billing/enums"; +import { CreateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/create-client-organization.request"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { OrgKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @Injectable() @@ -11,6 +18,9 @@ export class WebProviderService { private cryptoService: CryptoService, private syncService: SyncService, private apiService: ApiService, + private i18nService: I18nService, + private encryptService: EncryptService, + private billingApiService: BillingApiServiceAbstraction, ) {} async addOrganizationToProvider(providerId: string, organizationId: string) { @@ -28,6 +38,46 @@ export class WebProviderService { return response; } + async createClientOrganization( + providerId: string, + name: string, + ownerEmail: string, + planType: PlanType, + seats: number, + ): Promise { + const organizationKey = (await this.cryptoService.makeOrgKey())[1]; + + const [publicKey, encryptedPrivateKey] = await this.cryptoService.makeKeyPair(organizationKey); + + const encryptedCollectionName = await this.encryptService.encrypt( + this.i18nService.t("defaultCollection"), + organizationKey, + ); + + const providerKey = await this.cryptoService.getProviderKey(providerId); + + const encryptedProviderKey = await this.encryptService.encrypt( + organizationKey.key, + providerKey, + ); + + const request = new CreateClientOrganizationRequest(); + request.name = name; + request.ownerEmail = ownerEmail; + request.planType = planType; + request.seats = seats; + + request.key = encryptedProviderKey.encryptedString; + request.keyPair = new OrganizationKeysRequest(publicKey, encryptedPrivateKey.encryptedString); + request.collectionName = encryptedCollectionName.encryptedString; + + await this.billingApiService.createClientOrganization(providerId, request); + + await this.apiService.refreshIdentityToken(); + + await this.syncService.fullSync(true); + } + async detachOrganization(providerId: string, organizationId: string): Promise { await this.apiService.deleteProviderOrganization(providerId, organizationId); await this.syncService.fullSync(true); diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html new file mode 100644 index 0000000000..4c5d9fca9b --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html @@ -0,0 +1,69 @@ +
+ + + {{ "newClientOrganization" | i18n }} + +
+

{{ "createNewClientToManageAsProvider" | i18n }}

+
+ {{ "selectAPlan" | i18n }} + {{ "thirtyFivePercentDiscount" | i18n }} +
+ +
+
+
+
+ {{ "selected" | i18n }} +
+
+

{{ planCard.name }}

+ {{ + planCard.cost | currency: "$" + }} + /{{ "monthPerMember" | i18n }} +
+
+
+
+
+
+ + + {{ "organizationName" | i18n }} + + + + + + {{ "clientOwnerEmail" | i18n }} + + + +
+
+ + + {{ "seats" | i18n }} + + + +
+
+ + + + +
+
diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts new file mode 100644 index 0000000000..8427572516 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts @@ -0,0 +1,142 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; + +import { PlanType } from "@bitwarden/common/billing/enums"; +import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { DialogService } from "@bitwarden/components"; + +import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; + +type CreateClientOrganizationParams = { + providerId: string; + plans: PlanResponse[]; +}; + +export enum CreateClientOrganizationResultType { + Closed = "closed", + Submitted = "submitted", +} + +export const openCreateClientOrganizationDialog = ( + dialogService: DialogService, + dialogConfig: DialogConfig, +) => + dialogService.open( + CreateClientOrganizationComponent, + dialogConfig, + ); + +type PlanCard = { + name: string; + cost: number; + type: PlanType; + selected: boolean; +}; + +@Component({ + selector: "app-create-client-organization", + templateUrl: "./create-client-organization.component.html", +}) +export class CreateClientOrganizationComponent implements OnInit { + protected ResultType = CreateClientOrganizationResultType; + protected formGroup = this.formBuilder.group({ + clientOwnerEmail: ["", [Validators.required, Validators.email]], + organizationName: ["", Validators.required], + seats: [null, [Validators.required, Validators.min(1)]], + }); + protected planCards: PlanCard[]; + + constructor( + @Inject(DIALOG_DATA) private dialogParams: CreateClientOrganizationParams, + private dialogRef: DialogRef, + private formBuilder: FormBuilder, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private webProviderService: WebProviderService, + ) {} + + protected getPlanCardContainerClasses(selected: boolean) { + switch (selected) { + case true: { + return [ + "tw-group", + "tw-cursor-pointer", + "tw-block", + "tw-rounded", + "tw-border", + "tw-border-solid", + "tw-border-primary-600", + "hover:tw-border-primary-700", + "focus:tw-border-2", + "focus:tw-border-primary-700", + "focus:tw-rounded-lg", + ]; + } + case false: { + return [ + "tw-cursor-pointer", + "tw-block", + "tw-rounded", + "tw-border", + "tw-border-solid", + "tw-border-secondary-300", + "hover:tw-border-text-main", + "focus:tw-border-2", + "focus:tw-border-primary-700", + ]; + } + } + } + + async ngOnInit(): Promise { + const teamsPlan = this.dialogParams.plans.find((plan) => plan.type === PlanType.TeamsMonthly); + const enterprisePlan = this.dialogParams.plans.find( + (plan) => plan.type === PlanType.EnterpriseMonthly, + ); + + this.planCards = [ + { + name: this.i18nService.t("planNameTeams"), + cost: teamsPlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs, + type: teamsPlan.type, + selected: true, + }, + { + name: this.i18nService.t("planNameEnterprise"), + cost: enterprisePlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs, + type: enterprisePlan.type, + selected: false, + }, + ]; + } + + protected selectPlan(name: string) { + this.planCards.find((planCard) => planCard.name === name).selected = true; + this.planCards.find((planCard) => planCard.name !== name).selected = false; + } + + submit = async () => { + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + const selectedPlanCard = this.planCards.find((planCard) => planCard.selected); + + await this.webProviderService.createClientOrganization( + this.dialogParams.providerId, + this.formGroup.value.organizationName, + this.formGroup.value.clientOwnerEmail, + selectedPlanCard.type, + this.formGroup.value.seats, + ); + + this.platformUtilsService.showToast("success", null, this.i18nService.t("createdNewClient")); + + this.dialogRef.close(this.ResultType.Submitted); + }; +} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts new file mode 100644 index 0000000000..fd9ef8296c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts @@ -0,0 +1,3 @@ +export * from "./create-client-organization.component"; +export * from "./manage-client-organizations.component"; +export * from "./manage-client-organization-subscription.component"; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts index 2c8d59edc3..2182ac43ab 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts @@ -3,7 +3,7 @@ import { Component, Inject, OnInit } from "@angular/core"; import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; -import { ProviderSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/provider-subscription-update.request"; +import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request"; import { Plans } from "@bitwarden/common/billing/models/response/provider-subscription-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -45,7 +45,7 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit { async ngOnInit() { try { - const response = await this.billingApiService.getProviderClientSubscriptions(this.providerId); + const response = await this.billingApiService.getProviderSubscription(this.providerId); this.AdditionalSeatPurchased = this.getPurchasedSeatsByPlan(this.planName, response.plans); const seatMinimum = this.getProviderSeatMinimumByPlan(this.planName, response.plans); const assignedByPlan = this.getAssignedByPlan(this.planName, response.plans); @@ -69,10 +69,10 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit { return; } - const request = new ProviderSubscriptionUpdateRequest(); + const request = new UpdateClientOrganizationRequest(); request.assignedSeats = assignedSeats; - await this.billingApiService.putProviderClientSubscriptions( + await this.billingApiService.updateClientOrganization( this.providerId, this.providerOrganizationId, request, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html index dc303d338f..ec5df609c4 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html @@ -1,6 +1,12 @@ - + {{ "addNewOrganization" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.ts index a9f341be94..2184a617cf 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.ts @@ -1,7 +1,7 @@ import { SelectionModel } from "@angular/cdk/collections"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { BehaviorSubject, Subject, firstValueFrom, from } from "rxjs"; +import { BehaviorSubject, firstValueFrom, from, lastValueFrom, Subject } from "rxjs"; import { first, switchMap, takeUntil } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -9,6 +9,8 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; +import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; +import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; @@ -16,6 +18,10 @@ import { DialogService, TableDataSource } from "@bitwarden/components"; import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; +import { + CreateClientOrganizationResultType, + openCreateClientOrganizationDialog, +} from "./create-client-organization.component"; import { ManageClientOrganizationSubscriptionComponent } from "./manage-client-organization-subscription.component"; @Component({ @@ -52,6 +58,7 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy { private pagedClientsCount = 0; selection = new SelectionModel(true, []); protected dataSource = new TableDataSource(); + protected plans: PlanResponse[]; constructor( private route: ActivatedRoute, @@ -63,6 +70,7 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy { private validationService: ValidationService, private webProviderService: WebProviderService, private dialogService: DialogService, + private billingApiService: BillingApiService, ) {} async ngOnInit() { @@ -94,12 +102,16 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy { } async load() { - const response = await this.apiService.getProviderClients(this.providerId); - this.clients = response.data != null && response.data.length > 0 ? response.data : []; + const clientsResponse = await this.apiService.getProviderClients(this.providerId); + this.clients = + clientsResponse.data != null && clientsResponse.data.length > 0 ? clientsResponse.data : []; this.dataSource.data = this.clients; this.manageOrganizations = (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin; + const plansResponse = await this.billingApiService.getPlans(); + this.plans = plansResponse.data; + this.loading = false; } @@ -177,4 +189,21 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy { } this.actionPromise = null; } + + createClientOrganization = async () => { + const reference = openCreateClientOrganizationDialog(this.dialogService, { + data: { + providerId: this.providerId, + plans: this.plans, + }, + }); + + const result = await lastValueFrom(reference.closed); + + if (result === CreateClientOrganizationResultType.Closed) { + return; + } + + await this.load(); + }; } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index ad0881a4b3..cc6d947b33 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1051,10 +1051,12 @@ const safeProviders: SafeProvider[] = [ provide: OrganizationBillingServiceAbstraction, useClass: OrganizationBillingService, deps: [ + ApiServiceAbstraction, CryptoServiceAbstraction, EncryptService, I18nServiceAbstraction, OrganizationApiServiceAbstraction, + SyncServiceAbstraction, ], }), safeProvider({ diff --git a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts index 1311976c4b..16b86c5844 100644 --- a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts @@ -1,6 +1,9 @@ import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; -import { ProviderSubscriptionUpdateRequest } from "../models/request/provider-subscription-update.request"; +import { PlanResponse } from "../../billing/models/response/plan.response"; +import { ListResponse } from "../../models/response/list.response"; +import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export abstract class BillingApiServiceAbstraction { @@ -9,11 +12,16 @@ export abstract class BillingApiServiceAbstraction { request: SubscriptionCancellationRequest, ) => Promise; cancelPremiumUserSubscription: (request: SubscriptionCancellationRequest) => Promise; + createClientOrganization: ( + providerId: string, + request: CreateClientOrganizationRequest, + ) => Promise; getBillingStatus: (id: string) => Promise; - getProviderClientSubscriptions: (providerId: string) => Promise; - putProviderClientSubscriptions: ( + getPlans: () => Promise>; + getProviderSubscription: (providerId: string) => Promise; + updateClientOrganization: ( providerId: string, organizationId: string, - request: ProviderSubscriptionUpdateRequest, + request: UpdateClientOrganizationRequest, ) => Promise; } diff --git a/libs/common/src/billing/models/request/create-client-organization.request.ts b/libs/common/src/billing/models/request/create-client-organization.request.ts new file mode 100644 index 0000000000..2eac23531a --- /dev/null +++ b/libs/common/src/billing/models/request/create-client-organization.request.ts @@ -0,0 +1,12 @@ +import { OrganizationKeysRequest } from "../../../admin-console/models/request/organization-keys.request"; +import { PlanType } from "../../../billing/enums"; + +export class CreateClientOrganizationRequest { + name: string; + ownerEmail: string; + planType: PlanType; + seats: number; + key: string; + keyPair: OrganizationKeysRequest; + collectionName: string; +} diff --git a/libs/common/src/billing/models/request/provider-subscription-update.request.ts b/libs/common/src/billing/models/request/provider-subscription-update.request.ts deleted file mode 100644 index f2bf4c7e97..0000000000 --- a/libs/common/src/billing/models/request/provider-subscription-update.request.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class ProviderSubscriptionUpdateRequest { - assignedSeats: number; -} diff --git a/libs/common/src/billing/models/request/update-client-organization.request.ts b/libs/common/src/billing/models/request/update-client-organization.request.ts new file mode 100644 index 0000000000..16dbe1e17d --- /dev/null +++ b/libs/common/src/billing/models/request/update-client-organization.request.ts @@ -0,0 +1,3 @@ +export class UpdateClientOrganizationRequest { + assignedSeats: number; +} diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 48866ab90d..70d1d89252 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -2,7 +2,10 @@ import { ApiService } from "../../abstractions/api.service"; import { BillingApiServiceAbstraction } from "../../billing/abstractions/billilng-api.service.abstraction"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; -import { ProviderSubscriptionUpdateRequest } from "../models/request/provider-subscription-update.request"; +import { PlanResponse } from "../../billing/models/response/plan.response"; +import { ListResponse } from "../../models/response/list.response"; +import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export class BillingApiService implements BillingApiServiceAbstraction { @@ -25,6 +28,19 @@ export class BillingApiService implements BillingApiServiceAbstraction { return this.apiService.send("POST", "/accounts/cancel", request, true, false); } + createClientOrganization( + providerId: string, + request: CreateClientOrganizationRequest, + ): Promise { + return this.apiService.send( + "POST", + "/providers/" + providerId + "/clients", + request, + true, + false, + ); + } + async getBillingStatus(id: string): Promise { const r = await this.apiService.send( "GET", @@ -37,7 +53,12 @@ export class BillingApiService implements BillingApiServiceAbstraction { return new OrganizationBillingStatusResponse(r); } - async getProviderClientSubscriptions(providerId: string): Promise { + async getPlans(): Promise> { + const r = await this.apiService.send("GET", "/plans", null, false, true); + return new ListResponse(r, PlanResponse); + } + + async getProviderSubscription(providerId: string): Promise { const r = await this.apiService.send( "GET", "/providers/" + providerId + "/billing/subscription", @@ -48,14 +69,14 @@ export class BillingApiService implements BillingApiServiceAbstraction { return new ProviderSubscriptionResponse(r); } - async putProviderClientSubscriptions( + async updateClientOrganization( providerId: string, organizationId: string, - request: ProviderSubscriptionUpdateRequest, + request: UpdateClientOrganizationRequest, ): Promise { return await this.apiService.send( "PUT", - "/providers/" + providerId + "/organizations/" + organizationId, + "/providers/" + providerId + "/clients/" + organizationId, request, true, false, diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index f2df30e4e0..6b326472c9 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -1,3 +1,4 @@ +import { ApiService } from "../../abstractions/api.service"; import { OrganizationApiServiceAbstraction as OrganizationApiService } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { OrganizationKeysRequest } from "../../admin-console/models/request/organization-keys.request"; @@ -7,6 +8,7 @@ import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { EncString } from "../../platform/models/domain/enc-string"; import { OrgKey } from "../../types/key"; +import { SyncService } from "../../vault/abstractions/sync/sync.service.abstraction"; import { OrganizationBillingServiceAbstraction, OrganizationInformation, @@ -25,10 +27,12 @@ interface OrganizationKeys { export class OrganizationBillingService implements OrganizationBillingServiceAbstraction { constructor( + private apiService: ApiService, private cryptoService: CryptoService, private encryptService: EncryptService, private i18nService: I18nService, private organizationApiService: OrganizationApiService, + private syncService: SyncService, ) {} async purchaseSubscription(subscription: SubscriptionInformation): Promise { @@ -44,7 +48,13 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs this.setPaymentInformation(request, subscription.payment); - return await this.organizationApiService.create(request); + const response = await this.organizationApiService.create(request); + + await this.apiService.refreshIdentityToken(); + + await this.syncService.fullSync(true); + + return response; } async startFree(subscription: SubscriptionInformation): Promise { @@ -58,7 +68,13 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs this.setPlanInformation(request, subscription.plan); - return await this.organizationApiService.create(request); + const response = await this.organizationApiService.create(request); + + await this.apiService.refreshIdentityToken(); + + await this.syncService.fullSync(true); + + return response; } private async makeOrganizationKeys(): Promise { From 2c8855692c70e931c1f38d425445ca9718141600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Thu, 18 Apr 2024 14:17:34 +0100 Subject: [PATCH 26/86] [AC-2209] Update the dash in the admin console collections table permission column to no access (#8609) * [AC-2209] Update permission text to display "No access" when collection is not assigned * [AC-2209] Add permission tooltip for unassigned collections --- .../vault-items/vault-collection-row.component.html | 2 +- .../vault-items/vault-collection-row.component.ts | 12 ++++++++++-- apps/web/src/locales/en/messages.json | 6 ++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html index a6d7854267..d333f92d5c 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html @@ -48,7 +48,7 @@ > -

+

{{ permissionText }}

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 666bec7a1a..8bf7779f88 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 @@ -6,6 +6,7 @@ import { CollectionView } from "@bitwarden/common/vault/models/view/collection.v import { GroupView } from "../../../admin-console/organizations/core"; import { CollectionAdminView } from "../../core/views/collection-admin.view"; +import { Unassigned } from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model"; import { convertToPermission, @@ -52,8 +53,8 @@ export class VaultCollectionRowComponent { } get permissionText() { - if (!(this.collection as CollectionAdminView).assigned) { - return "-"; + if (this.collection.id != Unassigned && !(this.collection as CollectionAdminView).assigned) { + return this.i18nService.t("noAccess"); } else { const permissionList = getPermissionList(this.organization?.flexibleCollections); return this.i18nService.t( @@ -62,6 +63,13 @@ export class VaultCollectionRowComponent { } } + get permissionTooltip() { + if (this.collection.id == Unassigned) { + return this.i18nService.t("collectionAdminConsoleManaged"); + } + return ""; + } + protected edit() { this.onEvent.next({ type: "editCollection", item: this.collection }); } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 49611145dd..7632392c23 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7979,5 +7979,11 @@ }, "createdNewClient": { "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } From a45706cde7bb1d6341c3c24b4229d7cf71f2eff4 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Thu, 18 Apr 2024 11:01:14 -0400 Subject: [PATCH 27/86] SM-1181: Rename service accounts to machine accounts in routing (#8688) --- .../src/app/secrets-manager/layout/navigation.component.html | 2 +- .../secrets-manager/projects/project/project.component.html | 2 +- .../app/secrets-manager/projects/projects-routing.module.ts | 2 +- .../service-accounts/guards/service-account-access.guard.ts | 4 ++-- .../people/service-account-people.component.ts | 4 ++-- .../service-accounts/service-account.component.ts | 2 +- .../bit-web/src/app/secrets-manager/sm-routing.module.ts | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) 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 e71f520996..c6c7bc6efb 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 @@ -19,7 +19,7 @@ {{ "secrets" | i18n }} {{ "people" | i18n }} - {{ "machineAccounts" | i18n }} + {{ "machineAccounts" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts index a5248c509f..6078520989 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts @@ -30,7 +30,7 @@ const routes: Routes = [ component: ProjectPeopleComponent, }, { - path: "service-accounts", + path: "machine-accounts", component: ProjectServiceAccountsComponent, }, ], diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts index 6258f6f9db..c474ec44d5 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts @@ -21,8 +21,8 @@ export const serviceAccountAccessGuard: CanActivateFn = async (route: ActivatedR return createUrlTreeFromSnapshot(route, [ "/sm", route.params.organizationId, - "service-accounts", + "machine-accounts", ]); } - return createUrlTreeFromSnapshot(route, ["/sm", route.params.organizationId, "service-accounts"]); + return createUrlTreeFromSnapshot(route, ["/sm", route.params.organizationId, "machine-accounts"]); }; 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 76b5e8928d..aeb124aa6a 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 @@ -43,7 +43,7 @@ export class ServiceAccountPeopleComponent implements OnInit, OnDestroy { catchError(() => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/sm", this.organizationId, "service-accounts"]); + this.router.navigate(["/sm", this.organizationId, "machine-accounts"]); return EMPTY; }), ); @@ -200,7 +200,7 @@ export class ServiceAccountPeopleComponent implements OnInit, OnDestroy { if (showAccessRemovalWarning) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["sm", this.organizationId, "service-accounts"]); + this.router.navigate(["sm", this.organizationId, "machine-accounts"]); } else if ( this.accessPolicySelectorService.isAccessRemoval(currentAccessPolicies, selectedPolicies) ) { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts index 083ec7aebb..bb687c51c6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts @@ -45,7 +45,7 @@ export class ServiceAccountComponent implements OnInit, OnDestroy { catchError(() => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/sm", this.organizationId, "service-accounts"]).then(() => { + this.router.navigate(["/sm", this.organizationId, "machine-accounts"]).then(() => { this.platformUtilsService.showToast( "error", null, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts index 55dc2f8b71..10aa08612f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts @@ -54,7 +54,7 @@ const routes: Routes = [ }, }, { - path: "service-accounts", + path: "machine-accounts", loadChildren: () => ServiceAccountsModule, data: { titleId: "machineAccounts", From adb1ee3d38066e81ec7d79cf7c94dfc884c2b9d6 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 18 Apr 2024 11:21:11 -0400 Subject: [PATCH 28/86] [AC-2420] Hide SM checkbox on member invite when org is on SM Standalone (#8644) * Refactoring * Hide SM toggle on member invite and default to true for SM standalone org * changed from hide sm checkbox to default and disable * Removed errant addition from conflict resolution --- .../member-dialog/member-dialog.component.html | 7 ++++++- .../member-dialog/member-dialog.component.ts | 9 +++++++++ .../organizations/members/people.component.ts | 9 +++++++++ .../angular/src/services/jslib-services.module.ts | 1 + .../billilng-api.service.abstraction.ts | 5 +++++ .../abstractions/organization-billing.service.ts | 2 ++ .../src/billing/services/billing-api.service.ts | 15 ++++++++++++++- .../services/organization-billing.service.ts | 15 +++++++++++++++ 8 files changed, 61 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html index 95febbd3c5..4d81d070fb 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html @@ -385,7 +385,12 @@

{{ "secretsManagerAccessDescription" | i18n }}

- + {{ "userAccessSecretsManagerGA" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 771b8cc505..f1af950650 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 @@ -63,6 +63,7 @@ export interface MemberDialogParams { organizationUserId: string; allOrganizationUserEmails: string[]; usesKeyConnector: boolean; + isOnSecretsManagerStandalone: boolean; initialTab?: MemberDialogTab; numConfirmedMembers: number; } @@ -88,6 +89,7 @@ export class MemberDialogComponent implements OnDestroy { organizationUserType = OrganizationUserType; PermissionMode = PermissionMode; showNoMasterPasswordWarning = false; + isOnSecretsManagerStandalone: boolean; protected organization$: Observable; protected collectionAccessItems: AccessItemView[] = []; @@ -160,6 +162,13 @@ export class MemberDialogComponent implements OnDestroy { this.editMode = this.params.organizationUserId != null; this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role; this.title = this.i18nService.t(this.editMode ? "editMember" : "inviteMember"); + this.isOnSecretsManagerStandalone = this.params.isOnSecretsManagerStandalone; + + if (this.isOnSecretsManagerStandalone) { + this.formGroup.patchValue({ + accessSecretsManager: true, + }); + } const groups$ = this.organization$.pipe( switchMap((organization) => diff --git a/apps/web/src/app/admin-console/organizations/members/people.component.ts b/apps/web/src/app/admin-console/organizations/members/people.component.ts index 6b632dce38..0df247d7b0 100644 --- a/apps/web/src/app/admin-console/organizations/members/people.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/people.component.ts @@ -37,6 +37,7 @@ import { import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; +import { OrganizationBillingServiceAbstraction as OrganizationBillingService } from "@bitwarden/common/billing/abstractions/organization-billing.service"; import { ProductType } from "@bitwarden/common/enums"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -93,6 +94,7 @@ export class PeopleComponent extends BasePeopleComponent { organization: Organization; status: OrganizationUserStatusType = null; orgResetPasswordPolicyEnabled = false; + orgIsOnSecretsManagerStandalone = false; protected canUseSecretsManager$: Observable; @@ -119,6 +121,7 @@ export class PeopleComponent extends BasePeopleComponent { private groupService: GroupService, private collectionService: CollectionService, organizationManagementPreferencesService: OrganizationManagementPreferencesService, + private organizationBillingService: OrganizationBillingService, ) { super( apiService, @@ -187,6 +190,11 @@ export class PeopleComponent extends BasePeopleComponent { .find((p) => p.organizationId === this.organization.id); this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled; + this.orgIsOnSecretsManagerStandalone = + await this.organizationBillingService.isOnSecretsManagerStandalone( + this.organization.id, + ); + await this.load(); this.searchText = qParams.search; @@ -446,6 +454,7 @@ export class PeopleComponent extends BasePeopleComponent { organizationUserId: user != null ? user.id : null, allOrganizationUserEmails: this.allUsers?.map((user) => user.email) ?? [], usesKeyConnector: user?.usesKeyConnector, + isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: initialTab, numConfirmedMembers: this.confirmedCount, }, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index cc6d947b33..859103474d 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1052,6 +1052,7 @@ const safeProviders: SafeProvider[] = [ useClass: OrganizationBillingService, deps: [ ApiServiceAbstraction, + BillingApiServiceAbstraction, CryptoServiceAbstraction, EncryptService, I18nServiceAbstraction, diff --git a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts index 16b86c5844..15f0d4b551 100644 --- a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts @@ -1,5 +1,6 @@ import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; +import { OrganizationSubscriptionResponse } from "../../billing/models/response/organization-subscription.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; @@ -11,12 +12,16 @@ export abstract class BillingApiServiceAbstraction { organizationId: string, request: SubscriptionCancellationRequest, ) => Promise; + cancelPremiumUserSubscription: (request: SubscriptionCancellationRequest) => Promise; createClientOrganization: ( providerId: string, request: CreateClientOrganizationRequest, ) => Promise; getBillingStatus: (id: string) => Promise; + getOrganizationSubscription: ( + organizationId: string, + ) => Promise; getPlans: () => Promise>; getProviderSubscription: (providerId: string) => Promise; updateClientOrganization: ( diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts index d19724b600..0917025eec 100644 --- a/libs/common/src/billing/abstractions/organization-billing.service.ts +++ b/libs/common/src/billing/abstractions/organization-billing.service.ts @@ -41,6 +41,8 @@ export type SubscriptionInformation = { }; export abstract class OrganizationBillingServiceAbstraction { + isOnSecretsManagerStandalone: (organizationId: string) => Promise; + purchaseSubscription: (subscription: SubscriptionInformation) => Promise; startFree: (subscription: SubscriptionInformation) => Promise; diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 70d1d89252..1c119b971d 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -2,6 +2,7 @@ import { ApiService } from "../../abstractions/api.service"; import { BillingApiServiceAbstraction } from "../../billing/abstractions/billilng-api.service.abstraction"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; +import { OrganizationSubscriptionResponse } from "../../billing/models/response/organization-subscription.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; @@ -49,10 +50,22 @@ export class BillingApiService implements BillingApiServiceAbstraction { true, true, ); - return new OrganizationBillingStatusResponse(r); } + async getOrganizationSubscription( + organizationId: string, + ): Promise { + const r = await this.apiService.send( + "GET", + "/organizations/" + organizationId + "/subscription", + null, + true, + true, + ); + return new OrganizationSubscriptionResponse(r); + } + async getPlans(): Promise> { const r = await this.apiService.send("GET", "/plans", null, false, true); return new ListResponse(r, PlanResponse); diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index 6b326472c9..fb2084bb6a 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -9,6 +9,7 @@ import { I18nService } from "../../platform/abstractions/i18n.service"; import { EncString } from "../../platform/models/domain/enc-string"; import { OrgKey } from "../../types/key"; import { SyncService } from "../../vault/abstractions/sync/sync.service.abstraction"; +import { BillingApiServiceAbstraction as BillingApiService } from "../abstractions/billilng-api.service.abstraction"; import { OrganizationBillingServiceAbstraction, OrganizationInformation, @@ -28,6 +29,7 @@ interface OrganizationKeys { export class OrganizationBillingService implements OrganizationBillingServiceAbstraction { constructor( private apiService: ApiService, + private billingApiService: BillingApiService, private cryptoService: CryptoService, private encryptService: EncryptService, private i18nService: I18nService, @@ -35,6 +37,19 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs private syncService: SyncService, ) {} + async isOnSecretsManagerStandalone(organizationId: string): Promise { + const response = await this.billingApiService.getOrganizationSubscription(organizationId); + if (response.customerDiscount?.id === "sm-standalone") { + const productIds = response.subscription.items.map((item) => item.productId); + return ( + response.customerDiscount?.appliesTo.filter((appliesToProductId) => + productIds.includes(appliesToProductId), + ).length > 0 + ); + } + return false; + } + async purchaseSubscription(subscription: SubscriptionInformation): Promise { const request = new OrganizationCreateRequest(); From 912b7c136ea4c056f7755dac17d10f3c7afa2a7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 18 Apr 2024 17:40:39 +0200 Subject: [PATCH 29/86] [PM-5796] Improve desktop biometric browser integration error handling (#7727) * Re-register native messaging host integrations on startup * Check for errors when generating the manifests * Add log to component * Switch to Promise.all * Add injectable service --- .../background/nativeMessaging.background.ts | 2 + .../src/app/accounts/settings.component.ts | 37 ++- .../native-messaging-manifest.service.ts | 13 + .../src/app/services/services.module.ts | 6 + apps/desktop/src/locales/en/messages.json | 6 + apps/desktop/src/main.ts | 20 +- apps/desktop/src/main/messaging.main.ts | 16 -- .../desktop/src/main/native-messaging.main.ts | 262 +++++++++--------- apps/desktop/src/platform/preload.ts | 7 + 9 files changed, 213 insertions(+), 156 deletions(-) create mode 100644 apps/desktop/src/app/services/native-messaging-manifest.service.ts diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index faf2e6e2cc..e5eed06c21 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -204,6 +204,8 @@ export class NativeMessagingBackground { this.privateKey = null; this.connected = false; + this.logService.error("NativeMessaging port disconnected because of error: " + error); + const reason = error != null ? "desktopIntegrationDisabled" : null; reject(new Error(reason)); }); diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 5f59530d8c..06533e18fc 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -14,6 +14,7 @@ import { DeviceType } from "@bitwarden/common/enums"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; @@ -27,6 +28,7 @@ import { DialogService } from "@bitwarden/components"; import { SetPinComponent } from "../../auth/components/set-pin.component"; import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; +import { NativeMessagingManifestService } from "../services/native-messaging-manifest.service"; @Component({ selector: "app-settings", @@ -126,6 +128,8 @@ export class SettingsComponent implements OnInit { private biometricStateService: BiometricStateService, private desktopAutofillSettingsService: DesktopAutofillSettingsService, private authRequestService: AuthRequestServiceAbstraction, + private logService: LogService, + private nativeMessagingManifestService: NativeMessagingManifestService, ) { const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop; @@ -628,11 +632,20 @@ export class SettingsComponent implements OnInit { } await this.stateService.setEnableBrowserIntegration(this.form.value.enableBrowserIntegration); - this.messagingService.send( - this.form.value.enableBrowserIntegration - ? "enableBrowserIntegration" - : "disableBrowserIntegration", + + const errorResult = await this.nativeMessagingManifestService.generate( + this.form.value.enableBrowserIntegration, ); + if (errorResult !== null) { + this.logService.error("Error in browser integration: " + errorResult); + await this.dialogService.openSimpleDialog({ + title: { key: "browserIntegrationErrorTitle" }, + content: { key: "browserIntegrationErrorDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "danger", + }); + } if (!this.form.value.enableBrowserIntegration) { this.form.controls.enableBrowserIntegrationFingerprint.setValue(false); @@ -651,11 +664,19 @@ export class SettingsComponent implements OnInit { await this.stateService.setDuckDuckGoSharedKey(null); } - this.messagingService.send( - this.form.value.enableDuckDuckGoBrowserIntegration - ? "enableDuckDuckGoBrowserIntegration" - : "disableDuckDuckGoBrowserIntegration", + const errorResult = await this.nativeMessagingManifestService.generateDuckDuckGo( + this.form.value.enableDuckDuckGoBrowserIntegration, ); + if (errorResult !== null) { + this.logService.error("Error in DDG browser integration: " + errorResult); + await this.dialogService.openSimpleDialog({ + title: { key: "browserIntegrationUnsupportedTitle" }, + content: errorResult.message, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "warning", + }); + } } async saveBrowserIntegrationFingerprint() { diff --git a/apps/desktop/src/app/services/native-messaging-manifest.service.ts b/apps/desktop/src/app/services/native-messaging-manifest.service.ts new file mode 100644 index 0000000000..6cc58a581b --- /dev/null +++ b/apps/desktop/src/app/services/native-messaging-manifest.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from "@angular/core"; + +@Injectable() +export class NativeMessagingManifestService { + constructor() {} + + async generate(create: boolean): Promise { + return ipc.platform.nativeMessaging.manifests.generate(create); + } + async generateDuckDuckGo(create: boolean): Promise { + return ipc.platform.nativeMessaging.manifests.generateDuckDuckGo(create); + } +} diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 8e412d4977..264f26cbe2 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -76,6 +76,7 @@ import { SearchBarService } from "../layout/search/search-bar.service"; import { DesktopFileDownloadService } from "./desktop-file-download.service"; import { InitService } from "./init.service"; +import { NativeMessagingManifestService } from "./native-messaging-manifest.service"; import { RendererCryptoFunctionService } from "./renderer-crypto-function.service"; const RELOAD_CALLBACK = new SafeInjectionToken<() => any>("RELOAD_CALLBACK"); @@ -249,6 +250,11 @@ const safeProviders: SafeProvider[] = [ provide: DesktopAutofillSettingsService, deps: [StateProvider], }), + safeProvider({ + provide: NativeMessagingManifestService, + useClass: NativeMessagingManifestService, + deps: [], + }), ]; @NgModule({ diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 00eace54e2..3d2b40ac62 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1632,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 67f08839c5..a4783e0573 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -291,12 +291,20 @@ export class Main { this.powerMonitorMain.init(); await this.updaterMain.init(); - if ( - (await this.stateService.getEnableBrowserIntegration()) || - (await firstValueFrom( - this.desktopAutofillSettingsService.enableDuckDuckGoBrowserIntegration$, - )) - ) { + const [browserIntegrationEnabled, ddgIntegrationEnabled] = await Promise.all([ + this.stateService.getEnableBrowserIntegration(), + firstValueFrom(this.desktopAutofillSettingsService.enableDuckDuckGoBrowserIntegration$), + ]); + + if (browserIntegrationEnabled || ddgIntegrationEnabled) { + // Re-register the native messaging host integrations on startup, in case they are not present + if (browserIntegrationEnabled) { + this.nativeMessagingMain.generateManifests().catch(this.logService.error); + } + if (ddgIntegrationEnabled) { + this.nativeMessagingMain.generateDdgManifests().catch(this.logService.error); + } + this.nativeMessagingMain.listen(); } diff --git a/apps/desktop/src/main/messaging.main.ts b/apps/desktop/src/main/messaging.main.ts index 256d551560..a9f80b7d20 100644 --- a/apps/desktop/src/main/messaging.main.ts +++ b/apps/desktop/src/main/messaging.main.ts @@ -75,22 +75,6 @@ export class MessagingMain { case "getWindowIsFocused": this.windowIsFocused(); break; - case "enableBrowserIntegration": - this.main.nativeMessagingMain.generateManifests(); - this.main.nativeMessagingMain.listen(); - break; - case "enableDuckDuckGoBrowserIntegration": - this.main.nativeMessagingMain.generateDdgManifests(); - this.main.nativeMessagingMain.listen(); - break; - case "disableBrowserIntegration": - this.main.nativeMessagingMain.removeManifests(); - this.main.nativeMessagingMain.stop(); - break; - case "disableDuckDuckGoBrowserIntegration": - this.main.nativeMessagingMain.removeDdgManifests(); - this.main.nativeMessagingMain.stop(); - break; default: break; } diff --git a/apps/desktop/src/main/native-messaging.main.ts b/apps/desktop/src/main/native-messaging.main.ts index 05e987e20b..d3dd25c644 100644 --- a/apps/desktop/src/main/native-messaging.main.ts +++ b/apps/desktop/src/main/native-messaging.main.ts @@ -22,7 +22,55 @@ export class NativeMessagingMain { private windowMain: WindowMain, private userPath: string, private exePath: string, - ) {} + ) { + ipcMain.handle( + "nativeMessaging.manifests", + async (_event: any, options: { create: boolean }) => { + if (options.create) { + this.listen(); + try { + await this.generateManifests(); + } catch (e) { + this.logService.error("Error generating manifests: " + e); + return e; + } + } else { + this.stop(); + try { + await this.removeManifests(); + } catch (e) { + this.logService.error("Error removing manifests: " + e); + return e; + } + } + return null; + }, + ); + + ipcMain.handle( + "nativeMessaging.ddgManifests", + async (_event: any, options: { create: boolean }) => { + if (options.create) { + this.listen(); + try { + await this.generateDdgManifests(); + } catch (e) { + this.logService.error("Error generating duckduckgo manifests: " + e); + return e; + } + } else { + this.stop(); + try { + await this.removeDdgManifests(); + } catch (e) { + this.logService.error("Error removing duckduckgo manifests: " + e); + return e; + } + } + return null; + }, + ); + } listen() { ipc.config.id = "bitwarden"; @@ -76,7 +124,7 @@ export class NativeMessagingMain { ipc.server.emit(socket, "message", message); } - generateManifests() { + async generateManifests() { const baseJson = { name: "com.8bit.bitwarden", description: "Bitwarden desktop <-> browser bridge", @@ -84,6 +132,10 @@ export class NativeMessagingMain { type: "stdio", }; + if (!existsSync(baseJson.path)) { + throw new Error(`Unable to find binary: ${baseJson.path}`); + } + const firefoxJson = { ...baseJson, ...{ allowed_extensions: ["{446900e4-71c2-419f-a6a7-df9c091e268b}"] }, @@ -92,8 +144,11 @@ export class NativeMessagingMain { ...baseJson, ...{ allowed_origins: [ + // Chrome extension "chrome-extension://nngceckbapebfimnlniiiahkandclblb/", + // Edge extension "chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh/", + // Opera extension "chrome-extension://ccnckbpmaceehanjmeomladnmlffdjgn/", ], }, @@ -102,27 +157,17 @@ export class NativeMessagingMain { switch (process.platform) { case "win32": { const destination = path.join(this.userPath, "browsers"); - // 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.writeManifest(path.join(destination, "firefox.json"), firefoxJson); - // 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.writeManifest(path.join(destination, "chrome.json"), chromeJson); + await this.writeManifest(path.join(destination, "firefox.json"), firefoxJson); + await this.writeManifest(path.join(destination, "chrome.json"), chromeJson); - // 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.createWindowsRegistry( - "HKLM\\SOFTWARE\\Mozilla\\Firefox", - "HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden", - path.join(destination, "firefox.json"), - ); - // 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.createWindowsRegistry( - "HKCU\\SOFTWARE\\Google\\Chrome", - "HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden", - path.join(destination, "chrome.json"), - ); + const nmhs = this.getWindowsNMHS(); + for (const [key, value] of Object.entries(nmhs)) { + let manifestPath = path.join(destination, "chrome.json"); + if (key === "Firefox") { + manifestPath = path.join(destination, "firefox.json"); + } + await this.createWindowsRegistry(value, manifestPath); + } break; } case "darwin": { @@ -136,38 +181,30 @@ export class NativeMessagingMain { manifest = firefoxJson; } - this.writeManifest(p, manifest).catch((e) => - this.logService.error(`Error writing manifest for ${key}. ${e}`), - ); + await this.writeManifest(p, manifest); } else { - this.logService.warning(`${key} not found skipping.`); + this.logService.warning(`${key} not found, skipping.`); } } break; } case "linux": if (existsSync(`${this.homedir()}/.mozilla/`)) { - // 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.writeManifest( + await this.writeManifest( `${this.homedir()}/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json`, firefoxJson, ); } if (existsSync(`${this.homedir()}/.config/google-chrome/`)) { - // 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.writeManifest( + await this.writeManifest( `${this.homedir()}/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json`, chromeJson, ); } if (existsSync(`${this.homedir()}/.config/microsoft-edge/`)) { - // 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.writeManifest( + await this.writeManifest( `${this.homedir()}/.config/microsoft-edge/NativeMessagingHosts/com.8bit.bitwarden.json`, chromeJson, ); @@ -178,20 +215,23 @@ export class NativeMessagingMain { } } - generateDdgManifests() { + async generateDdgManifests() { const manifest = { name: "com.8bit.bitwarden", description: "Bitwarden desktop <-> DuckDuckGo bridge", path: this.binaryPath(), type: "stdio", }; + + if (!existsSync(manifest.path)) { + throw new Error(`Unable to find binary: ${manifest.path}`); + } + switch (process.platform) { case "darwin": { /* eslint-disable-next-line no-useless-escape */ const path = `${this.homedir()}/Library/Containers/com.duckduckgo.macos.browser/Data/Library/Application\ Support/NativeMessagingHosts/com.8bit.bitwarden.json`; - this.writeManifest(path, manifest).catch((e) => - this.logService.error(`Error writing manifest for DuckDuckGo. ${e}`), - ); + await this.writeManifest(path, manifest); break; } default: @@ -199,86 +239,50 @@ export class NativeMessagingMain { } } - removeManifests() { + async removeManifests() { switch (process.platform) { - case "win32": - // 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 - fs.unlink(path.join(this.userPath, "browsers", "firefox.json")); - // 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 - fs.unlink(path.join(this.userPath, "browsers", "chrome.json")); - // 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.deleteWindowsRegistry( - "HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden", - ); - // 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.deleteWindowsRegistry( - "HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden", - ); + case "win32": { + await this.removeIfExists(path.join(this.userPath, "browsers", "firefox.json")); + await this.removeIfExists(path.join(this.userPath, "browsers", "chrome.json")); + + const nmhs = this.getWindowsNMHS(); + for (const [, value] of Object.entries(nmhs)) { + await this.deleteWindowsRegistry(value); + } break; + } case "darwin": { const nmhs = this.getDarwinNMHS(); for (const [, value] of Object.entries(nmhs)) { - const p = path.join(value, "NativeMessagingHosts", "com.8bit.bitwarden.json"); - if (existsSync(p)) { - // 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 - fs.unlink(p); - } + await this.removeIfExists( + path.join(value, "NativeMessagingHosts", "com.8bit.bitwarden.json"), + ); } break; } - case "linux": - if ( - existsSync(`${this.homedir()}/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json`) - ) { - // 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 - fs.unlink(`${this.homedir()}/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json`); - } - - if ( - existsSync( - `${this.homedir()}/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json`, - ) - ) { - // 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 - fs.unlink( - `${this.homedir()}/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json`, - ); - } - - if ( - existsSync( - `${this.homedir()}/.config/microsoft-edge/NativeMessagingHosts/com.8bit.bitwarden.json`, - ) - ) { - // 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 - fs.unlink( - `${this.homedir()}/.config/microsoft-edge/NativeMessagingHosts/com.8bit.bitwarden.json`, - ); - } + case "linux": { + await this.removeIfExists( + `${this.homedir()}/.mozilla/native-messaging-hosts/com.8bit.bitwarden.json`, + ); + await this.removeIfExists( + `${this.homedir()}/.config/google-chrome/NativeMessagingHosts/com.8bit.bitwarden.json`, + ); + await this.removeIfExists( + `${this.homedir()}/.config/microsoft-edge/NativeMessagingHosts/com.8bit.bitwarden.json`, + ); break; + } default: break; } } - removeDdgManifests() { + async removeDdgManifests() { switch (process.platform) { case "darwin": { /* eslint-disable-next-line no-useless-escape */ const path = `${this.homedir()}/Library/Containers/com.duckduckgo.macos.browser/Data/Library/Application\ Support/NativeMessagingHosts/com.8bit.bitwarden.json`; - if (existsSync(path)) { - // 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 - fs.unlink(path); - } + await this.removeIfExists(path); break; } default: @@ -286,6 +290,16 @@ export class NativeMessagingMain { } } + private getWindowsNMHS() { + return { + Firefox: "HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden", + Chrome: "HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden", + Chromium: "HKCU\\SOFTWARE\\Chromium\\NativeMessagingHosts\\com.8bit.bitwarden", + // Edge uses the same registry key as Chrome as a fallback, but it's has its own separate key as well. + "Microsoft Edge": "HKCU\\SOFTWARE\\Microsoft\\Edge\\NativeMessagingHosts\\com.8bit.bitwarden", + }; + } + private getDarwinNMHS() { /* eslint-disable no-useless-escape */ return { @@ -305,10 +319,13 @@ export class NativeMessagingMain { } private async writeManifest(destination: string, manifest: object) { + this.logService.debug(`Writing manifest: ${destination}`); + if (!existsSync(path.dirname(destination))) { await fs.mkdir(path.dirname(destination)); } - fs.writeFile(destination, JSON.stringify(manifest, null, 2)).catch(this.logService.error); + + await fs.writeFile(destination, JSON.stringify(manifest, null, 2)); } private binaryPath() { @@ -327,39 +344,26 @@ export class NativeMessagingMain { return regedit; } - private async createWindowsRegistry(check: string, location: string, jsonFile: string) { + private async createWindowsRegistry(location: string, jsonFile: string) { const regedit = this.getRegeditInstance(); - const list = util.promisify(regedit.list); const createKey = util.promisify(regedit.createKey); const putValue = util.promisify(regedit.putValue); this.logService.debug(`Adding registry: ${location}`); - // Check installed - try { - await list(check); - } catch { - this.logService.warning(`Not finding registry ${check} skipping.`); - return; - } + await createKey(location); - try { - await createKey(location); + // Insert path to manifest + const obj: any = {}; + obj[location] = { + default: { + value: jsonFile, + type: "REG_DEFAULT", + }, + }; - // Insert path to manifest - const obj: any = {}; - obj[location] = { - default: { - value: jsonFile, - type: "REG_DEFAULT", - }, - }; - - return putValue(obj); - } catch (error) { - this.logService.error(error); - } + return putValue(obj); } private async deleteWindowsRegistry(key: string) { @@ -385,4 +389,10 @@ export class NativeMessagingMain { return homedir(); } } + + private async removeIfExists(path: string) { + if (existsSync(path)) { + await fs.unlink(path); + } + } } diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index 1f6bd200e0..04819998d5 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -74,6 +74,13 @@ const nativeMessaging = { onMessage: (callback: (message: LegacyMessageWrapper | Message) => void) => { ipcRenderer.on("nativeMessaging", (_event, message) => callback(message)); }, + + manifests: { + generate: (create: boolean): Promise => + ipcRenderer.invoke("nativeMessaging.manifests", { create }), + generateDuckDuckGo: (create: boolean): Promise => + ipcRenderer.invoke("nativeMessaging.ddgManifests", { create }), + }, }; const crypto = { From 9277465951b160a98c27d55b386d4275d10d1595 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Thu, 18 Apr 2024 11:05:16 -0500 Subject: [PATCH 30/86] [PM-5744] Adjust Fido2 Content Script Injection to Meet mv3 Requirements (#8222) * [PM-5876] Adjust LP Fileless Importer to Suppress Download with DOM Append in Manifest v3 * [PM-5876] Incorporating jest tests for affected logic * [PM-5876] Fixing jest test that leverages rxjs * [PM-5876] Updating documentation within BrowserApi.executeScriptInTab * [PM-5876] Implementing jest tests for the new LP suppress download content scripts * [PM-5876] Adding a change to webpack to ensure we do not package the mv2 side script for `lp-suppress-import-download.mv2.ts` if building the extension for mv3 * [PM-5876] Implementing changes based on feedback during code review * [PM-5876] Implementing changes based on feedback during code review * [PM-5876] Implementing changes based on feedback during code review * [PM-5876] Implementing changes based on feedback during code review * [PM-5876] Implementing a configuration to feed the script injection of the Fileless Importer CSV download supression script * [PM-5744] Adjust injection of `page-script.ts` within FIDO 2 implementation to ensure mv3 compatibility * [PM-5744] Adjusting structure of manifest.json to clean up implementation and ensure consistency between mv2 and mv3 * [PM-5744] Reverting inclusion of the ConsoleLogService * [PM-4791] Injected content scripts prevent proper XML file display and disrupt XML responses * [PM-5744] Adjust FIDO2 content script injection methodology to be compatible with manifest v3 * [PM-5744] Adjusting references to Fido2Service to mirror change of name to Fido2Background * [PM-5744] Migrating runtime background messages that are associated with Fido2 into Fido2Background * [PM-5744] Fixing named reference within Fido2Background * [PM-5744] Migrating all Fido2 messages from the runtime.background.ts script to the fido2.background.ts script * [PM-5744] Removing unnecessary dependency from runtime background * [PM-5744] Removing unnecessary dependency from runtime background * [PM-5744] Reworking how we handle init of Fido2Background * [PM-5744] Reworking page-script.ts to ensure that it can destory its global values on unload * [PM-5744] Reworking page-script.ts to ensure that it can destory its global values on unload * [PM-5744] Implementing separated injection methodology between manifest v2 and v3 * [PM-4791] Adjsuting reference for Fido2 script injection to ensure it only triggers on https protocol types * [PM-5744] Removing unnecessary message and handling reload of content scripts based on updates on observable * [PM-5744] Refactoring content-script implementation for fido2 * [PM-5744] Refactoring content-script implementation for fido2 * [PM-5744] Reworking implementation to avoid having multiple contenType checks within the FIDO2 content scripts * [PM-5744] Re-implementing the messageWithResponse within runtime.background.ts * [PM-5744] Reverting change to autofill.service.ts * [PM-5744] Removing return value from runtime.background.ts process message call * [PM-5744] Reworking how we handle injection of the fido2 page and content script elements * [PM-5744] Adjusting how we override the navigator.credentials request/reponse structure * [PM-5744] Working through jest tests for the fido2Background implementation * [PM-5744] Finalizing jest tests for the Fido2Background implementation * [PM-5744] Stubbing out jest tests for content-script and page-script * [PM-5744] Implementing a methodology that allows us to dynamically set and unset content scripts * [PM-5744] Applying cleanup to page-script.ts to lighten the footprint of the script * [PM-5744] Further simplifying page-script implementation * [PM-5744] Reworking Fido2Utils to remove references to the base Utils methods to allow the page-script.ts file to render at a lower file size * [PM-5744] Reworking Fido2Utils to remove references to the base Utils methods to allow the page-script.ts file to render at a lower file size * [PM-5744] Implementing the `RegisterContentScriptPolyfill` as a separately compiled file as opposed to an import * [PM-5744] Implementing methodology to ensure that the RegisterContentScript polyfill is not built in cases where it is not needed * [PM-5744] Implementing methodology to ensure that the RegisterContentScript polyfill is not built in cases where it is not needed * [PM-5744] Reverting package-lock.json * [PM-5744] Implementing a methodology to ensure we can instantiate the RegisterContentScript polyfill in a siloed manner * [PM-5744] Migrating chrome extension api calls to the BrowserApi class * [PM-5744] Implementing typing information within the RegisterContentScriptsPolyfill * [PM-5744] Removing any eslint-disable references within the RegisterContentScriptsPolyfill * [PM-5744] Refactoring polyfill implementation * [PM-5744] Refactoring polyfill implementation * [PM-5744] Fixing an issue where Safari was not resolving the await chrome proxy * [PM-5744] Fixing jest tests for the page-script append method * [PM-5744] Fixing an issue found where collection of page details can trigger a context invalidated message when the extension is refreshed * [PM-5744] Implementing jest tests for the added BrowserApi methods * [PM-5744] Refactoring Fido2Background implementation * [PM-5744] Refactoring Fido2Background implementation * [PM-5744] Adding enums to the implementation for the Fido2 Content Scripts and working through jest tests for the BrowserApi and Fido2Background classes * [PM-5744] Adding comments to the FIDO2 content-script.ts file * [PM-5744] Adding jest tests for the Fido2 content-script.ts * [PM-5744] Adding jest tests for the Fido2 content-script.ts * [PM-5744] Adding jest tests for the Fido2 page-script.ts * [PM-5744] Working through an attempt to jest test the page-script.ts file * [PM-5744] Finalizing jest tests for the page-script.ts implementation * [PM-5744] Applying stricter type information for the passed params within fido2-testing-utils.ts * [PM-5744] Adjusting documentation * [PM-5744] Adjusting implementation of jest tests to use mock proxies * [PM-5744] Adjusting jest tests to simply implementation * [PM-5744] Adjust jest tests based on code review feedback * [PM-5744] Adjust jest tests based on code review feedback * [PM-5744] Adjust jest tests based on code review feedback * [PM-5744] Adjusting jest tests to based on feedback * [PM-5744] Adjusting jest tests to based on feedback * [PM-5744] Adjusting jest tests to based on feedback * [PM-5744] Adjusting conditional within page-script.ts * [PM-5744] Removing unnecessary global reference to the messager * [PM-5744] Updating jest tests * [PM-5744] Updating jest tests * [PM-5744] Updating jest tests * [PM-5744] Updating jest tests * [PM-5744] Updating how we export the Fido2Background class * [PM-5744] Adding duplciate jest tests to fido2-utils.ts to ensure we maintain functionality for utils methods pulled from platform utils * [PM-5189] Addressing code review feedback * [PM-5744] Applying code review feedback, reworking obserable subscription within fido2 background * [PM-5744] Reworking jest tests to avoid mocking `firstValueFrom` * [PM-5744] Reworking jest tests to avoid usage of private methods * [PM-5744] Reworking jest tests to avoid usage of private methods * [PM-5744] Implementing jest tests for the ScriptInjectorService and updating references within the Browser Extension to use the new service * [PM-5744] Converting ScriptInjectorService to a dependnecy instead of a static class * [PM-5744] Reworking typing for the ScriptInjectorService * [PM-5744] Adjusting implementation based on feedback provided during code review * [PM-5744] Adjusting implementation based on feedback provided during code review * [PM-5744] Adjusting implementation based on feedback provided during code review * [PM-5744] Adjusting implementation based on feedback provided during code review * [PM-5744] Adjusting how the ScriptInjectorService accepts the config to simplify the data structure * [PM-5744] Updating jest tests to cover edge cases within ScriptInjectorService * [PM-5744] Updating jest tests to reference the ScriptInjectorService directly rather than the underlying ExecuteScript api call * [PM-5744] Updating jest tests to reflect provided feedback during code review * [PM-5744] Updating jest tests to reflect provided feedback during code review * [PM-5744] Updating documentation based on code review feedback * [PM-5744] Updating how we extend the abstract ScriptInjectorService * [PM-5744] Updating reference to the frame property on the ScriptInjectionConfig --- .../autofill-service.factory.ts | 8 +- .../autofill/content/autofill-init.spec.ts | 11 + .../src/autofill/content/autofill-init.ts | 21 +- .../services/autofill.service.spec.ts | 5 + .../src/autofill/services/autofill.service.ts | 19 +- .../src/autofill/spec/autofill-mocks.ts | 1 + .../src/autofill/spec/fido2-testing-utils.ts | 74 +++ .../browser/src/background/main.background.ts | 24 +- .../src/background/runtime.background.ts | 65 +-- apps/browser/src/manifest.json | 10 +- apps/browser/src/manifest.v3.json | 7 - ...browser-script-injector-service.factory.ts | 19 + ...r-api.register-content-scripts-polyfill.ts | 435 ++++++++++++++++++ .../src/platform/browser/browser-api.spec.ts | 31 ++ .../src/platform/browser/browser-api.ts | 39 ++ .../abstractions/script-injector.service.ts | 45 ++ .../browser-script-injector.service.spec.ts | 173 +++++++ .../browser-script-injector.service.ts | 78 ++++ .../src/popup/services/services.module.ts | 8 + .../fileless-importer.background.ts | 6 - .../fileless-importer.background.spec.ts | 19 +- .../fileless-importer.background.ts | 33 +- .../fileless-importer-injected-scripts.ts | 11 +- .../abstractions/fido2.background.ts | 57 +++ .../fido2/background/fido2.background.spec.ts | 414 +++++++++++++++++ .../fido2/background/fido2.background.ts | 356 ++++++++++++++ .../fido2/content/content-script.spec.ts | 164 +++++++ .../src/vault/fido2/content/content-script.ts | 234 +++++----- .../fido2/content/messaging/messenger.spec.ts | 2 +- .../fido2/content/messaging/messenger.ts | 21 +- .../content/page-script-append.mv2.spec.ts | 69 +++ .../fido2/content/page-script-append.mv2.ts | 19 + .../src/vault/fido2/content/page-script.ts | 385 ++++++++-------- .../page-script.webauthn-supported.spec.ts | 121 +++++ .../page-script.webauthn-unsupported.spec.ts | 96 ++++ ...ger-fido2-content-script-injection.spec.ts | 16 - .../trigger-fido2-content-script-injection.ts | 5 - .../fido2/enums/fido2-content-script.enum.ts | 10 + .../vault/fido2/enums/fido2-port-name.enum.ts | 3 + .../services/abstractions/fido2.service.ts | 4 - .../src/vault/services/fido2.service.spec.ts | 35 -- .../src/vault/services/fido2.service.ts | 35 -- apps/browser/test.setup.ts | 17 + apps/browser/tsconfig.json | 1 + apps/browser/webpack.config.js | 4 +- .../services/fido2/fido2-client.service.ts | 17 +- .../vault/services/fido2/fido2-utils.spec.ts | 40 ++ .../src/vault/services/fido2/fido2-utils.ts | 64 ++- 48 files changed, 2767 insertions(+), 564 deletions(-) create mode 100644 apps/browser/src/autofill/spec/fido2-testing-utils.ts create mode 100644 apps/browser/src/platform/background/service-factories/browser-script-injector-service.factory.ts create mode 100644 apps/browser/src/platform/browser/browser-api.register-content-scripts-polyfill.ts create mode 100644 apps/browser/src/platform/services/abstractions/script-injector.service.ts create mode 100644 apps/browser/src/platform/services/browser-script-injector.service.spec.ts create mode 100644 apps/browser/src/platform/services/browser-script-injector.service.ts create mode 100644 apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts create mode 100644 apps/browser/src/vault/fido2/background/fido2.background.spec.ts create mode 100644 apps/browser/src/vault/fido2/background/fido2.background.ts create mode 100644 apps/browser/src/vault/fido2/content/content-script.spec.ts create mode 100644 apps/browser/src/vault/fido2/content/page-script-append.mv2.spec.ts create mode 100644 apps/browser/src/vault/fido2/content/page-script-append.mv2.ts create mode 100644 apps/browser/src/vault/fido2/content/page-script.webauthn-supported.spec.ts create mode 100644 apps/browser/src/vault/fido2/content/page-script.webauthn-unsupported.spec.ts delete mode 100644 apps/browser/src/vault/fido2/content/trigger-fido2-content-script-injection.spec.ts delete mode 100644 apps/browser/src/vault/fido2/content/trigger-fido2-content-script-injection.ts create mode 100644 apps/browser/src/vault/fido2/enums/fido2-content-script.enum.ts create mode 100644 apps/browser/src/vault/fido2/enums/fido2-port-name.enum.ts delete mode 100644 apps/browser/src/vault/services/abstractions/fido2.service.ts delete mode 100644 apps/browser/src/vault/services/fido2.service.spec.ts delete mode 100644 apps/browser/src/vault/services/fido2.service.ts create mode 100644 libs/common/src/vault/services/fido2/fido2-utils.spec.ts diff --git a/apps/browser/src/autofill/background/service_factories/autofill-service.factory.ts b/apps/browser/src/autofill/background/service_factories/autofill-service.factory.ts index c948f7aa94..7b423ca4f4 100644 --- a/apps/browser/src/autofill/background/service_factories/autofill-service.factory.ts +++ b/apps/browser/src/autofill/background/service_factories/autofill-service.factory.ts @@ -7,6 +7,10 @@ import { eventCollectionServiceFactory, } from "../../../background/service-factories/event-collection-service.factory"; import { billingAccountProfileStateServiceFactory } from "../../../platform/background/service-factories/billing-account-profile-state-service.factory"; +import { + browserScriptInjectorServiceFactory, + BrowserScriptInjectorServiceInitOptions, +} from "../../../platform/background/service-factories/browser-script-injector-service.factory"; import { CachedServices, factory, @@ -45,7 +49,8 @@ export type AutoFillServiceInitOptions = AutoFillServiceOptions & EventCollectionServiceInitOptions & LogServiceInitOptions & UserVerificationServiceInitOptions & - DomainSettingsServiceInitOptions; + DomainSettingsServiceInitOptions & + BrowserScriptInjectorServiceInitOptions; export function autofillServiceFactory( cache: { autofillService?: AbstractAutoFillService } & CachedServices, @@ -65,6 +70,7 @@ export function autofillServiceFactory( await domainSettingsServiceFactory(cache, opts), await userVerificationServiceFactory(cache, opts), await billingAccountProfileStateServiceFactory(cache, opts), + await browserScriptInjectorServiceFactory(cache, opts), ), ); } diff --git a/apps/browser/src/autofill/content/autofill-init.spec.ts b/apps/browser/src/autofill/content/autofill-init.spec.ts index b299ddccbf..8f1a8bf992 100644 --- a/apps/browser/src/autofill/content/autofill-init.spec.ts +++ b/apps/browser/src/autofill/content/autofill-init.spec.ts @@ -560,6 +560,17 @@ describe("AutofillInit", () => { }); describe("destroy", () => { + it("clears the timeout used to collect page details on load", () => { + jest.spyOn(window, "clearTimeout"); + + autofillInit.init(); + autofillInit.destroy(); + + expect(window.clearTimeout).toHaveBeenCalledWith( + autofillInit["collectPageDetailsOnLoadTimeout"], + ); + }); + it("removes the extension message listeners", () => { autofillInit.destroy(); diff --git a/apps/browser/src/autofill/content/autofill-init.ts b/apps/browser/src/autofill/content/autofill-init.ts index 2de35dee20..e78a1fb5ee 100644 --- a/apps/browser/src/autofill/content/autofill-init.ts +++ b/apps/browser/src/autofill/content/autofill-init.ts @@ -16,6 +16,7 @@ class AutofillInit implements AutofillInitInterface { private readonly domElementVisibilityService: DomElementVisibilityService; private readonly collectAutofillContentService: CollectAutofillContentService; private readonly insertAutofillContentService: InsertAutofillContentService; + private collectPageDetailsOnLoadTimeout: number | NodeJS.Timeout | undefined; private readonly extensionMessageHandlers: AutofillExtensionMessageHandlers = { collectPageDetails: ({ message }) => this.collectPageDetails(message), collectPageDetailsImmediately: ({ message }) => this.collectPageDetails(message, true), @@ -66,17 +67,19 @@ class AutofillInit implements AutofillInitInterface { * to act on the page. */ private collectPageDetailsOnLoad() { - const sendCollectDetailsMessage = () => - setTimeout( + const sendCollectDetailsMessage = () => { + this.clearCollectPageDetailsOnLoadTimeout(); + this.collectPageDetailsOnLoadTimeout = setTimeout( () => sendExtensionMessage("bgCollectPageDetails", { sender: "autofillInit" }), 250, ); + }; - if (document.readyState === "complete") { + if (globalThis.document.readyState === "complete") { sendCollectDetailsMessage(); } - window.addEventListener("load", sendCollectDetailsMessage); + globalThis.addEventListener("load", sendCollectDetailsMessage); } /** @@ -247,6 +250,15 @@ class AutofillInit implements AutofillInitInterface { this.autofillOverlayContentService.autofillOverlayVisibility = data?.autofillOverlayVisibility; } + /** + * Clears the send collect details message timeout. + */ + private clearCollectPageDetailsOnLoadTimeout() { + if (this.collectPageDetailsOnLoadTimeout) { + clearTimeout(this.collectPageDetailsOnLoadTimeout); + } + } + /** * Sets up the extension message listeners for the content script. */ @@ -288,6 +300,7 @@ class AutofillInit implements AutofillInitInterface { * listeners, timeouts, and object instances to prevent memory leaks. */ destroy() { + this.clearCollectPageDetailsOnLoadTimeout(); chrome.runtime.onMessage.removeListener(this.handleExtensionMessage); this.collectAutofillContentService.destroy(); this.autofillOverlayContentService?.destroy(); diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 4db64f417d..5064d2e7df 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -32,6 +32,7 @@ import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { BrowserApi } from "../../platform/browser/browser-api"; +import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service"; import { AutofillPort } from "../enums/autofill-port.enums"; import AutofillField from "../models/autofill-field"; import AutofillPageDetails from "../models/autofill-page-details"; @@ -67,6 +68,7 @@ describe("AutofillService", () => { const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService); let domainSettingsService: DomainSettingsService; + let scriptInjectorService: BrowserScriptInjectorService; const totpService = mock(); const eventCollectionService = mock(); const logService = mock(); @@ -74,6 +76,7 @@ describe("AutofillService", () => { const billingAccountProfileStateService = mock(); beforeEach(() => { + scriptInjectorService = new BrowserScriptInjectorService(); autofillService = new AutofillService( cipherService, autofillSettingsService, @@ -83,6 +86,7 @@ describe("AutofillService", () => { domainSettingsService, userVerificationService, billingAccountProfileStateService, + scriptInjectorService, ); domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); @@ -250,6 +254,7 @@ describe("AutofillService", () => { expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabMock.id, { file: "content/content-message-handler.js", + frameId: 0, ...defaultExecuteScriptOptions, }); }); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 8b33d03419..6cf58558dc 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -20,6 +20,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; import { BrowserApi } from "../../platform/browser/browser-api"; +import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; import { openVaultItemPasswordRepromptPopout } from "../../vault/popup/utils/vault-popout-window"; import { AutofillPort } from "../enums/autofill-port.enums"; import AutofillField from "../models/autofill-field"; @@ -55,6 +56,7 @@ export default class AutofillService implements AutofillServiceInterface { private domainSettingsService: DomainSettingsService, private userVerificationService: UserVerificationService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private scriptInjectorService: ScriptInjectorService, ) {} /** @@ -113,19 +115,22 @@ export default class AutofillService implements AutofillServiceInterface { if (triggeringOnPageLoad && autoFillOnPageLoadIsEnabled) { injectedScripts.push("autofiller.js"); } else { - await BrowserApi.executeScriptInTab(tab.id, { - file: "content/content-message-handler.js", - runAt: "document_start", + await this.scriptInjectorService.inject({ + tabId: tab.id, + injectDetails: { file: "content/content-message-handler.js", runAt: "document_start" }, }); } injectedScripts.push("notificationBar.js", "contextMenuHandler.js"); for (const injectedScript of injectedScripts) { - await BrowserApi.executeScriptInTab(tab.id, { - file: `content/${injectedScript}`, - frameId, - runAt: "document_start", + await this.scriptInjectorService.inject({ + tabId: tab.id, + injectDetails: { + file: `content/${injectedScript}`, + runAt: "document_start", + frame: frameId, + }, }); } } diff --git a/apps/browser/src/autofill/spec/autofill-mocks.ts b/apps/browser/src/autofill/spec/autofill-mocks.ts index 708489c57e..9c957f6b1b 100644 --- a/apps/browser/src/autofill/spec/autofill-mocks.ts +++ b/apps/browser/src/autofill/spec/autofill-mocks.ts @@ -267,6 +267,7 @@ function createPortSpyMock(name: string) { disconnect: jest.fn(), sender: { tab: createChromeTabMock(), + url: "https://jest-testing-website.com", }, }); } diff --git a/apps/browser/src/autofill/spec/fido2-testing-utils.ts b/apps/browser/src/autofill/spec/fido2-testing-utils.ts new file mode 100644 index 0000000000..c9b39c16cc --- /dev/null +++ b/apps/browser/src/autofill/spec/fido2-testing-utils.ts @@ -0,0 +1,74 @@ +import { mock } from "jest-mock-extended"; + +import { + AssertCredentialResult, + CreateCredentialResult, +} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; + +export function createCredentialCreationOptionsMock( + customFields: Partial = {}, +): CredentialCreationOptions { + return mock({ + publicKey: mock({ + authenticatorSelection: { authenticatorAttachment: "platform" }, + excludeCredentials: [{ id: new ArrayBuffer(32), type: "public-key" }], + pubKeyCredParams: [{ alg: -7, type: "public-key" }], + user: { id: new ArrayBuffer(32), name: "test", displayName: "test" }, + }), + ...customFields, + }); +} + +export function createCreateCredentialResultMock( + customFields: Partial = {}, +): CreateCredentialResult { + return mock({ + credentialId: "mock", + clientDataJSON: "mock", + attestationObject: "mock", + authData: "mock", + publicKey: "mock", + publicKeyAlgorithm: -7, + transports: ["internal"], + ...customFields, + }); +} + +export function createCredentialRequestOptionsMock( + customFields: Partial = {}, +): CredentialRequestOptions { + return mock({ + mediation: "optional", + publicKey: mock({ + allowCredentials: [{ id: new ArrayBuffer(32), type: "public-key" }], + }), + ...customFields, + }); +} + +export function createAssertCredentialResultMock( + customFields: Partial = {}, +): AssertCredentialResult { + return mock({ + credentialId: "mock", + clientDataJSON: "mock", + authenticatorData: "mock", + signature: "mock", + userHandle: "mock", + ...customFields, + }); +} + +export function setupMockedWebAuthnSupport() { + (globalThis as any).PublicKeyCredential = class PolyfillPublicKeyCredential { + static isUserVerifyingPlatformAuthenticatorAvailable = () => Promise.resolve(true); + }; + (globalThis as any).AuthenticatorAttestationResponse = + class PolyfillAuthenticatorAttestationResponse {}; + (globalThis as any).AuthenticatorAssertionResponse = + class PolyfillAuthenticatorAssertionResponse {}; + (globalThis as any).navigator.credentials = { + create: jest.fn().mockResolvedValue({}), + get: jest.fn().mockResolvedValue({}), + }; +} diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 69ed4cfa3d..325a7f1943 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -214,6 +214,7 @@ import BrowserLocalStorageService from "../platform/services/browser-local-stora import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service"; import BrowserMessagingPrivateModeBackgroundService from "../platform/services/browser-messaging-private-mode-background.service"; import BrowserMessagingService from "../platform/services/browser-messaging.service"; +import { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service"; import { DefaultBrowserStateService } from "../platform/services/default-browser-state.service"; import I18nService from "../platform/services/i18n.service"; import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service"; @@ -223,9 +224,9 @@ import { BackgroundDerivedStateProvider } from "../platform/state/background-der import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service"; import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; import FilelessImporterBackground from "../tools/background/fileless-importer.background"; +import { Fido2Background as Fido2BackgroundAbstraction } from "../vault/fido2/background/abstractions/fido2.background"; +import { Fido2Background } from "../vault/fido2/background/fido2.background"; import { BrowserFido2UserInterfaceService } from "../vault/fido2/browser-fido2-user-interface.service"; -import { Fido2Service as Fido2ServiceAbstraction } from "../vault/services/abstractions/fido2.service"; -import Fido2Service from "../vault/services/fido2.service"; import { VaultFilterService } from "../vault/services/vault-filter.service"; import CommandsBackground from "./commands.background"; @@ -316,7 +317,7 @@ export default class MainBackground { activeUserStateProvider: ActiveUserStateProvider; derivedStateProvider: DerivedStateProvider; stateProvider: StateProvider; - fido2Service: Fido2ServiceAbstraction; + fido2Background: Fido2BackgroundAbstraction; individualVaultExportService: IndividualVaultExportServiceAbstraction; organizationVaultExportService: OrganizationVaultExportServiceAbstraction; vaultSettingsService: VaultSettingsServiceAbstraction; @@ -324,6 +325,7 @@ export default class MainBackground { stateEventRunnerService: StateEventRunnerService; ssoLoginService: SsoLoginServiceAbstraction; billingAccountProfileStateService: BillingAccountProfileStateService; + scriptInjectorService: BrowserScriptInjectorService; onUpdatedRan: boolean; onReplacedRan: boolean; @@ -791,6 +793,7 @@ export default class MainBackground { ); this.totpService = new TotpService(this.cryptoFunctionService, this.logService); + this.scriptInjectorService = new BrowserScriptInjectorService(); this.autofillService = new AutofillService( this.cipherService, this.autofillSettingsService, @@ -800,6 +803,7 @@ export default class MainBackground { this.domainSettingsService, this.userVerificationService, this.billingAccountProfileStateService, + this.scriptInjectorService, ); this.auditService = new AuditService(this.cryptoFunctionService, this.apiService); @@ -849,7 +853,6 @@ export default class MainBackground { this.messagingService, ); - this.fido2Service = new Fido2Service(); this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.authService); this.fido2AuthenticatorService = new Fido2AuthenticatorService( this.cipherService, @@ -890,11 +893,16 @@ export default class MainBackground { // Background if (!this.popupOnlyContext) { + this.fido2Background = new Fido2Background( + this.logService, + this.fido2ClientService, + this.vaultSettingsService, + this.scriptInjectorService, + ); this.runtimeBackground = new RuntimeBackground( this, this.autofillService, this.platformUtilsService as BrowserPlatformUtilsService, - this.i18nService, this.notificationsService, this.stateService, this.autofillSettingsService, @@ -903,7 +911,7 @@ export default class MainBackground { this.messagingService, this.logService, this.configService, - this.fido2Service, + this.fido2Background, ); this.nativeMessagingBackground = new NativeMessagingBackground( this.accountService, @@ -959,6 +967,7 @@ export default class MainBackground { this.notificationBackground, this.importService, this.syncService, + this.scriptInjectorService, ); this.tabsBackground = new TabsBackground( this, @@ -1045,11 +1054,12 @@ export default class MainBackground { await this.stateService.init({ runMigrations: !this.isPrivateMode }); await (this.i18nService as I18nService).init(); - (this.eventUploadService as EventUploadService).init(true); + await (this.eventUploadService as EventUploadService).init(true); this.twoFactorService.init(); if (!this.popupOnlyContext) { await this.vaultTimeoutService.init(true); + this.fido2Background.init(); await this.runtimeBackground.init(); await this.notificationBackground.init(); this.filelessImporterBackground.init(); diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index a88bc051d8..44fe4818e0 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -4,7 +4,6 @@ import { NotificationsService } from "@bitwarden/common/abstractions/notificatio import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { SystemService } from "@bitwarden/common/platform/abstractions/system.service"; @@ -22,8 +21,7 @@ import { BrowserApi } from "../platform/browser/browser-api"; import { BrowserStateService } from "../platform/services/abstractions/browser-state.service"; import { BrowserEnvironmentService } from "../platform/services/browser-environment.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; -import { AbortManager } from "../vault/background/abort-manager"; -import { Fido2Service } from "../vault/services/abstractions/fido2.service"; +import { Fido2Background } from "../vault/fido2/background/abstractions/fido2.background"; import MainBackground from "./main.background"; @@ -32,13 +30,11 @@ export default class RuntimeBackground { private pageDetailsToAutoFill: any[] = []; private onInstalledReason: string = null; private lockedVaultPendingNotifications: LockedVaultPendingNotificationsData[] = []; - private abortManager = new AbortManager(); constructor( private main: MainBackground, private autofillService: AutofillService, private platformUtilsService: BrowserPlatformUtilsService, - private i18nService: I18nService, private notificationsService: NotificationsService, private stateService: BrowserStateService, private autofillSettingsService: AutofillSettingsServiceAbstraction, @@ -47,7 +43,7 @@ export default class RuntimeBackground { private messagingService: MessagingService, private logService: LogService, private configService: ConfigService, - private fido2Service: Fido2Service, + private fido2Background: Fido2Background, ) { // onInstalled listener must be wired up before anything else, so we do it in the ctor chrome.runtime.onInstalled.addListener((details: any) => { @@ -66,12 +62,7 @@ export default class RuntimeBackground { sender: chrome.runtime.MessageSender, sendResponse: any, ) => { - const messagesWithResponse = [ - "checkFido2FeatureEnabled", - "fido2RegisterCredentialRequest", - "fido2GetCredentialRequest", - "biometricUnlock", - ]; + const messagesWithResponse = ["biometricUnlock"]; if (messagesWithResponse.includes(msg.command)) { this.processMessage(msg, sender).then( @@ -81,10 +72,7 @@ export default class RuntimeBackground { return true; } - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.processMessage(msg, sender); - return false; + this.processMessage(msg, sender).catch((e) => this.logService.error(e)); }; BrowserApi.messageListener("runtime.background", backgroundMessageListener); @@ -269,46 +257,6 @@ export default class RuntimeBackground { case "getClickedElementResponse": this.platformUtilsService.copyToClipboard(msg.identifier); break; - case "triggerFido2ContentScriptInjection": - await this.fido2Service.injectFido2ContentScripts(sender); - break; - case "fido2AbortRequest": - this.abortManager.abort(msg.abortedRequestId); - break; - case "checkFido2FeatureEnabled": - return await this.main.fido2ClientService.isFido2FeatureEnabled(msg.hostname, msg.origin); - case "fido2RegisterCredentialRequest": - return await this.abortManager.runWithAbortController( - msg.requestId, - async (abortController) => { - try { - return await this.main.fido2ClientService.createCredential( - msg.data, - sender.tab, - abortController, - ); - } finally { - await BrowserApi.focusTab(sender.tab.id); - await BrowserApi.focusWindow(sender.tab.windowId); - } - }, - ); - case "fido2GetCredentialRequest": - return await this.abortManager.runWithAbortController( - msg.requestId, - async (abortController) => { - try { - return await this.main.fido2ClientService.assertCredential( - msg.data, - sender.tab, - abortController, - ); - } finally { - await BrowserApi.focusTab(sender.tab.id); - await BrowserApi.focusWindow(sender.tab.windowId); - } - }, - ); case "switchAccount": { await this.main.switchAccount(msg.userId); break; @@ -343,9 +291,8 @@ export default class RuntimeBackground { private async checkOnInstalled() { setTimeout(async () => { - // 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.autofillService.loadAutofillScriptsOnInstall(); + void this.fido2Background.injectFido2ContentScriptsInAllTabs(); + void this.autofillService.loadAutofillScriptsOnInstall(); if (this.onInstalledReason != null) { if (this.onInstalledReason === "install") { diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index aec7523d5e..78f1e2cc41 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -22,13 +22,6 @@ "exclude_matches": ["*://*/*.xml*", "file:///*.xml*"], "run_at": "document_start" }, - { - "all_frames": true, - "js": ["content/fido2/trigger-fido2-content-script-injection.js"], - "matches": ["https://*/*"], - "exclude_matches": ["https://*/*.xml*"], - "run_at": "document_start" - }, { "all_frames": true, "css": ["content/autofill.css"], @@ -67,7 +60,8 @@ "clipboardWrite", "idle", "webRequest", - "webRequestBlocking" + "webRequestBlocking", + "webNavigation" ], "optional_permissions": ["nativeMessaging", "privacy"], "content_security_policy": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index d67b4affab..6c58b405f4 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -23,13 +23,6 @@ "exclude_matches": ["*://*/*.xml*", "file:///*.xml*"], "run_at": "document_start" }, - { - "all_frames": true, - "js": ["content/fido2/trigger-fido2-content-script-injection.js"], - "matches": ["https://*/*"], - "exclude_matches": ["https://*/*.xml*"], - "run_at": "document_start" - }, { "all_frames": true, "css": ["content/autofill.css"], diff --git a/apps/browser/src/platform/background/service-factories/browser-script-injector-service.factory.ts b/apps/browser/src/platform/background/service-factories/browser-script-injector-service.factory.ts new file mode 100644 index 0000000000..e3bc687f28 --- /dev/null +++ b/apps/browser/src/platform/background/service-factories/browser-script-injector-service.factory.ts @@ -0,0 +1,19 @@ +import { BrowserScriptInjectorService } from "../../services/browser-script-injector.service"; + +import { CachedServices, FactoryOptions, factory } from "./factory-options"; + +type BrowserScriptInjectorServiceOptions = FactoryOptions; + +export type BrowserScriptInjectorServiceInitOptions = BrowserScriptInjectorServiceOptions; + +export function browserScriptInjectorServiceFactory( + cache: { browserScriptInjectorService?: BrowserScriptInjectorService } & CachedServices, + opts: BrowserScriptInjectorServiceInitOptions, +): Promise { + return factory( + cache, + "browserScriptInjectorService", + opts, + async () => new BrowserScriptInjectorService(), + ); +} diff --git a/apps/browser/src/platform/browser/browser-api.register-content-scripts-polyfill.ts b/apps/browser/src/platform/browser/browser-api.register-content-scripts-polyfill.ts new file mode 100644 index 0000000000..8a20f3e999 --- /dev/null +++ b/apps/browser/src/platform/browser/browser-api.register-content-scripts-polyfill.ts @@ -0,0 +1,435 @@ +/** + * MIT License + * + * Copyright (c) Federico Brigante (https://fregante.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @see https://github.com/fregante/content-scripts-register-polyfill + * @version 4.0.2 + */ +import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; + +import { BrowserApi } from "./browser-api"; + +let registerContentScripts: ( + contentScriptOptions: browser.contentScripts.RegisteredContentScriptOptions, + callback?: (registeredContentScript: browser.contentScripts.RegisteredContentScript) => void, +) => Promise; +export async function registerContentScriptsPolyfill( + contentScriptOptions: browser.contentScripts.RegisteredContentScriptOptions, + callback?: (registeredContentScript: browser.contentScripts.RegisteredContentScript) => void, +) { + if (!registerContentScripts) { + registerContentScripts = buildRegisterContentScriptsPolyfill(); + } + + return registerContentScripts(contentScriptOptions, callback); +} + +function buildRegisterContentScriptsPolyfill() { + const logService = new ConsoleLogService(false); + const chromeProxy = globalThis.chrome && NestedProxy(globalThis.chrome); + const patternValidationRegex = + /^(https?|wss?|file|ftp|\*):\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^file:\/\/\/.*$|^resource:\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^about:/; + const isFirefox = globalThis.navigator?.userAgent.includes("Firefox/"); + const gotScripting = Boolean(globalThis.chrome?.scripting); + const gotNavigation = typeof chrome === "object" && "webNavigation" in chrome; + + function NestedProxy(target: T): T { + return new Proxy(target, { + get(target, prop) { + if (!target[prop as keyof T]) { + return; + } + + if (typeof target[prop as keyof T] !== "function") { + return NestedProxy(target[prop as keyof T]); + } + + return (...arguments_: any[]) => + new Promise((resolve, reject) => { + target[prop as keyof T](...arguments_, (result: any) => { + if (chrome.runtime.lastError) { + reject(new Error(chrome.runtime.lastError.message)); + } else { + resolve(result); + } + }); + }); + }, + }); + } + + function assertValidPattern(matchPattern: string) { + if (!isValidPattern(matchPattern)) { + throw new Error( + `${matchPattern} is an invalid pattern, it must match ${String(patternValidationRegex)}`, + ); + } + } + + function isValidPattern(matchPattern: string) { + return matchPattern === "" || patternValidationRegex.test(matchPattern); + } + + function getRawPatternRegex(matchPattern: string) { + assertValidPattern(matchPattern); + let [, protocol, host = "", pathname] = matchPattern.split(/(^[^:]+:[/][/])([^/]+)?/); + protocol = protocol + .replace("*", isFirefox ? "(https?|wss?)" : "https?") + .replaceAll(/[/]/g, "[/]"); + + if (host === "*") { + host = "[^/]+"; + } else if (host) { + host = host + .replace(/^[*][.]/, "([^/]+.)*") + .replaceAll(/[.]/g, "[.]") + .replace(/[*]$/, "[^.]+"); + } + + pathname = pathname + .replaceAll(/[/]/g, "[/]") + .replaceAll(/[.]/g, "[.]") + .replaceAll(/[*]/g, ".*"); + + return "^" + protocol + host + "(" + pathname + ")?$"; + } + + function patternToRegex(...matchPatterns: string[]) { + if (matchPatterns.length === 0) { + return /$./; + } + + if (matchPatterns.includes("")) { + // regex + return /^(https?|file|ftp):[/]+/; + } + + if (matchPatterns.includes("*://*/*")) { + // all stars regex + return isFirefox ? /^(https?|wss?):[/][/][^/]+([/].*)?$/ : /^https?:[/][/][^/]+([/].*)?$/; + } + + return new RegExp(matchPatterns.map((x) => getRawPatternRegex(x)).join("|")); + } + + function castAllFramesTarget(target: number | { tabId: number; frameId: number }) { + if (typeof target === "object") { + return { ...target, allFrames: false }; + } + + return { + tabId: target, + frameId: undefined, + allFrames: true, + }; + } + + function castArray(possibleArray: any | any[]) { + if (Array.isArray(possibleArray)) { + return possibleArray; + } + + return [possibleArray]; + } + + function arrayOrUndefined(value?: number) { + return value === undefined ? undefined : [value]; + } + + async function insertCSS( + { + tabId, + frameId, + files, + allFrames, + matchAboutBlank, + runAt, + }: { + tabId: number; + frameId?: number; + files: browser.extensionTypes.ExtensionFileOrCode[]; + allFrames: boolean; + matchAboutBlank: boolean; + runAt: browser.extensionTypes.RunAt; + }, + { ignoreTargetErrors }: { ignoreTargetErrors?: boolean } = {}, + ) { + const everyInsertion = Promise.all( + files.map(async (content) => { + if (typeof content === "string") { + content = { file: content }; + } + + if (gotScripting) { + return chrome.scripting.insertCSS({ + target: { + tabId, + frameIds: arrayOrUndefined(frameId), + allFrames: frameId === undefined ? allFrames : undefined, + }, + files: "file" in content ? [content.file] : undefined, + css: "code" in content ? content.code : undefined, + }); + } + + return chromeProxy.tabs.insertCSS(tabId, { + ...content, + matchAboutBlank, + allFrames, + frameId, + runAt: runAt ?? "document_start", + }); + }), + ); + + if (ignoreTargetErrors) { + await catchTargetInjectionErrors(everyInsertion); + } else { + await everyInsertion; + } + } + function assertNoCode(files: browser.extensionTypes.ExtensionFileOrCode[]) { + if (files.some((content) => "code" in content)) { + throw new Error("chrome.scripting does not support injecting strings of `code`"); + } + } + + async function executeScript( + { + tabId, + frameId, + files, + allFrames, + matchAboutBlank, + runAt, + }: { + tabId: number; + frameId?: number; + files: browser.extensionTypes.ExtensionFileOrCode[]; + allFrames: boolean; + matchAboutBlank: boolean; + runAt: browser.extensionTypes.RunAt; + }, + { ignoreTargetErrors }: { ignoreTargetErrors?: boolean } = {}, + ) { + const normalizedFiles = files.map((file) => (typeof file === "string" ? { file } : file)); + + if (gotScripting) { + assertNoCode(normalizedFiles); + const injection = chrome.scripting.executeScript({ + target: { + tabId, + frameIds: arrayOrUndefined(frameId), + allFrames: frameId === undefined ? allFrames : undefined, + }, + files: normalizedFiles.map(({ file }: { file: string }) => file), + }); + + if (ignoreTargetErrors) { + await catchTargetInjectionErrors(injection); + } else { + await injection; + } + + return; + } + + const executions = []; + for (const content of normalizedFiles) { + if ("code" in content) { + await executions.at(-1); + } + + executions.push( + chromeProxy.tabs.executeScript(tabId, { + ...content, + matchAboutBlank, + allFrames, + frameId, + runAt, + }), + ); + } + + if (ignoreTargetErrors) { + await catchTargetInjectionErrors(Promise.all(executions)); + } else { + await Promise.all(executions); + } + } + + async function injectContentScript( + where: { tabId: number; frameId: number }, + scripts: { + css: browser.extensionTypes.ExtensionFileOrCode[]; + js: browser.extensionTypes.ExtensionFileOrCode[]; + matchAboutBlank: boolean; + runAt: browser.extensionTypes.RunAt; + }, + options = {}, + ) { + const targets = castArray(where); + await Promise.all( + targets.map(async (target) => + injectContentScriptInSpecificTarget(castAllFramesTarget(target), scripts, options), + ), + ); + } + + async function injectContentScriptInSpecificTarget( + { frameId, tabId, allFrames }: { frameId?: number; tabId: number; allFrames: boolean }, + scripts: { + css: browser.extensionTypes.ExtensionFileOrCode[]; + js: browser.extensionTypes.ExtensionFileOrCode[]; + matchAboutBlank: boolean; + runAt: browser.extensionTypes.RunAt; + }, + options = {}, + ) { + const injections = castArray(scripts).flatMap((script) => [ + insertCSS( + { + tabId, + frameId, + allFrames, + files: script.css ?? [], + matchAboutBlank: script.matchAboutBlank ?? script.match_about_blank, + runAt: script.runAt ?? script.run_at, + }, + options, + ), + executeScript( + { + tabId, + frameId, + allFrames, + files: script.js ?? [], + matchAboutBlank: script.matchAboutBlank ?? script.match_about_blank, + runAt: script.runAt ?? script.run_at, + }, + options, + ), + ]); + await Promise.all(injections); + } + + async function catchTargetInjectionErrors(promise: Promise) { + try { + await promise; + } catch (error) { + const targetErrors = + /^No frame with id \d+ in tab \d+.$|^No tab with id: \d+.$|^The tab was closed.$|^The frame was removed.$/; + if (!targetErrors.test(error?.message)) { + throw error; + } + } + } + + async function isOriginPermitted(url: string) { + return chromeProxy.permissions.contains({ + origins: [new URL(url).origin + "/*"], + }); + } + + return async ( + contentScriptOptions: browser.contentScripts.RegisteredContentScriptOptions, + callback: CallableFunction, + ) => { + const { + js = [], + css = [], + matchAboutBlank, + matches = [], + excludeMatches, + runAt, + } = contentScriptOptions; + let { allFrames } = contentScriptOptions; + + if (gotNavigation) { + allFrames = false; + } else if (allFrames) { + logService.warning( + "`allFrames: true` requires the `webNavigation` permission to work correctly: https://github.com/fregante/content-scripts-register-polyfill#permissions", + ); + } + + if (matches.length === 0) { + throw new Error( + "Type error for parameter contentScriptOptions (Error processing matches: Array requires at least 1 items; you have 0) for contentScripts.register.", + ); + } + + await Promise.all( + matches.map(async (pattern: string) => { + if (!(await chromeProxy.permissions.contains({ origins: [pattern] }))) { + throw new Error(`Permission denied to register a content script for ${pattern}`); + } + }), + ); + + const matchesRegex = patternToRegex(...matches); + const excludeMatchesRegex = patternToRegex( + ...(excludeMatches !== null && excludeMatches !== void 0 ? excludeMatches : []), + ); + const inject = async (url: string, tabId: number, frameId = 0) => { + if ( + !matchesRegex.test(url) || + excludeMatchesRegex.test(url) || + !(await isOriginPermitted(url)) + ) { + return; + } + + await injectContentScript( + { tabId, frameId }, + { css, js, matchAboutBlank, runAt }, + { ignoreTargetErrors: true }, + ); + }; + const tabListener = async ( + tabId: number, + { status }: chrome.tabs.TabChangeInfo, + { url }: chrome.tabs.Tab, + ) => { + if (status === "loading" && url) { + void inject(url, tabId); + } + }; + const navListener = async ({ + tabId, + frameId, + url, + }: chrome.webNavigation.WebNavigationTransitionCallbackDetails) => { + void inject(url, tabId, frameId); + }; + + if (gotNavigation) { + BrowserApi.addListener(chrome.webNavigation.onCommitted, navListener); + } else { + BrowserApi.addListener(chrome.tabs.onUpdated, tabListener); + } + + const registeredContentScript = { + async unregister() { + if (gotNavigation) { + chrome.webNavigation.onCommitted.removeListener(navListener); + } else { + chrome.tabs.onUpdated.removeListener(tabListener); + } + }, + }; + + if (typeof callback === "function") { + callback(registeredContentScript); + } + + return registeredContentScript; + }; +} diff --git a/apps/browser/src/platform/browser/browser-api.spec.ts b/apps/browser/src/platform/browser/browser-api.spec.ts index a1dafb38ec..e452d6d8ee 100644 --- a/apps/browser/src/platform/browser/browser-api.spec.ts +++ b/apps/browser/src/platform/browser/browser-api.spec.ts @@ -550,4 +550,35 @@ describe("BrowserApi", () => { expect(callbackMock).toHaveBeenCalled(); }); }); + + describe("registerContentScriptsMv2", () => { + const details: browser.contentScripts.RegisteredContentScriptOptions = { + matches: [""], + js: [{ file: "content/fido2/page-script.js" }], + }; + + it("registers content scripts through the `browser.contentScripts` API when the API is available", async () => { + globalThis.browser = mock({ + contentScripts: { register: jest.fn() }, + }); + + await BrowserApi.registerContentScriptsMv2(details); + + expect(browser.contentScripts.register).toHaveBeenCalledWith(details); + }); + + it("registers content scripts through the `registerContentScriptsPolyfill` when the `browser.contentScripts.register` API is not available", async () => { + globalThis.browser = mock({ + contentScripts: { register: undefined }, + }); + jest.spyOn(BrowserApi, "addListener"); + + await BrowserApi.registerContentScriptsMv2(details); + + expect(BrowserApi.addListener).toHaveBeenCalledWith( + chrome.webNavigation.onCommitted, + expect.any(Function), + ); + }); + }); }); diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index b2ee66f051..b793777d8b 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -5,6 +5,8 @@ import { DeviceType } from "@bitwarden/common/enums"; import { TabMessage } from "../../types/tab-messages"; import { BrowserPlatformUtilsService } from "../services/platform-utils/browser-platform-utils.service"; +import { registerContentScriptsPolyfill } from "./browser-api.register-content-scripts-polyfill"; + export class BrowserApi { static isWebExtensionsApi: boolean = typeof browser !== "undefined"; static isSafariApi: boolean = @@ -591,4 +593,41 @@ export class BrowserApi { } }); } + + /** + * Handles registration of static content scripts within manifest v2. + * + * @param contentScriptOptions - Details of the registered content scripts + */ + static async registerContentScriptsMv2( + contentScriptOptions: browser.contentScripts.RegisteredContentScriptOptions, + ): Promise { + if (typeof browser !== "undefined" && !!browser.contentScripts?.register) { + return await browser.contentScripts.register(contentScriptOptions); + } + + return await registerContentScriptsPolyfill(contentScriptOptions); + } + + /** + * Handles registration of static content scripts within manifest v3. + * + * @param scripts - Details of the registered content scripts + */ + static async registerContentScriptsMv3( + scripts: chrome.scripting.RegisteredContentScript[], + ): Promise { + await chrome.scripting.registerContentScripts(scripts); + } + + /** + * Handles unregistering of static content scripts within manifest v3. + * + * @param filter - Optional filter to unregister content scripts. Passing an empty object will unregister all content scripts. + */ + static async unregisterContentScriptsMv3( + filter?: chrome.scripting.ContentScriptFilter, + ): Promise { + await chrome.scripting.unregisterContentScripts(filter); + } } diff --git a/apps/browser/src/platform/services/abstractions/script-injector.service.ts b/apps/browser/src/platform/services/abstractions/script-injector.service.ts new file mode 100644 index 0000000000..b41e5c7617 --- /dev/null +++ b/apps/browser/src/platform/services/abstractions/script-injector.service.ts @@ -0,0 +1,45 @@ +export type CommonScriptInjectionDetails = { + /** + * Script injected into the document. + * Overridden by `mv2Details` and `mv3Details`. + */ + file?: string; + /** + * Identifies the frame targeted for script injection. Defaults to the top level frame (0). + * Can also be set to "all_frames" to inject into all frames in a tab. + */ + frame?: "all_frames" | number; + /** + * When the script executes. Defaults to "document_start". + * @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_scripts + */ + runAt?: "document_start" | "document_end" | "document_idle"; +}; + +export type Mv2ScriptInjectionDetails = { + file: string; +}; + +export type Mv3ScriptInjectionDetails = { + file: string; + /** + * The world in which the script should be executed. Defaults to "ISOLATED". + * @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/ExecutionWorld + */ + world?: chrome.scripting.ExecutionWorld; +}; + +/** + * Configuration for injecting a script into a tab. The `file` property should present as a + * path that is relative to the root directory of the extension build, ie "content/script.js". + */ +export type ScriptInjectionConfig = { + tabId: number; + injectDetails: CommonScriptInjectionDetails; + mv2Details?: Mv2ScriptInjectionDetails; + mv3Details?: Mv3ScriptInjectionDetails; +}; + +export abstract class ScriptInjectorService { + abstract inject(config: ScriptInjectionConfig): Promise; +} diff --git a/apps/browser/src/platform/services/browser-script-injector.service.spec.ts b/apps/browser/src/platform/services/browser-script-injector.service.spec.ts new file mode 100644 index 0000000000..6ae84c6464 --- /dev/null +++ b/apps/browser/src/platform/services/browser-script-injector.service.spec.ts @@ -0,0 +1,173 @@ +import { BrowserApi } from "../browser/browser-api"; + +import { + CommonScriptInjectionDetails, + Mv3ScriptInjectionDetails, +} from "./abstractions/script-injector.service"; +import { BrowserScriptInjectorService } from "./browser-script-injector.service"; + +describe("ScriptInjectorService", () => { + const tabId = 1; + const combinedManifestVersionFile = "content/autofill-init.js"; + const mv2SpecificFile = "content/autofill-init-mv2.js"; + const mv2Details = { file: mv2SpecificFile }; + const mv3SpecificFile = "content/autofill-init-mv3.js"; + const mv3Details: Mv3ScriptInjectionDetails = { file: mv3SpecificFile, world: "MAIN" }; + const sharedInjectDetails: CommonScriptInjectionDetails = { + runAt: "document_start", + }; + const manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get"); + let scriptInjectorService: BrowserScriptInjectorService; + jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation(); + jest.spyOn(BrowserApi, "isManifestVersion"); + + beforeEach(() => { + scriptInjectorService = new BrowserScriptInjectorService(); + }); + + describe("inject", () => { + describe("injection of a single script that functions in both manifest v2 and v3", () => { + it("injects the script in manifest v2 when given combined injection details", async () => { + manifestVersionSpy.mockReturnValue(2); + + await scriptInjectorService.inject({ + tabId, + injectDetails: { + file: combinedManifestVersionFile, + frame: "all_frames", + ...sharedInjectDetails, + }, + }); + + expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabId, { + ...sharedInjectDetails, + allFrames: true, + file: combinedManifestVersionFile, + }); + }); + + it("injects the script in manifest v3 when given combined injection details", async () => { + manifestVersionSpy.mockReturnValue(3); + + await scriptInjectorService.inject({ + tabId, + injectDetails: { + file: combinedManifestVersionFile, + frame: 10, + ...sharedInjectDetails, + }, + }); + + expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith( + tabId, + { ...sharedInjectDetails, frameId: 10, file: combinedManifestVersionFile }, + { world: "ISOLATED" }, + ); + }); + }); + + describe("injection of mv2 specific details", () => { + describe("given the extension is running manifest v2", () => { + it("injects the mv2 script injection details file", async () => { + manifestVersionSpy.mockReturnValue(2); + + await scriptInjectorService.inject({ + mv2Details, + tabId, + injectDetails: sharedInjectDetails, + }); + + expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabId, { + ...sharedInjectDetails, + frameId: 0, + file: mv2SpecificFile, + }); + }); + }); + + describe("given the extension is running manifest v3", () => { + it("injects the common script injection details file", async () => { + manifestVersionSpy.mockReturnValue(3); + + await scriptInjectorService.inject({ + mv2Details, + tabId, + injectDetails: { ...sharedInjectDetails, file: combinedManifestVersionFile }, + }); + + expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith( + tabId, + { + ...sharedInjectDetails, + frameId: 0, + file: combinedManifestVersionFile, + }, + { world: "ISOLATED" }, + ); + }); + + it("throws an error if no common script injection details file is specified", async () => { + manifestVersionSpy.mockReturnValue(3); + + await expect( + scriptInjectorService.inject({ + mv2Details, + tabId, + injectDetails: { ...sharedInjectDetails, file: null }, + }), + ).rejects.toThrow("No file specified for script injection"); + }); + }); + }); + + describe("injection of mv3 specific details", () => { + describe("given the extension is running manifest v3", () => { + it("injects the mv3 script injection details file", async () => { + manifestVersionSpy.mockReturnValue(3); + + await scriptInjectorService.inject({ + mv3Details, + tabId, + injectDetails: sharedInjectDetails, + }); + + expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith( + tabId, + { ...sharedInjectDetails, frameId: 0, file: mv3SpecificFile }, + { world: "MAIN" }, + ); + }); + }); + + describe("given the extension is running manifest v2", () => { + it("injects the common script injection details file", async () => { + manifestVersionSpy.mockReturnValue(2); + + await scriptInjectorService.inject({ + mv3Details, + tabId, + injectDetails: { ...sharedInjectDetails, file: combinedManifestVersionFile }, + }); + + expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabId, { + ...sharedInjectDetails, + frameId: 0, + file: combinedManifestVersionFile, + }); + }); + + it("throws an error if no common script injection details file is specified", async () => { + manifestVersionSpy.mockReturnValue(2); + + await expect( + scriptInjectorService.inject({ + mv3Details, + tabId, + injectDetails: { ...sharedInjectDetails, file: "" }, + }), + ).rejects.toThrow("No file specified for script injection"); + }); + }); + }); + }); +}); diff --git a/apps/browser/src/platform/services/browser-script-injector.service.ts b/apps/browser/src/platform/services/browser-script-injector.service.ts new file mode 100644 index 0000000000..54513188d5 --- /dev/null +++ b/apps/browser/src/platform/services/browser-script-injector.service.ts @@ -0,0 +1,78 @@ +import { BrowserApi } from "../browser/browser-api"; + +import { + CommonScriptInjectionDetails, + ScriptInjectionConfig, + ScriptInjectorService, +} from "./abstractions/script-injector.service"; + +export class BrowserScriptInjectorService extends ScriptInjectorService { + /** + * Facilitates the injection of a script into a tab context. Will adjust + * behavior between manifest v2 and v3 based on the passed configuration. + * + * @param config - The configuration for the script injection. + */ + async inject(config: ScriptInjectionConfig): Promise { + const { tabId, injectDetails, mv3Details } = config; + const file = this.getScriptFile(config); + if (!file) { + throw new Error("No file specified for script injection"); + } + + const injectionDetails = this.buildInjectionDetails(injectDetails, file); + + if (BrowserApi.isManifestVersion(3)) { + await BrowserApi.executeScriptInTab(tabId, injectionDetails, { + world: mv3Details?.world ?? "ISOLATED", + }); + + return; + } + + await BrowserApi.executeScriptInTab(tabId, injectionDetails); + } + + /** + * Retrieves the script file to inject based on the configuration. + * + * @param config - The configuration for the script injection. + */ + private getScriptFile(config: ScriptInjectionConfig): string { + const { injectDetails, mv2Details, mv3Details } = config; + + if (BrowserApi.isManifestVersion(3)) { + return mv3Details?.file ?? injectDetails?.file; + } + + return mv2Details?.file ?? injectDetails?.file; + } + + /** + * Builds the injection details for the script injection. + * + * @param injectDetails - The details for the script injection. + * @param file - The file to inject. + */ + private buildInjectionDetails( + injectDetails: CommonScriptInjectionDetails, + file: string, + ): chrome.tabs.InjectDetails { + const { frame, runAt } = injectDetails; + const injectionDetails: chrome.tabs.InjectDetails = { file }; + + if (runAt) { + injectionDetails.runAt = runAt; + } + + if (!frame) { + return { ...injectionDetails, frameId: 0 }; + } + + if (frame !== "all_frames") { + return { ...injectionDetails, frameId: frame }; + } + + return { ...injectionDetails, allFrames: true }; + } +} diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index fe70058640..4906198047 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -94,10 +94,12 @@ import { BrowserApi } from "../../platform/browser/browser-api"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service"; import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service"; +import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service"; import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service"; import BrowserMessagingPrivateModePopupService from "../../platform/services/browser-messaging-private-mode-popup.service"; import BrowserMessagingService from "../../platform/services/browser-messaging.service"; +import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service"; import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service"; import I18nService from "../../platform/services/i18n.service"; import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service"; @@ -319,8 +321,14 @@ const safeProviders: SafeProvider[] = [ DomainSettingsService, UserVerificationService, BillingAccountProfileStateService, + ScriptInjectorService, ], }), + safeProvider({ + provide: ScriptInjectorService, + useClass: BrowserScriptInjectorService, + deps: [], + }), safeProvider({ provide: KeyConnectorService, useFactory: getBgService("keyConnectorService"), diff --git a/apps/browser/src/tools/background/abstractions/fileless-importer.background.ts b/apps/browser/src/tools/background/abstractions/fileless-importer.background.ts index e4b8413718..2ade5bf767 100644 --- a/apps/browser/src/tools/background/abstractions/fileless-importer.background.ts +++ b/apps/browser/src/tools/background/abstractions/fileless-importer.background.ts @@ -1,10 +1,5 @@ import { FilelessImportTypeKeys } from "../../enums/fileless-import.enums"; -type SuppressDownloadScriptInjectionConfig = { - file: string; - scriptingApiDetails?: { world: chrome.scripting.ExecutionWorld }; -}; - type FilelessImportPortMessage = { command?: string; importType?: FilelessImportTypeKeys; @@ -32,7 +27,6 @@ interface FilelessImporterBackground { } export { - SuppressDownloadScriptInjectionConfig, FilelessImportPortMessage, ImportNotificationMessageHandlers, LpImporterMessageHandlers, diff --git a/apps/browser/src/tools/background/fileless-importer.background.spec.ts b/apps/browser/src/tools/background/fileless-importer.background.spec.ts index 858889b887..7d226fcd9d 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.spec.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.spec.ts @@ -16,6 +16,7 @@ import { triggerRuntimeOnConnectEvent, } from "../../autofill/spec/testing-utils"; import { BrowserApi } from "../../platform/browser/browser-api"; +import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service"; import { FilelessImportPort, FilelessImportType } from "../enums/fileless-import.enums"; import FilelessImporterBackground from "./fileless-importer.background"; @@ -37,8 +38,10 @@ describe("FilelessImporterBackground ", () => { const notificationBackground = mock(); const importService = mock(); const syncService = mock(); + let scriptInjectorService: BrowserScriptInjectorService; beforeEach(() => { + scriptInjectorService = new BrowserScriptInjectorService(); filelessImporterBackground = new FilelessImporterBackground( configService, authService, @@ -46,6 +49,7 @@ describe("FilelessImporterBackground ", () => { notificationBackground, importService, syncService, + scriptInjectorService, ); filelessImporterBackground.init(); }); @@ -138,7 +142,7 @@ describe("FilelessImporterBackground ", () => { expect(executeScriptInTabSpy).toHaveBeenCalledWith( lpImporterPort.sender.tab.id, - { file: "content/lp-suppress-import-download.js", runAt: "document_start" }, + { file: "content/lp-suppress-import-download.js", runAt: "document_start", frameId: 0 }, { world: "MAIN" }, ); }); @@ -149,14 +153,11 @@ describe("FilelessImporterBackground ", () => { triggerRuntimeOnConnectEvent(lpImporterPort); await flushPromises(); - expect(executeScriptInTabSpy).toHaveBeenCalledWith( - lpImporterPort.sender.tab.id, - { - file: "content/lp-suppress-import-download-script-append-mv2.js", - runAt: "document_start", - }, - undefined, - ); + expect(executeScriptInTabSpy).toHaveBeenCalledWith(lpImporterPort.sender.tab.id, { + file: "content/lp-suppress-import-download-script-append-mv2.js", + runAt: "document_start", + frameId: 0, + }); }); }); diff --git a/apps/browser/src/tools/background/fileless-importer.background.ts b/apps/browser/src/tools/background/fileless-importer.background.ts index 57c2faa930..07c6408e8d 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.ts @@ -11,6 +11,7 @@ import { ImportServiceAbstraction } from "@bitwarden/importer/core"; import NotificationBackground from "../../autofill/background/notification.background"; import { BrowserApi } from "../../platform/browser/browser-api"; +import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; import { FilelessImporterInjectedScriptsConfig } from "../config/fileless-importer-injected-scripts"; import { FilelessImportPort, @@ -23,7 +24,6 @@ import { LpImporterMessageHandlers, FilelessImporterBackground as FilelessImporterBackgroundInterface, FilelessImportPortMessage, - SuppressDownloadScriptInjectionConfig, } from "./abstractions/fileless-importer.background"; class FilelessImporterBackground implements FilelessImporterBackgroundInterface { @@ -53,6 +53,7 @@ class FilelessImporterBackground implements FilelessImporterBackgroundInterface * @param notificationBackground - Used to inject the notification bar into the tab. * @param importService - Used to import the export data into the vault. * @param syncService - Used to trigger a full sync after the import is completed. + * @param scriptInjectorService - Used to inject content scripts that initialize the import process */ constructor( private configService: ConfigService, @@ -61,6 +62,7 @@ class FilelessImporterBackground implements FilelessImporterBackgroundInterface private notificationBackground: NotificationBackground, private importService: ImportServiceAbstraction, private syncService: SyncService, + private scriptInjectorService: ScriptInjectorService, ) {} /** @@ -110,23 +112,6 @@ class FilelessImporterBackground implements FilelessImporterBackgroundInterface await this.notificationBackground.requestFilelessImport(tab, importType); } - /** - * Injects the script used to suppress the download of the LP importer export file. - * - * @param sender - The sender of the message. - * @param injectionConfig - The configuration for the injection. - */ - private async injectScriptConfig( - sender: chrome.runtime.MessageSender, - injectionConfig: SuppressDownloadScriptInjectionConfig, - ) { - await BrowserApi.executeScriptInTab( - sender.tab.id, - { file: injectionConfig.file, runAt: "document_start" }, - injectionConfig.scriptingApiDetails, - ); - } - /** * Triggers the download of the CSV file from the LP importer. This is triggered * when the user opts to not save the export to Bitwarden within the notification bar. @@ -219,12 +204,12 @@ class FilelessImporterBackground implements FilelessImporterBackgroundInterface switch (port.name) { case FilelessImportPort.LpImporter: this.lpImporterPort = port; - await this.injectScriptConfig( - port.sender, - BrowserApi.manifestVersion === 3 - ? FilelessImporterInjectedScriptsConfig.LpSuppressImportDownload.mv3 - : FilelessImporterInjectedScriptsConfig.LpSuppressImportDownload.mv2, - ); + await this.scriptInjectorService.inject({ + tabId: port.sender.tab.id, + injectDetails: { runAt: "document_start" }, + mv2Details: FilelessImporterInjectedScriptsConfig.LpSuppressImportDownload.mv2, + mv3Details: FilelessImporterInjectedScriptsConfig.LpSuppressImportDownload.mv3, + }); break; case FilelessImportPort.NotificationBar: this.importNotificationsPort = port; diff --git a/apps/browser/src/tools/config/fileless-importer-injected-scripts.ts b/apps/browser/src/tools/config/fileless-importer-injected-scripts.ts index dbc05fe18c..898ee1205a 100644 --- a/apps/browser/src/tools/config/fileless-importer-injected-scripts.ts +++ b/apps/browser/src/tools/config/fileless-importer-injected-scripts.ts @@ -1,9 +1,12 @@ -import { SuppressDownloadScriptInjectionConfig } from "../background/abstractions/fileless-importer.background"; +import { + Mv2ScriptInjectionDetails, + Mv3ScriptInjectionDetails, +} from "../../platform/services/abstractions/script-injector.service"; type FilelessImporterInjectedScriptsConfigurations = { LpSuppressImportDownload: { - mv2: SuppressDownloadScriptInjectionConfig; - mv3: SuppressDownloadScriptInjectionConfig; + mv2: Mv2ScriptInjectionDetails; + mv3: Mv3ScriptInjectionDetails; }; }; @@ -14,7 +17,7 @@ const FilelessImporterInjectedScriptsConfig: FilelessImporterInjectedScriptsConf }, mv3: { file: "content/lp-suppress-import-download.js", - scriptingApiDetails: { world: "MAIN" }, + world: "MAIN", }, }, } as const; diff --git a/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts b/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts new file mode 100644 index 0000000000..49f248c7b8 --- /dev/null +++ b/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts @@ -0,0 +1,57 @@ +import { + AssertCredentialParams, + AssertCredentialResult, + CreateCredentialParams, + CreateCredentialResult, +} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; + +type SharedFido2ScriptInjectionDetails = { + runAt: browser.contentScripts.RegisteredContentScriptOptions["runAt"]; +}; + +type SharedFido2ScriptRegistrationOptions = SharedFido2ScriptInjectionDetails & { + matches: string[]; + excludeMatches: string[]; + allFrames: true; +}; + +type Fido2ExtensionMessage = { + [key: string]: any; + command: string; + hostname?: string; + origin?: string; + requestId?: string; + abortedRequestId?: string; + data?: AssertCredentialParams | CreateCredentialParams; +}; + +type Fido2ExtensionMessageEventParams = { + message: Fido2ExtensionMessage; + sender: chrome.runtime.MessageSender; +}; + +type Fido2BackgroundExtensionMessageHandlers = { + [key: string]: CallableFunction; + fido2AbortRequest: ({ message }: Fido2ExtensionMessageEventParams) => void; + fido2RegisterCredentialRequest: ({ + message, + sender, + }: Fido2ExtensionMessageEventParams) => Promise; + fido2GetCredentialRequest: ({ + message, + sender, + }: Fido2ExtensionMessageEventParams) => Promise; +}; + +interface Fido2Background { + init(): void; + injectFido2ContentScriptsInAllTabs(): Promise; +} + +export { + SharedFido2ScriptInjectionDetails, + SharedFido2ScriptRegistrationOptions, + Fido2ExtensionMessage, + Fido2BackgroundExtensionMessageHandlers, + Fido2Background, +}; diff --git a/apps/browser/src/vault/fido2/background/fido2.background.spec.ts b/apps/browser/src/vault/fido2/background/fido2.background.spec.ts new file mode 100644 index 0000000000..534d8a99c5 --- /dev/null +++ b/apps/browser/src/vault/fido2/background/fido2.background.spec.ts @@ -0,0 +1,414 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { + AssertCredentialParams, + CreateCredentialParams, +} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; +import { Fido2ClientService } from "@bitwarden/common/vault/services/fido2/fido2-client.service"; + +import { createPortSpyMock } from "../../../autofill/spec/autofill-mocks"; +import { + flushPromises, + sendExtensionRuntimeMessage, + triggerPortOnDisconnectEvent, + triggerRuntimeOnConnectEvent, +} from "../../../autofill/spec/testing-utils"; +import { BrowserApi } from "../../../platform/browser/browser-api"; +import { BrowserScriptInjectorService } from "../../../platform/services/browser-script-injector.service"; +import { AbortManager } from "../../background/abort-manager"; +import { Fido2ContentScript, Fido2ContentScriptId } from "../enums/fido2-content-script.enum"; +import { Fido2PortName } from "../enums/fido2-port-name.enum"; + +import { Fido2ExtensionMessage } from "./abstractions/fido2.background"; +import { Fido2Background } from "./fido2.background"; + +const sharedExecuteScriptOptions = { runAt: "document_start" }; +const sharedScriptInjectionDetails = { frame: "all_frames", ...sharedExecuteScriptOptions }; +const contentScriptDetails = { + file: Fido2ContentScript.ContentScript, + ...sharedScriptInjectionDetails, +}; +const sharedRegistrationOptions = { + matches: ["https://*/*"], + excludeMatches: ["https://*/*.xml*"], + allFrames: true, + ...sharedExecuteScriptOptions, +}; + +describe("Fido2Background", () => { + const tabsQuerySpy: jest.SpyInstance = jest.spyOn(BrowserApi, "tabsQuery"); + const isManifestVersionSpy: jest.SpyInstance = jest.spyOn(BrowserApi, "isManifestVersion"); + const focusTabSpy: jest.SpyInstance = jest.spyOn(BrowserApi, "focusTab").mockResolvedValue(); + const focusWindowSpy: jest.SpyInstance = jest + .spyOn(BrowserApi, "focusWindow") + .mockResolvedValue(); + let abortManagerMock!: MockProxy; + let abortController!: MockProxy; + let registeredContentScripsMock!: MockProxy; + let tabMock!: MockProxy; + let senderMock!: MockProxy; + let logService!: MockProxy; + let fido2ClientService!: MockProxy; + let vaultSettingsService!: MockProxy; + let scriptInjectorServiceMock!: MockProxy; + let enablePasskeysMock$!: BehaviorSubject; + let fido2Background!: Fido2Background; + + beforeEach(() => { + tabMock = mock({ + id: 123, + url: "https://example.com", + windowId: 456, + }); + senderMock = mock({ id: "1", tab: tabMock }); + logService = mock(); + fido2ClientService = mock(); + vaultSettingsService = mock(); + abortManagerMock = mock(); + abortController = mock(); + registeredContentScripsMock = mock(); + scriptInjectorServiceMock = mock(); + + enablePasskeysMock$ = new BehaviorSubject(true); + vaultSettingsService.enablePasskeys$ = enablePasskeysMock$; + fido2ClientService.isFido2FeatureEnabled.mockResolvedValue(true); + fido2Background = new Fido2Background( + logService, + fido2ClientService, + vaultSettingsService, + scriptInjectorServiceMock, + ); + fido2Background["abortManager"] = abortManagerMock; + abortManagerMock.runWithAbortController.mockImplementation((_requestId, runner) => + runner(abortController), + ); + isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 2); + }); + + afterEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + }); + + describe("injectFido2ContentScriptsInAllTabs", () => { + it("does not inject any FIDO2 content scripts when no tabs have a secure url protocol", async () => { + const insecureTab = mock({ id: 789, url: "http://example.com" }); + tabsQuerySpy.mockResolvedValueOnce([insecureTab]); + + await fido2Background.injectFido2ContentScriptsInAllTabs(); + + expect(scriptInjectorServiceMock.inject).not.toHaveBeenCalled(); + }); + + it("only injects the FIDO2 content script into tabs that contain a secure url protocol", async () => { + const secondTabMock = mock({ id: 456, url: "https://example.com" }); + const insecureTab = mock({ id: 789, url: "http://example.com" }); + const noUrlTab = mock({ id: 101, url: undefined }); + tabsQuerySpy.mockResolvedValueOnce([tabMock, secondTabMock, insecureTab, noUrlTab]); + + await fido2Background.injectFido2ContentScriptsInAllTabs(); + + expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({ + tabId: tabMock.id, + injectDetails: contentScriptDetails, + }); + expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({ + tabId: secondTabMock.id, + injectDetails: contentScriptDetails, + }); + expect(scriptInjectorServiceMock.inject).not.toHaveBeenCalledWith({ + tabId: insecureTab.id, + injectDetails: contentScriptDetails, + }); + expect(scriptInjectorServiceMock.inject).not.toHaveBeenCalledWith({ + tabId: noUrlTab.id, + injectDetails: contentScriptDetails, + }); + }); + + it("injects the `page-script.js` content script into the provided tab", async () => { + tabsQuerySpy.mockResolvedValueOnce([tabMock]); + + await fido2Background.injectFido2ContentScriptsInAllTabs(); + + expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({ + tabId: tabMock.id, + injectDetails: sharedScriptInjectionDetails, + mv2Details: { file: Fido2ContentScript.PageScriptAppend }, + mv3Details: { file: Fido2ContentScript.PageScript, world: "MAIN" }, + }); + }); + }); + + describe("handleEnablePasskeysUpdate", () => { + let portMock!: MockProxy; + + beforeEach(() => { + fido2Background.init(); + jest.spyOn(BrowserApi, "registerContentScriptsMv2"); + jest.spyOn(BrowserApi, "registerContentScriptsMv3"); + jest.spyOn(BrowserApi, "unregisterContentScriptsMv3"); + portMock = createPortSpyMock(Fido2PortName.InjectedScript); + triggerRuntimeOnConnectEvent(portMock); + triggerRuntimeOnConnectEvent(createPortSpyMock("some-other-port")); + + tabsQuerySpy.mockResolvedValue([tabMock]); + }); + + it("does not destroy and re-inject the content scripts when triggering `handleEnablePasskeysUpdate` with an undefined currentEnablePasskeysSetting property", async () => { + await flushPromises(); + + expect(portMock.disconnect).not.toHaveBeenCalled(); + expect(scriptInjectorServiceMock.inject).not.toHaveBeenCalled(); + }); + + it("destroys the content scripts but skips re-injecting them when the enablePasskeys setting is set to `false`", async () => { + enablePasskeysMock$.next(false); + await flushPromises(); + + expect(portMock.disconnect).toHaveBeenCalled(); + expect(scriptInjectorServiceMock.inject).not.toHaveBeenCalled(); + }); + + it("destroys and re-injects the content scripts when the enablePasskeys setting is set to `true`", async () => { + enablePasskeysMock$.next(true); + await flushPromises(); + + expect(portMock.disconnect).toHaveBeenCalled(); + expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({ + tabId: tabMock.id, + injectDetails: sharedScriptInjectionDetails, + mv2Details: { file: Fido2ContentScript.PageScriptAppend }, + mv3Details: { file: Fido2ContentScript.PageScript, world: "MAIN" }, + }); + expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({ + tabId: tabMock.id, + injectDetails: contentScriptDetails, + }); + }); + + describe("given manifest v2", () => { + it("registers the page-script-append-mv2.js and content-script.js content scripts when the enablePasskeys setting is set to `true`", async () => { + isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 2); + + enablePasskeysMock$.next(true); + await flushPromises(); + + expect(BrowserApi.registerContentScriptsMv2).toHaveBeenCalledWith({ + js: [ + { file: Fido2ContentScript.PageScriptAppend }, + { file: Fido2ContentScript.ContentScript }, + ], + ...sharedRegistrationOptions, + }); + }); + + it("unregisters any existing registered content scripts when the enablePasskeys setting is set to `false`", async () => { + isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 2); + fido2Background["registeredContentScripts"] = registeredContentScripsMock; + + enablePasskeysMock$.next(false); + await flushPromises(); + + expect(registeredContentScripsMock.unregister).toHaveBeenCalled(); + expect(BrowserApi.registerContentScriptsMv2).not.toHaveBeenCalledTimes(2); + }); + }); + + describe("given manifest v3", () => { + it("registers the page-script.js and content-script.js content scripts when the enablePasskeys setting is set to `true`", async () => { + isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 3); + + enablePasskeysMock$.next(true); + await flushPromises(); + + expect(BrowserApi.registerContentScriptsMv3).toHaveBeenCalledWith([ + { + id: Fido2ContentScriptId.PageScript, + js: [Fido2ContentScript.PageScript], + world: "MAIN", + ...sharedRegistrationOptions, + }, + { + id: Fido2ContentScriptId.ContentScript, + js: [Fido2ContentScript.ContentScript], + ...sharedRegistrationOptions, + }, + ]); + expect(BrowserApi.unregisterContentScriptsMv3).not.toHaveBeenCalled(); + }); + + it("unregisters the page-script.js and content-script.js content scripts when the enablePasskeys setting is set to `false`", async () => { + isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 3); + + enablePasskeysMock$.next(false); + await flushPromises(); + + expect(BrowserApi.unregisterContentScriptsMv3).toHaveBeenCalledWith({ + ids: [Fido2ContentScriptId.PageScript, Fido2ContentScriptId.ContentScript], + }); + expect(BrowserApi.registerContentScriptsMv3).not.toHaveBeenCalledTimes(2); + }); + }); + }); + + describe("extension message handlers", () => { + beforeEach(() => { + fido2Background.init(); + }); + + it("ignores messages that do not have a handler associated with a command within the message", () => { + const message = mock({ command: "nonexistentCommand" }); + + sendExtensionRuntimeMessage(message); + + expect(abortManagerMock.abort).not.toHaveBeenCalled(); + }); + + it("sends a response for rejected promises returned by a handler", async () => { + const message = mock({ command: "fido2RegisterCredentialRequest" }); + const sender = mock(); + const sendResponse = jest.fn(); + fido2ClientService.createCredential.mockRejectedValue(new Error("error")); + + sendExtensionRuntimeMessage(message, sender, sendResponse); + await flushPromises(); + + expect(sendResponse).toHaveBeenCalledWith({ error: { message: "error" } }); + }); + + describe("fido2AbortRequest message", () => { + it("aborts the request associated with the passed abortedRequestId", async () => { + const message = mock({ + command: "fido2AbortRequest", + abortedRequestId: "123", + }); + + sendExtensionRuntimeMessage(message); + await flushPromises(); + + expect(abortManagerMock.abort).toHaveBeenCalledWith(message.abortedRequestId); + }); + }); + + describe("fido2RegisterCredentialRequest message", () => { + it("creates a credential within the Fido2ClientService", async () => { + const message = mock({ + command: "fido2RegisterCredentialRequest", + requestId: "123", + data: mock(), + }); + + sendExtensionRuntimeMessage(message, senderMock); + await flushPromises(); + + expect(fido2ClientService.createCredential).toHaveBeenCalledWith( + message.data, + tabMock, + abortController, + ); + expect(focusTabSpy).toHaveBeenCalledWith(tabMock.id); + expect(focusWindowSpy).toHaveBeenCalledWith(tabMock.windowId); + }); + }); + + describe("fido2GetCredentialRequest", () => { + it("asserts a credential within the Fido2ClientService", async () => { + const message = mock({ + command: "fido2GetCredentialRequest", + requestId: "123", + data: mock(), + }); + + sendExtensionRuntimeMessage(message, senderMock); + await flushPromises(); + + expect(fido2ClientService.assertCredential).toHaveBeenCalledWith( + message.data, + tabMock, + abortController, + ); + expect(focusTabSpy).toHaveBeenCalledWith(tabMock.id); + expect(focusWindowSpy).toHaveBeenCalledWith(tabMock.windowId); + }); + }); + }); + + describe("handle ports onConnect", () => { + let portMock!: MockProxy; + + beforeEach(() => { + fido2Background.init(); + portMock = createPortSpyMock(Fido2PortName.InjectedScript); + fido2ClientService.isFido2FeatureEnabled.mockResolvedValue(true); + }); + + it("ignores port connections that do not have the correct port name", async () => { + const port = createPortSpyMock("nonexistentPort"); + + triggerRuntimeOnConnectEvent(port); + await flushPromises(); + + expect(port.onDisconnect.addListener).not.toHaveBeenCalled(); + }); + + it("ignores port connections that do not have a sender url", async () => { + portMock.sender = undefined; + + triggerRuntimeOnConnectEvent(portMock); + await flushPromises(); + + expect(portMock.onDisconnect.addListener).not.toHaveBeenCalled(); + }); + + it("disconnects the port connection when the Fido2 feature is not enabled", async () => { + fido2ClientService.isFido2FeatureEnabled.mockResolvedValue(false); + + triggerRuntimeOnConnectEvent(portMock); + await flushPromises(); + + expect(portMock.disconnect).toHaveBeenCalled(); + }); + + it("disconnects the port connection when the url is malformed", async () => { + portMock.sender.url = "malformed-url"; + + triggerRuntimeOnConnectEvent(portMock); + await flushPromises(); + + expect(portMock.disconnect).toHaveBeenCalled(); + expect(logService.error).toHaveBeenCalled(); + }); + + it("adds the port to the fido2ContentScriptPortsSet when the Fido2 feature is enabled", async () => { + triggerRuntimeOnConnectEvent(portMock); + await flushPromises(); + + expect(portMock.onDisconnect.addListener).toHaveBeenCalled(); + }); + }); + + describe("handleInjectScriptPortOnDisconnect", () => { + let portMock!: MockProxy; + + beforeEach(() => { + fido2Background.init(); + portMock = createPortSpyMock(Fido2PortName.InjectedScript); + triggerRuntimeOnConnectEvent(portMock); + fido2Background["fido2ContentScriptPortsSet"].add(portMock); + }); + + it("does not destroy or inject the content script when the port has already disconnected before the enablePasskeys setting is set to `false`", async () => { + triggerPortOnDisconnectEvent(portMock); + + enablePasskeysMock$.next(false); + await flushPromises(); + + expect(portMock.disconnect).not.toHaveBeenCalled(); + expect(scriptInjectorServiceMock.inject).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/vault/fido2/background/fido2.background.ts b/apps/browser/src/vault/fido2/background/fido2.background.ts new file mode 100644 index 0000000000..856874cee3 --- /dev/null +++ b/apps/browser/src/vault/fido2/background/fido2.background.ts @@ -0,0 +1,356 @@ +import { firstValueFrom, startWith } from "rxjs"; +import { pairwise } from "rxjs/operators"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { + AssertCredentialParams, + AssertCredentialResult, + CreateCredentialParams, + CreateCredentialResult, + Fido2ClientService, +} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; + +import { BrowserApi } from "../../../platform/browser/browser-api"; +import { ScriptInjectorService } from "../../../platform/services/abstractions/script-injector.service"; +import { AbortManager } from "../../background/abort-manager"; +import { Fido2ContentScript, Fido2ContentScriptId } from "../enums/fido2-content-script.enum"; +import { Fido2PortName } from "../enums/fido2-port-name.enum"; + +import { + Fido2Background as Fido2BackgroundInterface, + Fido2BackgroundExtensionMessageHandlers, + Fido2ExtensionMessage, + SharedFido2ScriptInjectionDetails, + SharedFido2ScriptRegistrationOptions, +} from "./abstractions/fido2.background"; + +export class Fido2Background implements Fido2BackgroundInterface { + private abortManager = new AbortManager(); + private fido2ContentScriptPortsSet = new Set(); + private registeredContentScripts: browser.contentScripts.RegisteredContentScript; + private readonly sharedInjectionDetails: SharedFido2ScriptInjectionDetails = { + runAt: "document_start", + }; + private readonly sharedRegistrationOptions: SharedFido2ScriptRegistrationOptions = { + matches: ["https://*/*"], + excludeMatches: ["https://*/*.xml*"], + allFrames: true, + ...this.sharedInjectionDetails, + }; + private readonly extensionMessageHandlers: Fido2BackgroundExtensionMessageHandlers = { + fido2AbortRequest: ({ message }) => this.abortRequest(message), + fido2RegisterCredentialRequest: ({ message, sender }) => + this.registerCredentialRequest(message, sender), + fido2GetCredentialRequest: ({ message, sender }) => this.getCredentialRequest(message, sender), + }; + + constructor( + private logService: LogService, + private fido2ClientService: Fido2ClientService, + private vaultSettingsService: VaultSettingsService, + private scriptInjectorService: ScriptInjectorService, + ) {} + + /** + * Initializes the FIDO2 background service. Sets up the extension message + * and port listeners. Subscribes to the enablePasskeys$ observable to + * handle passkey enable/disable events. + */ + init() { + BrowserApi.messageListener("fido2.background", this.handleExtensionMessage); + BrowserApi.addListener(chrome.runtime.onConnect, this.handleInjectedScriptPortConnection); + this.vaultSettingsService.enablePasskeys$ + .pipe(startWith(undefined), pairwise()) + .subscribe(([previous, current]) => this.handleEnablePasskeysUpdate(previous, current)); + } + + /** + * Injects the FIDO2 content and page script into all existing browser tabs. + */ + async injectFido2ContentScriptsInAllTabs() { + const tabs = await BrowserApi.tabsQuery({}); + for (let index = 0; index < tabs.length; index++) { + const tab = tabs[index]; + if (!tab.url?.startsWith("https")) { + continue; + } + + void this.injectFido2ContentScripts(tab); + } + } + + /** + * Handles reacting to the enablePasskeys setting being updated. If the setting + * is enabled, the FIDO2 content scripts are injected into all tabs. If the setting + * is disabled, the FIDO2 content scripts will be from all tabs. This logic will + * not trigger until after the first setting update. + * + * @param previousEnablePasskeysSetting - The previous value of the enablePasskeys setting. + * @param enablePasskeys - The new value of the enablePasskeys setting. + */ + private async handleEnablePasskeysUpdate( + previousEnablePasskeysSetting: boolean, + enablePasskeys: boolean, + ) { + await this.updateContentScriptRegistration(); + + if (previousEnablePasskeysSetting === undefined) { + return; + } + + this.destroyLoadedFido2ContentScripts(); + if (enablePasskeys) { + void this.injectFido2ContentScriptsInAllTabs(); + } + } + + /** + * Updates the registration status of static FIDO2 content + * scripts based on the enablePasskeys setting. + */ + private async updateContentScriptRegistration() { + if (BrowserApi.isManifestVersion(2)) { + await this.updateMv2ContentScriptsRegistration(); + + return; + } + + await this.updateMv3ContentScriptsRegistration(); + } + + /** + * Updates the registration status of static FIDO2 content + * scripts based on the enablePasskeys setting for manifest v2. + */ + private async updateMv2ContentScriptsRegistration() { + if (!(await this.isPasskeySettingEnabled())) { + await this.registeredContentScripts?.unregister(); + + return; + } + + this.registeredContentScripts = await BrowserApi.registerContentScriptsMv2({ + js: [ + { file: Fido2ContentScript.PageScriptAppend }, + { file: Fido2ContentScript.ContentScript }, + ], + ...this.sharedRegistrationOptions, + }); + } + + /** + * Updates the registration status of static FIDO2 content + * scripts based on the enablePasskeys setting for manifest v3. + */ + private async updateMv3ContentScriptsRegistration() { + if (await this.isPasskeySettingEnabled()) { + void BrowserApi.registerContentScriptsMv3([ + { + id: Fido2ContentScriptId.PageScript, + js: [Fido2ContentScript.PageScript], + world: "MAIN", + ...this.sharedRegistrationOptions, + }, + { + id: Fido2ContentScriptId.ContentScript, + js: [Fido2ContentScript.ContentScript], + ...this.sharedRegistrationOptions, + }, + ]); + + return; + } + + void BrowserApi.unregisterContentScriptsMv3({ + ids: [Fido2ContentScriptId.PageScript, Fido2ContentScriptId.ContentScript], + }); + } + + /** + * Injects the FIDO2 content and page script into the current tab. + * + * @param tab - The current tab to inject the scripts into. + */ + private async injectFido2ContentScripts(tab: chrome.tabs.Tab): Promise { + void this.scriptInjectorService.inject({ + tabId: tab.id, + injectDetails: { frame: "all_frames", ...this.sharedInjectionDetails }, + mv2Details: { file: Fido2ContentScript.PageScriptAppend }, + mv3Details: { file: Fido2ContentScript.PageScript, world: "MAIN" }, + }); + + void this.scriptInjectorService.inject({ + tabId: tab.id, + injectDetails: { + file: Fido2ContentScript.ContentScript, + frame: "all_frames", + ...this.sharedInjectionDetails, + }, + }); + } + + /** + * Iterates over the set of injected FIDO2 content script ports + * and disconnects them, destroying the content scripts. + */ + private destroyLoadedFido2ContentScripts() { + this.fido2ContentScriptPortsSet.forEach((port) => { + port.disconnect(); + this.fido2ContentScriptPortsSet.delete(port); + }); + } + + /** + * Aborts the FIDO2 request with the provided requestId. + * + * @param message - The FIDO2 extension message containing the requestId to abort. + */ + private abortRequest(message: Fido2ExtensionMessage) { + this.abortManager.abort(message.abortedRequestId); + } + + /** + * Registers a new FIDO2 credential with the provided request data. + * + * @param message - The FIDO2 extension message containing the request data. + * @param sender - The sender of the message. + */ + private async registerCredentialRequest( + message: Fido2ExtensionMessage, + sender: chrome.runtime.MessageSender, + ): Promise { + return await this.handleCredentialRequest( + message, + sender.tab, + this.fido2ClientService.createCredential.bind(this.fido2ClientService), + ); + } + + /** + * Gets a FIDO2 credential with the provided request data. + * + * @param message - The FIDO2 extension message containing the request data. + * @param sender - The sender of the message. + */ + private async getCredentialRequest( + message: Fido2ExtensionMessage, + sender: chrome.runtime.MessageSender, + ): Promise { + return await this.handleCredentialRequest( + message, + sender.tab, + this.fido2ClientService.assertCredential.bind(this.fido2ClientService), + ); + } + + /** + * Handles Fido2 credential requests by calling the provided callback with the + * request data, tab, and abort controller. The callback is expected to return + * a promise that resolves with the result of the credential request. + * + * @param requestId - The request ID associated with the request. + * @param data - The request data to handle. + * @param tab - The tab associated with the request. + * @param callback - The callback to call with the request data, tab, and abort controller. + */ + private handleCredentialRequest = async ( + { requestId, data }: Fido2ExtensionMessage, + tab: chrome.tabs.Tab, + callback: ( + data: AssertCredentialParams | CreateCredentialParams, + tab: chrome.tabs.Tab, + abortController: AbortController, + ) => Promise, + ) => { + return await this.abortManager.runWithAbortController(requestId, async (abortController) => { + try { + return await callback(data, tab, abortController); + } finally { + await BrowserApi.focusTab(tab.id); + await BrowserApi.focusWindow(tab.windowId); + } + }); + }; + + /** + * Checks if the enablePasskeys setting is enabled. + */ + private async isPasskeySettingEnabled() { + return await firstValueFrom(this.vaultSettingsService.enablePasskeys$); + } + + /** + * Handles the FIDO2 extension message by calling the + * appropriate handler based on the message command. + * + * @param message - The FIDO2 extension message to handle. + * @param sender - The sender of the message. + * @param sendResponse - The function to call with the response. + */ + private handleExtensionMessage = ( + message: Fido2ExtensionMessage, + sender: chrome.runtime.MessageSender, + sendResponse: (response?: any) => void, + ) => { + const handler: CallableFunction | undefined = this.extensionMessageHandlers[message?.command]; + if (!handler) { + return; + } + + const messageResponse = handler({ message, sender }); + if (!messageResponse) { + return; + } + + Promise.resolve(messageResponse) + .then( + (response) => sendResponse(response), + (error) => sendResponse({ error: { ...error, message: error.message } }), + ) + .catch(this.logService.error); + + return true; + }; + + /** + * Handles the connection of a FIDO2 content script port by checking if the + * FIDO2 feature is enabled for the sender's hostname and origin. If the feature + * is not enabled, the port is disconnected. + * + * @param port - The port which is connecting + */ + private handleInjectedScriptPortConnection = async (port: chrome.runtime.Port) => { + if (port.name !== Fido2PortName.InjectedScript || !port.sender?.url) { + return; + } + + try { + const { hostname, origin } = new URL(port.sender.url); + if (!(await this.fido2ClientService.isFido2FeatureEnabled(hostname, origin))) { + port.disconnect(); + return; + } + + this.fido2ContentScriptPortsSet.add(port); + port.onDisconnect.addListener(this.handleInjectScriptPortOnDisconnect); + } catch (error) { + this.logService.error(error); + port.disconnect(); + } + }; + + /** + * Handles the disconnection of a FIDO2 content script port + * by removing it from the set of connected ports. + * + * @param port - The port which is disconnecting + */ + private handleInjectScriptPortOnDisconnect = (port: chrome.runtime.Port) => { + if (port.name !== Fido2PortName.InjectedScript) { + return; + } + + this.fido2ContentScriptPortsSet.delete(port); + }; +} diff --git a/apps/browser/src/vault/fido2/content/content-script.spec.ts b/apps/browser/src/vault/fido2/content/content-script.spec.ts new file mode 100644 index 0000000000..29d3e9c257 --- /dev/null +++ b/apps/browser/src/vault/fido2/content/content-script.spec.ts @@ -0,0 +1,164 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { CreateCredentialResult } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; + +import { createPortSpyMock } from "../../../autofill/spec/autofill-mocks"; +import { triggerPortOnDisconnectEvent } from "../../../autofill/spec/testing-utils"; +import { Fido2PortName } from "../enums/fido2-port-name.enum"; + +import { InsecureCreateCredentialParams, MessageType } from "./messaging/message"; +import { MessageWithMetadata, Messenger } from "./messaging/messenger"; + +jest.mock("../../../autofill/utils", () => ({ + sendExtensionMessage: jest.fn((command, options) => { + return chrome.runtime.sendMessage(Object.assign({ command }, options)); + }), +})); + +describe("Fido2 Content Script", () => { + let messenger: Messenger; + const messengerForDOMCommunicationSpy = jest + .spyOn(Messenger, "forDOMCommunication") + .mockImplementation((window) => { + const windowOrigin = window.location.origin; + + messenger = new Messenger({ + postMessage: (message, port) => window.postMessage(message, windowOrigin, [port]), + addEventListener: (listener) => window.addEventListener("message", listener), + removeEventListener: (listener) => window.removeEventListener("message", listener), + }); + messenger.destroy = jest.fn(); + return messenger; + }); + const portSpy: MockProxy = createPortSpyMock(Fido2PortName.InjectedScript); + chrome.runtime.connect = jest.fn(() => portSpy); + + afterEach(() => { + Object.defineProperty(document, "contentType", { + value: "text/html", + writable: true, + }); + + jest.clearAllMocks(); + jest.resetModules(); + }); + + it("destroys the messenger when the port is disconnected", () => { + require("./content-script"); + + triggerPortOnDisconnectEvent(portSpy); + + expect(messenger.destroy).toHaveBeenCalled(); + }); + + it("handles a FIDO2 credential creation request message from the window message listener, formats the message and sends the formatted message to the extension background", async () => { + const message = mock({ + type: MessageType.CredentialCreationRequest, + data: mock(), + }); + const mockResult = { credentialId: "mock" } as CreateCredentialResult; + jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue(mockResult); + + require("./content-script"); + + const response = await messenger.handler!(message, new AbortController()); + + expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ + command: "fido2RegisterCredentialRequest", + data: expect.objectContaining({ + origin: globalThis.location.origin, + sameOriginWithAncestors: true, + }), + requestId: expect.any(String), + }); + expect(response).toEqual({ + type: MessageType.CredentialCreationResponse, + result: mockResult, + }); + }); + + it("handles a FIDO2 credential get request message from the window message listener, formats the message and sends the formatted message to the extension background", async () => { + const message = mock({ + type: MessageType.CredentialGetRequest, + data: mock(), + }); + + require("./content-script"); + + await messenger.handler!(message, new AbortController()); + + expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ + command: "fido2GetCredentialRequest", + data: expect.objectContaining({ + origin: globalThis.location.origin, + sameOriginWithAncestors: true, + }), + requestId: expect.any(String), + }); + }); + + it("removes the abort handler when the FIDO2 request is complete", async () => { + const message = mock({ + type: MessageType.CredentialCreationRequest, + data: mock(), + }); + const abortController = new AbortController(); + const abortSpy = jest.spyOn(abortController.signal, "removeEventListener"); + + require("./content-script"); + + await messenger.handler!(message, abortController); + + expect(abortSpy).toHaveBeenCalled(); + }); + + it("sends an extension message to abort the FIDO2 request when the abort controller is signaled", async () => { + const message = mock({ + type: MessageType.CredentialCreationRequest, + data: mock(), + }); + const abortController = new AbortController(); + const abortSpy = jest.spyOn(abortController.signal, "addEventListener"); + jest + .spyOn(chrome.runtime, "sendMessage") + .mockImplementationOnce(async (extensionId: string, message: unknown, options: any) => { + abortController.abort(); + }); + + require("./content-script"); + + await messenger.handler!(message, abortController); + + expect(abortSpy).toHaveBeenCalled(); + expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ + command: "fido2AbortRequest", + abortedRequestId: expect.any(String), + }); + }); + + it("rejects credential requests and returns an error result", async () => { + const errorMessage = "Test error"; + const message = mock({ + type: MessageType.CredentialCreationRequest, + data: mock(), + }); + const abortController = new AbortController(); + jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue({ error: errorMessage }); + + require("./content-script"); + const result = messenger.handler!(message, abortController); + + await expect(result).rejects.toEqual(errorMessage); + }); + + it("skips initializing the content script if the document content type is not 'text/html'", () => { + Object.defineProperty(document, "contentType", { + value: "application/json", + writable: true, + }); + + require("./content-script"); + + expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/browser/src/vault/fido2/content/content-script.ts b/apps/browser/src/vault/fido2/content/content-script.ts index c2fc862f55..809db11553 100644 --- a/apps/browser/src/vault/fido2/content/content-script.ts +++ b/apps/browser/src/vault/fido2/content/content-script.ts @@ -3,142 +3,134 @@ import { CreateCredentialParams, } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; -import { Message, MessageType } from "./messaging/message"; -import { Messenger } from "./messaging/messenger"; +import { sendExtensionMessage } from "../../../autofill/utils"; +import { Fido2PortName } from "../enums/fido2-port-name.enum"; -function isFido2FeatureEnabled(): Promise { - return new Promise((resolve) => { - chrome.runtime.sendMessage( - { - command: "checkFido2FeatureEnabled", - hostname: window.location.hostname, - origin: window.location.origin, - }, - (response: { result?: boolean }) => resolve(response.result), - ); - }); -} - -function isSameOriginWithAncestors() { - try { - return window.self === window.top; - } catch { - return false; - } -} -const messenger = Messenger.forDOMCommunication(window); - -function injectPageScript() { - // Locate an existing page-script on the page - const existingPageScript = document.getElementById("bw-fido2-page-script"); - - // Inject the page-script if it doesn't exist - if (!existingPageScript) { - const s = document.createElement("script"); - s.src = chrome.runtime.getURL("content/fido2/page-script.js"); - s.id = "bw-fido2-page-script"; - (document.head || document.documentElement).appendChild(s); +import { + InsecureAssertCredentialParams, + InsecureCreateCredentialParams, + Message, + MessageType, +} from "./messaging/message"; +import { MessageWithMetadata, Messenger } from "./messaging/messenger"; +(function (globalContext) { + if (globalContext.document.contentType !== "text/html") { return; } - // If the page-script already exists, send a reconnect message to the page-script - // 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 - messenger.sendReconnectCommand(); -} + // Initialization logic, set up the messenger and connect a port to the background script. + const messenger = Messenger.forDOMCommunication(globalContext.window); + messenger.handler = handleFido2Message; + const port = chrome.runtime.connect({ name: Fido2PortName.InjectedScript }); + port.onDisconnect.addListener(handlePortOnDisconnect); -function initializeFido2ContentScript() { - injectPageScript(); - - messenger.handler = async (message, abortController) => { + /** + * Handles FIDO2 credential requests and returns the result. + * + * @param message - The message to handle. + * @param abortController - The abort controller used to handle exit conditions from the FIDO2 request. + */ + async function handleFido2Message( + message: MessageWithMetadata, + abortController: AbortController, + ) { const requestId = Date.now().toString(); const abortHandler = () => - chrome.runtime.sendMessage({ - command: "fido2AbortRequest", - abortedRequestId: requestId, - }); + sendExtensionMessage("fido2AbortRequest", { abortedRequestId: requestId }); abortController.signal.addEventListener("abort", abortHandler); - if (message.type === MessageType.CredentialCreationRequest) { - return new Promise((resolve, reject) => { - const data: CreateCredentialParams = { - ...message.data, - origin: window.location.origin, - sameOriginWithAncestors: isSameOriginWithAncestors(), - }; - - chrome.runtime.sendMessage( - { - command: "fido2RegisterCredentialRequest", - data, - requestId: requestId, - }, - (response) => { - if (response && response.error !== undefined) { - return reject(response.error); - } - - resolve({ - type: MessageType.CredentialCreationResponse, - result: response.result, - }); - }, + try { + if (message.type === MessageType.CredentialCreationRequest) { + return handleCredentialCreationRequestMessage( + requestId, + message.data as InsecureCreateCredentialParams, ); - }); - } + } - if (message.type === MessageType.CredentialGetRequest) { - return new Promise((resolve, reject) => { - const data: AssertCredentialParams = { - ...message.data, - origin: window.location.origin, - sameOriginWithAncestors: isSameOriginWithAncestors(), - }; - - chrome.runtime.sendMessage( - { - command: "fido2GetCredentialRequest", - data, - requestId: requestId, - }, - (response) => { - if (response && response.error !== undefined) { - return reject(response.error); - } - - resolve({ - type: MessageType.CredentialGetResponse, - result: response.result, - }); - }, + if (message.type === MessageType.CredentialGetRequest) { + return handleCredentialGetRequestMessage( + requestId, + message.data as InsecureAssertCredentialParams, ); - }).finally(() => - abortController.signal.removeEventListener("abort", abortHandler), - ) as Promise; + } + } finally { + abortController.signal.removeEventListener("abort", abortHandler); } - - return undefined; - }; -} - -async function run() { - if (!(await isFido2FeatureEnabled())) { - return; } - initializeFido2ContentScript(); + /** + * Handles the credential creation request message and returns the result. + * + * @param requestId - The request ID of the message. + * @param data - Data associated with the credential request. + */ + async function handleCredentialCreationRequestMessage( + requestId: string, + data: InsecureCreateCredentialParams, + ): Promise { + return respondToCredentialRequest( + "fido2RegisterCredentialRequest", + MessageType.CredentialCreationResponse, + requestId, + data, + ); + } - const port = chrome.runtime.connect({ name: "fido2ContentScriptReady" }); - port.onDisconnect.addListener(() => { - // Cleanup the messenger and remove the event listener - // 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 - messenger.destroy(); - }); -} + /** + * Handles the credential get request message and returns the result. + * + * @param requestId - The request ID of the message. + * @param data - Data associated with the credential request. + */ + async function handleCredentialGetRequestMessage( + requestId: string, + data: InsecureAssertCredentialParams, + ): Promise { + return respondToCredentialRequest( + "fido2GetCredentialRequest", + MessageType.CredentialGetResponse, + requestId, + data, + ); + } -// Only run the script if the document is an HTML document -if (document.contentType === "text/html") { - void run(); -} + /** + * Sends a message to the extension to handle the + * credential request and returns the result. + * + * @param command - The command to send to the extension. + * @param type - The type of message, either CredentialCreationResponse or CredentialGetResponse. + * @param requestId - The request ID of the message. + * @param messageData - Data associated with the credential request. + */ + async function respondToCredentialRequest( + command: string, + type: MessageType.CredentialCreationResponse | MessageType.CredentialGetResponse, + requestId: string, + messageData: InsecureCreateCredentialParams | InsecureAssertCredentialParams, + ): Promise { + const data: CreateCredentialParams | AssertCredentialParams = { + ...messageData, + origin: globalContext.location.origin, + sameOriginWithAncestors: globalContext.self === globalContext.top, + }; + + const result = await sendExtensionMessage(command, { data, requestId }); + + if (result && result.error !== undefined) { + return Promise.reject(result.error); + } + + return Promise.resolve({ type, result }); + } + + /** + * Handles the disconnect event of the port. Calls + * to the messenger to destroy and tear down the + * implemented page-script.js logic. + */ + function handlePortOnDisconnect() { + void messenger.destroy(); + } +})(globalThis); diff --git a/apps/browser/src/vault/fido2/content/messaging/messenger.spec.ts b/apps/browser/src/vault/fido2/content/messaging/messenger.spec.ts index 0c46ac39aa..5283c60882 100644 --- a/apps/browser/src/vault/fido2/content/messaging/messenger.spec.ts +++ b/apps/browser/src/vault/fido2/content/messaging/messenger.spec.ts @@ -68,7 +68,7 @@ describe("Messenger", () => { const abortController = new AbortController(); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises - messengerA.request(createRequest(), abortController); + messengerA.request(createRequest(), abortController.signal); abortController.abort(); const received = handlerB.receive(); diff --git a/apps/browser/src/vault/fido2/content/messaging/messenger.ts b/apps/browser/src/vault/fido2/content/messaging/messenger.ts index cc29282227..f05c138eab 100644 --- a/apps/browser/src/vault/fido2/content/messaging/messenger.ts +++ b/apps/browser/src/vault/fido2/content/messaging/messenger.ts @@ -47,7 +47,7 @@ export class Messenger { } /** - * The handler that will be called when a message is recieved. The handler should return + * The handler that will be called when a message is received. The handler should return * a promise that resolves to the response message. If the handler throws an error, the * error will be sent back to the sender. */ @@ -65,10 +65,10 @@ export class Messenger { * AbortController signals will be forwarded to the content script. * * @param request data to send to the content script - * @param abortController the abort controller that might be used to abort the request + * @param abortSignal the abort controller that might be used to abort the request * @returns the response from the content script */ - async request(request: Message, abortController?: AbortController): Promise { + async request(request: Message, abortSignal?: AbortSignal): Promise { const requestChannel = new MessageChannel(); const { port1: localPort, port2: remotePort } = requestChannel; @@ -82,7 +82,7 @@ export class Messenger { metadata: { SENDER }, type: MessageType.AbortRequest, }); - abortController?.signal.addEventListener("abort", abortListener); + abortSignal?.addEventListener("abort", abortListener); this.broadcastChannel.postMessage( { ...request, SENDER, senderId: this.messengerId }, @@ -90,7 +90,7 @@ export class Messenger { ); const response = await promise; - abortController?.signal.removeEventListener("abort", abortListener); + abortSignal?.removeEventListener("abort", abortListener); if (response.type === MessageType.ErrorResponse) { const error = new Error(); @@ -113,12 +113,7 @@ export class Messenger { const message = event.data; const port = event.ports?.[0]; - if ( - message?.SENDER !== SENDER || - message.senderId == this.messengerId || - message == null || - port == null - ) { + if (message?.SENDER !== SENDER || message.senderId == this.messengerId || port == null) { return; } @@ -167,10 +162,6 @@ export class Messenger { } } - async sendReconnectCommand() { - await this.request({ type: MessageType.ReconnectRequest }); - } - private async sendDisconnectCommand() { await this.request({ type: MessageType.DisconnectRequest }); } diff --git a/apps/browser/src/vault/fido2/content/page-script-append.mv2.spec.ts b/apps/browser/src/vault/fido2/content/page-script-append.mv2.spec.ts new file mode 100644 index 0000000000..d40a725a1f --- /dev/null +++ b/apps/browser/src/vault/fido2/content/page-script-append.mv2.spec.ts @@ -0,0 +1,69 @@ +import { Fido2ContentScript } from "../enums/fido2-content-script.enum"; + +describe("FIDO2 page-script for manifest v2", () => { + let createdScriptElement: HTMLScriptElement; + jest.spyOn(window.document, "createElement"); + + afterEach(() => { + Object.defineProperty(window.document, "contentType", { value: "text/html", writable: true }); + jest.clearAllMocks(); + jest.resetModules(); + }); + + it("skips appending the `page-script.js` file if the document contentType is not `text/html`", () => { + Object.defineProperty(window.document, "contentType", { value: "text/plain", writable: true }); + + require("./page-script-append.mv2"); + + expect(window.document.createElement).not.toHaveBeenCalled(); + }); + + it("appends the `page-script.js` file to the document head when the contentType is `text/html`", () => { + jest.spyOn(window.document.head, "insertBefore").mockImplementation((node) => { + createdScriptElement = node as HTMLScriptElement; + return node; + }); + + require("./page-script-append.mv2"); + + expect(window.document.createElement).toHaveBeenCalledWith("script"); + expect(chrome.runtime.getURL).toHaveBeenCalledWith(Fido2ContentScript.PageScript); + expect(window.document.head.insertBefore).toHaveBeenCalledWith( + expect.any(HTMLScriptElement), + window.document.head.firstChild, + ); + expect(createdScriptElement.src).toBe(`chrome-extension://id/${Fido2ContentScript.PageScript}`); + }); + + it("appends the `page-script.js` file to the document element if the head is not available", () => { + window.document.documentElement.removeChild(window.document.head); + jest.spyOn(window.document.documentElement, "insertBefore").mockImplementation((node) => { + createdScriptElement = node as HTMLScriptElement; + return node; + }); + + require("./page-script-append.mv2"); + + expect(window.document.createElement).toHaveBeenCalledWith("script"); + expect(chrome.runtime.getURL).toHaveBeenCalledWith(Fido2ContentScript.PageScript); + expect(window.document.documentElement.insertBefore).toHaveBeenCalledWith( + expect.any(HTMLScriptElement), + window.document.documentElement.firstChild, + ); + expect(createdScriptElement.src).toBe(`chrome-extension://id/${Fido2ContentScript.PageScript}`); + }); + + it("removes the appended `page-script.js` file after the script has triggered a load event", () => { + createdScriptElement = document.createElement("script"); + jest.spyOn(window.document, "createElement").mockImplementation((element) => { + return createdScriptElement; + }); + + require("./page-script-append.mv2"); + + jest.spyOn(createdScriptElement, "remove"); + createdScriptElement.dispatchEvent(new Event("load")); + + expect(createdScriptElement.remove).toHaveBeenCalled(); + }); +}); diff --git a/apps/browser/src/vault/fido2/content/page-script-append.mv2.ts b/apps/browser/src/vault/fido2/content/page-script-append.mv2.ts new file mode 100644 index 0000000000..4e806d2990 --- /dev/null +++ b/apps/browser/src/vault/fido2/content/page-script-append.mv2.ts @@ -0,0 +1,19 @@ +/** + * This script handles injection of the FIDO2 override page script into the document. + * This is required for manifest v2, but will be removed when we migrate fully to manifest v3. + */ +import { Fido2ContentScript } from "../enums/fido2-content-script.enum"; + +(function (globalContext) { + if (globalContext.document.contentType !== "text/html") { + return; + } + + const script = globalContext.document.createElement("script"); + script.src = chrome.runtime.getURL(Fido2ContentScript.PageScript); + script.addEventListener("load", () => script.remove()); + + const scriptInsertionPoint = + globalContext.document.head || globalContext.document.documentElement; + scriptInsertionPoint.insertBefore(script, scriptInsertionPoint.firstChild); +})(globalThis); diff --git a/apps/browser/src/vault/fido2/content/page-script.ts b/apps/browser/src/vault/fido2/content/page-script.ts index 9adea68307..1de0f3258a 100644 --- a/apps/browser/src/vault/fido2/content/page-script.ts +++ b/apps/browser/src/vault/fido2/content/page-script.ts @@ -5,212 +5,229 @@ import { WebauthnUtils } from "../webauthn-utils"; import { MessageType } from "./messaging/message"; import { Messenger } from "./messaging/messenger"; -const BrowserPublicKeyCredential = window.PublicKeyCredential; - -const browserNativeWebauthnSupport = window.PublicKeyCredential != undefined; -let browserNativeWebauthnPlatformAuthenticatorSupport = false; -if (!browserNativeWebauthnSupport) { - // Polyfill webauthn support - try { - // credentials is read-only if supported, use type-casting to force assignment - (navigator as any).credentials = { - async create() { - throw new Error("Webauthn not supported in this browser."); - }, - async get() { - throw new Error("Webauthn not supported in this browser."); - }, - }; - window.PublicKeyCredential = class PolyfillPublicKeyCredential { - static isUserVerifyingPlatformAuthenticatorAvailable() { - return Promise.resolve(true); - } - } as any; - window.AuthenticatorAttestationResponse = - class PolyfillAuthenticatorAttestationResponse {} as any; - } catch { - /* empty */ +(function (globalContext) { + if (globalContext.document.contentType !== "text/html") { + return; } -} + const BrowserPublicKeyCredential = globalContext.PublicKeyCredential; + const BrowserNavigatorCredentials = navigator.credentials; + const BrowserAuthenticatorAttestationResponse = globalContext.AuthenticatorAttestationResponse; -if (browserNativeWebauthnSupport) { - // 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 - BrowserPublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().then((available) => { - browserNativeWebauthnPlatformAuthenticatorSupport = available; - - if (!available) { - // Polyfill platform authenticator support - window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = () => - Promise.resolve(true); + const browserNativeWebauthnSupport = globalContext.PublicKeyCredential != undefined; + let browserNativeWebauthnPlatformAuthenticatorSupport = false; + if (!browserNativeWebauthnSupport) { + // Polyfill webauthn support + try { + // credentials are read-only if supported, use type-casting to force assignment + (navigator as any).credentials = { + async create() { + throw new Error("Webauthn not supported in this browser."); + }, + async get() { + throw new Error("Webauthn not supported in this browser."); + }, + }; + globalContext.PublicKeyCredential = class PolyfillPublicKeyCredential { + static isUserVerifyingPlatformAuthenticatorAvailable() { + return Promise.resolve(true); + } + } as any; + globalContext.AuthenticatorAttestationResponse = + class PolyfillAuthenticatorAttestationResponse {} as any; + } catch { + /* empty */ } - }); -} + } else { + void BrowserPublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().then( + (available) => { + browserNativeWebauthnPlatformAuthenticatorSupport = available; -const browserCredentials = { - create: navigator.credentials.create.bind( - navigator.credentials, - ) as typeof navigator.credentials.create, - get: navigator.credentials.get.bind(navigator.credentials) as typeof navigator.credentials.get, -}; - -const messenger = ((window as any).messenger = Messenger.forDOMCommunication(window)); - -navigator.credentials.create = createWebAuthnCredential; -navigator.credentials.get = getWebAuthnCredential; - -/** - * Creates a new webauthn credential. - * - * @param options Options for creating new credentials. - * @param abortController Abort controller to abort the request if needed. - * @returns Promise that resolves to the new credential object. - */ -async function createWebAuthnCredential( - options?: CredentialCreationOptions, - abortController?: AbortController, -): Promise { - if (!isWebauthnCall(options)) { - return await browserCredentials.create(options); - } - - const fallbackSupported = - (options?.publicKey?.authenticatorSelection?.authenticatorAttachment === "platform" && - browserNativeWebauthnPlatformAuthenticatorSupport) || - (options?.publicKey?.authenticatorSelection?.authenticatorAttachment !== "platform" && - browserNativeWebauthnSupport); - try { - const response = await messenger.request( - { - type: MessageType.CredentialCreationRequest, - data: WebauthnUtils.mapCredentialCreationOptions(options, fallbackSupported), + if (!available) { + // Polyfill platform authenticator support + globalContext.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = () => + Promise.resolve(true); + } }, - abortController, ); + } - if (response.type !== MessageType.CredentialCreationResponse) { - throw new Error("Something went wrong."); - } + const browserCredentials = { + create: navigator.credentials.create.bind( + navigator.credentials, + ) as typeof navigator.credentials.create, + get: navigator.credentials.get.bind(navigator.credentials) as typeof navigator.credentials.get, + }; - return WebauthnUtils.mapCredentialRegistrationResult(response.result); - } catch (error) { - if (error && error.fallbackRequested && fallbackSupported) { - await waitForFocus(); + const messenger = Messenger.forDOMCommunication(window); + let waitForFocusTimeout: number | NodeJS.Timeout; + let focusListenerHandler: () => void; + + navigator.credentials.create = createWebAuthnCredential; + navigator.credentials.get = getWebAuthnCredential; + + /** + * Creates a new webauthn credential. + * + * @param options Options for creating new credentials. + * @returns Promise that resolves to the new credential object. + */ + async function createWebAuthnCredential( + options?: CredentialCreationOptions, + ): Promise { + if (!isWebauthnCall(options)) { return await browserCredentials.create(options); } - throw error; - } -} + const authenticatorAttachmentIsPlatform = + options?.publicKey?.authenticatorSelection?.authenticatorAttachment === "platform"; -/** - * Retrieves a webauthn credential. - * - * @param options Options for creating new credentials. - * @param abortController Abort controller to abort the request if needed. - * @returns Promise that resolves to the new credential object. - */ -async function getWebAuthnCredential( - options?: CredentialRequestOptions, - abortController?: AbortController, -): Promise { - if (!isWebauthnCall(options)) { - return await browserCredentials.get(options); + const fallbackSupported = + (authenticatorAttachmentIsPlatform && browserNativeWebauthnPlatformAuthenticatorSupport) || + (!authenticatorAttachmentIsPlatform && browserNativeWebauthnSupport); + try { + const response = await messenger.request( + { + type: MessageType.CredentialCreationRequest, + data: WebauthnUtils.mapCredentialCreationOptions(options, fallbackSupported), + }, + options?.signal, + ); + + if (response.type !== MessageType.CredentialCreationResponse) { + throw new Error("Something went wrong."); + } + + return WebauthnUtils.mapCredentialRegistrationResult(response.result); + } catch (error) { + if (error && error.fallbackRequested && fallbackSupported) { + await waitForFocus(); + return await browserCredentials.create(options); + } + + throw error; + } } - const fallbackSupported = browserNativeWebauthnSupport; - - try { - if (options?.mediation && options.mediation !== "optional") { - throw new FallbackRequestedError(); - } - - const response = await messenger.request( - { - type: MessageType.CredentialGetRequest, - data: WebauthnUtils.mapCredentialRequestOptions(options, fallbackSupported), - }, - abortController, - ); - - if (response.type !== MessageType.CredentialGetResponse) { - throw new Error("Something went wrong."); - } - - return WebauthnUtils.mapCredentialAssertResult(response.result); - } catch (error) { - if (error && error.fallbackRequested && fallbackSupported) { - await waitForFocus(); + /** + * Retrieves a webauthn credential. + * + * @param options Options for creating new credentials. + * @returns Promise that resolves to the new credential object. + */ + async function getWebAuthnCredential(options?: CredentialRequestOptions): Promise { + if (!isWebauthnCall(options)) { return await browserCredentials.get(options); } - throw error; - } -} + const fallbackSupported = browserNativeWebauthnSupport; -function isWebauthnCall(options?: CredentialCreationOptions | CredentialRequestOptions) { - return options && "publicKey" in options; -} + try { + if (options?.mediation && options.mediation !== "optional") { + throw new FallbackRequestedError(); + } -/** - * Wait for window to be focused. - * Safari doesn't allow scripts to trigger webauthn when window is not focused. - * - * @param fallbackWait How long to wait when the script is not able to add event listeners to `window.top`. Defaults to 500ms. - * @param timeout Maximum time to wait for focus in milliseconds. Defaults to 5 minutes. - * @returns Promise that resolves when window is focused, or rejects if timeout is reached. - */ -async function waitForFocus(fallbackWait = 500, timeout = 5 * 60 * 1000) { - try { - if (window.top.document.hasFocus()) { - return; + const response = await messenger.request( + { + type: MessageType.CredentialGetRequest, + data: WebauthnUtils.mapCredentialRequestOptions(options, fallbackSupported), + }, + options?.signal, + ); + + if (response.type !== MessageType.CredentialGetResponse) { + throw new Error("Something went wrong."); + } + + return WebauthnUtils.mapCredentialAssertResult(response.result); + } catch (error) { + if (error && error.fallbackRequested && fallbackSupported) { + await waitForFocus(); + return await browserCredentials.get(options); + } + + throw error; } - } catch { - // Cannot access window.top due to cross-origin frame, fallback to waiting - return await new Promise((resolve) => window.setTimeout(resolve, fallbackWait)); } - let focusListener; - const focusPromise = new Promise((resolve) => { - focusListener = () => resolve(); - window.top.addEventListener("focus", focusListener); - }); - - let timeoutId; - const timeoutPromise = new Promise((_, reject) => { - timeoutId = window.setTimeout( - () => - reject( - new DOMException("The operation either timed out or was not allowed.", "AbortError"), - ), - timeout, - ); - }); - - try { - await Promise.race([focusPromise, timeoutPromise]); - } finally { - window.top.removeEventListener("focus", focusListener); - window.clearTimeout(timeoutId); - } -} - -/** - * Sets up a listener to handle cleanup or reconnection when the extension's - * context changes due to being reloaded or unloaded. - */ -messenger.handler = (message, abortController) => { - const type = message.type; - - // Handle cleanup for disconnect request - if (type === MessageType.DisconnectRequest && browserNativeWebauthnSupport) { - navigator.credentials.create = browserCredentials.create; - navigator.credentials.get = browserCredentials.get; + function isWebauthnCall(options?: CredentialCreationOptions | CredentialRequestOptions) { + return options && "publicKey" in options; } - // Handle reinitialization for reconnect request - if (type === MessageType.ReconnectRequest && browserNativeWebauthnSupport) { - navigator.credentials.create = createWebAuthnCredential; - navigator.credentials.get = getWebAuthnCredential; + /** + * Wait for window to be focused. + * Safari doesn't allow scripts to trigger webauthn when window is not focused. + * + * @param fallbackWait How long to wait when the script is not able to add event listeners to `window.top`. Defaults to 500ms. + * @param timeout Maximum time to wait for focus in milliseconds. Defaults to 5 minutes. + * @returns Promise that resolves when window is focused, or rejects if timeout is reached. + */ + async function waitForFocus(fallbackWait = 500, timeout = 5 * 60 * 1000) { + try { + if (globalContext.top.document.hasFocus()) { + return; + } + } catch { + // Cannot access window.top due to cross-origin frame, fallback to waiting + return await new Promise((resolve) => globalContext.setTimeout(resolve, fallbackWait)); + } + + const focusPromise = new Promise((resolve) => { + focusListenerHandler = () => resolve(); + globalContext.top.addEventListener("focus", focusListenerHandler); + }); + + const timeoutPromise = new Promise((_, reject) => { + waitForFocusTimeout = globalContext.setTimeout( + () => + reject( + new DOMException("The operation either timed out or was not allowed.", "AbortError"), + ), + timeout, + ); + }); + + try { + await Promise.race([focusPromise, timeoutPromise]); + } finally { + clearWaitForFocus(); + } } -}; + + function clearWaitForFocus() { + globalContext.top.removeEventListener("focus", focusListenerHandler); + if (waitForFocusTimeout) { + globalContext.clearTimeout(waitForFocusTimeout); + } + } + + function destroy() { + try { + if (browserNativeWebauthnSupport) { + navigator.credentials.create = browserCredentials.create; + navigator.credentials.get = browserCredentials.get; + } else { + (navigator as any).credentials = BrowserNavigatorCredentials; + globalContext.PublicKeyCredential = BrowserPublicKeyCredential; + globalContext.AuthenticatorAttestationResponse = BrowserAuthenticatorAttestationResponse; + } + + clearWaitForFocus(); + void messenger.destroy(); + } catch (e) { + /** empty */ + } + } + + /** + * Sets up a listener to handle cleanup or reconnection when the extension's + * context changes due to being reloaded or unloaded. + */ + messenger.handler = (message) => { + const type = message.type; + + // Handle cleanup for disconnect request + if (type === MessageType.DisconnectRequest) { + destroy(); + } + }; +})(globalThis); diff --git a/apps/browser/src/vault/fido2/content/page-script.webauthn-supported.spec.ts b/apps/browser/src/vault/fido2/content/page-script.webauthn-supported.spec.ts new file mode 100644 index 0000000000..211959c466 --- /dev/null +++ b/apps/browser/src/vault/fido2/content/page-script.webauthn-supported.spec.ts @@ -0,0 +1,121 @@ +import { + createAssertCredentialResultMock, + createCreateCredentialResultMock, + createCredentialCreationOptionsMock, + createCredentialRequestOptionsMock, + setupMockedWebAuthnSupport, +} from "../../../autofill/spec/fido2-testing-utils"; +import { WebauthnUtils } from "../webauthn-utils"; + +import { MessageType } from "./messaging/message"; +import { Messenger } from "./messaging/messenger"; + +let messenger: Messenger; +jest.mock("./messaging/messenger", () => { + return { + Messenger: class extends jest.requireActual("./messaging/messenger").Messenger { + static forDOMCommunication: any = jest.fn((window) => { + const windowOrigin = window.location.origin; + + messenger = new Messenger({ + postMessage: (message, port) => window.postMessage(message, windowOrigin, [port]), + addEventListener: (listener) => window.addEventListener("message", listener), + removeEventListener: (listener) => window.removeEventListener("message", listener), + }); + messenger.destroy = jest.fn(); + return messenger; + }); + }, + }; +}); +jest.mock("../webauthn-utils"); + +describe("Fido2 page script with native WebAuthn support", () => { + const mockCredentialCreationOptions = createCredentialCreationOptionsMock(); + const mockCreateCredentialsResult = createCreateCredentialResultMock(); + const mockCredentialRequestOptions = createCredentialRequestOptionsMock(); + const mockCredentialAssertResult = createAssertCredentialResultMock(); + setupMockedWebAuthnSupport(); + + require("./page-script"); + + afterAll(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + + describe("creating WebAuthn credentials", () => { + beforeEach(() => { + messenger.request = jest.fn().mockResolvedValue({ + type: MessageType.CredentialCreationResponse, + result: mockCreateCredentialsResult, + }); + }); + + it("falls back to the default browser credentials API if an error occurs", async () => { + window.top.document.hasFocus = jest.fn().mockReturnValue(true); + messenger.request = jest.fn().mockRejectedValue({ fallbackRequested: true }); + + try { + await navigator.credentials.create(mockCredentialCreationOptions); + expect("This will fail the test").toBe(true); + } catch { + expect(WebauthnUtils.mapCredentialRegistrationResult).not.toHaveBeenCalled(); + } + }); + + it("creates and returns a WebAuthn credential when the navigator API is called to create credentials", async () => { + await navigator.credentials.create(mockCredentialCreationOptions); + + expect(WebauthnUtils.mapCredentialCreationOptions).toHaveBeenCalledWith( + mockCredentialCreationOptions, + true, + ); + expect(WebauthnUtils.mapCredentialRegistrationResult).toHaveBeenCalledWith( + mockCreateCredentialsResult, + ); + }); + }); + + describe("get WebAuthn credentials", () => { + beforeEach(() => { + messenger.request = jest.fn().mockResolvedValue({ + type: MessageType.CredentialGetResponse, + result: mockCredentialAssertResult, + }); + }); + + it("falls back to the default browser credentials API when an error occurs", async () => { + window.top.document.hasFocus = jest.fn().mockReturnValue(true); + messenger.request = jest.fn().mockRejectedValue({ fallbackRequested: true }); + + const returnValue = await navigator.credentials.get(mockCredentialRequestOptions); + + expect(returnValue).toBeDefined(); + expect(WebauthnUtils.mapCredentialAssertResult).not.toHaveBeenCalled(); + }); + + it("gets and returns the WebAuthn credentials", async () => { + await navigator.credentials.get(mockCredentialRequestOptions); + + expect(WebauthnUtils.mapCredentialRequestOptions).toHaveBeenCalledWith( + mockCredentialRequestOptions, + true, + ); + expect(WebauthnUtils.mapCredentialAssertResult).toHaveBeenCalledWith( + mockCredentialAssertResult, + ); + }); + }); + + describe("destroy", () => { + it("should destroy the message listener when receiving a disconnect request", async () => { + jest.spyOn(globalThis.top, "removeEventListener"); + const SENDER = "bitwarden-webauthn"; + void messenger.handler({ type: MessageType.DisconnectRequest, SENDER, senderId: "1" }); + + expect(globalThis.top.removeEventListener).toHaveBeenCalledWith("focus", undefined); + expect(messenger.destroy).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/vault/fido2/content/page-script.webauthn-unsupported.spec.ts b/apps/browser/src/vault/fido2/content/page-script.webauthn-unsupported.spec.ts new file mode 100644 index 0000000000..f3aee685e1 --- /dev/null +++ b/apps/browser/src/vault/fido2/content/page-script.webauthn-unsupported.spec.ts @@ -0,0 +1,96 @@ +import { + createAssertCredentialResultMock, + createCreateCredentialResultMock, + createCredentialCreationOptionsMock, + createCredentialRequestOptionsMock, +} from "../../../autofill/spec/fido2-testing-utils"; +import { WebauthnUtils } from "../webauthn-utils"; + +import { MessageType } from "./messaging/message"; +import { Messenger } from "./messaging/messenger"; + +let messenger: Messenger; +jest.mock("./messaging/messenger", () => { + return { + Messenger: class extends jest.requireActual("./messaging/messenger").Messenger { + static forDOMCommunication: any = jest.fn((window) => { + const windowOrigin = window.location.origin; + + messenger = new Messenger({ + postMessage: (message, port) => window.postMessage(message, windowOrigin, [port]), + addEventListener: (listener) => window.addEventListener("message", listener), + removeEventListener: (listener) => window.removeEventListener("message", listener), + }); + messenger.destroy = jest.fn(); + return messenger; + }); + }, + }; +}); +jest.mock("../webauthn-utils"); + +describe("Fido2 page script without native WebAuthn support", () => { + const mockCredentialCreationOptions = createCredentialCreationOptionsMock(); + const mockCreateCredentialsResult = createCreateCredentialResultMock(); + const mockCredentialRequestOptions = createCredentialRequestOptionsMock(); + const mockCredentialAssertResult = createAssertCredentialResultMock(); + require("./page-script"); + + afterAll(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + + describe("creating WebAuthn credentials", () => { + beforeEach(() => { + messenger.request = jest.fn().mockResolvedValue({ + type: MessageType.CredentialCreationResponse, + result: mockCreateCredentialsResult, + }); + }); + + it("creates and returns a WebAuthn credential", async () => { + await navigator.credentials.create(mockCredentialCreationOptions); + + expect(WebauthnUtils.mapCredentialCreationOptions).toHaveBeenCalledWith( + mockCredentialCreationOptions, + false, + ); + expect(WebauthnUtils.mapCredentialRegistrationResult).toHaveBeenCalledWith( + mockCreateCredentialsResult, + ); + }); + }); + + describe("get WebAuthn credentials", () => { + beforeEach(() => { + messenger.request = jest.fn().mockResolvedValue({ + type: MessageType.CredentialGetResponse, + result: mockCredentialAssertResult, + }); + }); + + it("gets and returns the WebAuthn credentials", async () => { + await navigator.credentials.get(mockCredentialRequestOptions); + + expect(WebauthnUtils.mapCredentialRequestOptions).toHaveBeenCalledWith( + mockCredentialRequestOptions, + false, + ); + expect(WebauthnUtils.mapCredentialAssertResult).toHaveBeenCalledWith( + mockCredentialAssertResult, + ); + }); + }); + + describe("destroy", () => { + it("should destroy the message listener when receiving a disconnect request", async () => { + jest.spyOn(globalThis.top, "removeEventListener"); + const SENDER = "bitwarden-webauthn"; + void messenger.handler({ type: MessageType.DisconnectRequest, SENDER, senderId: "1" }); + + expect(globalThis.top.removeEventListener).toHaveBeenCalledWith("focus", undefined); + expect(messenger.destroy).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/vault/fido2/content/trigger-fido2-content-script-injection.spec.ts b/apps/browser/src/vault/fido2/content/trigger-fido2-content-script-injection.spec.ts deleted file mode 100644 index 8f4efe0330..0000000000 --- a/apps/browser/src/vault/fido2/content/trigger-fido2-content-script-injection.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -describe("TriggerFido2ContentScriptInjection", () => { - afterEach(() => { - jest.resetModules(); - jest.clearAllMocks(); - }); - - describe("init", () => { - it("sends a message to the extension background", () => { - require("../content/trigger-fido2-content-script-injection"); - - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ - command: "triggerFido2ContentScriptInjection", - }); - }); - }); -}); diff --git a/apps/browser/src/vault/fido2/content/trigger-fido2-content-script-injection.ts b/apps/browser/src/vault/fido2/content/trigger-fido2-content-script-injection.ts deleted file mode 100644 index 7ca6956729..0000000000 --- a/apps/browser/src/vault/fido2/content/trigger-fido2-content-script-injection.ts +++ /dev/null @@ -1,5 +0,0 @@ -(function () { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - chrome.runtime.sendMessage({ command: "triggerFido2ContentScriptInjection" }); -})(); diff --git a/apps/browser/src/vault/fido2/enums/fido2-content-script.enum.ts b/apps/browser/src/vault/fido2/enums/fido2-content-script.enum.ts new file mode 100644 index 0000000000..287de6804b --- /dev/null +++ b/apps/browser/src/vault/fido2/enums/fido2-content-script.enum.ts @@ -0,0 +1,10 @@ +export const Fido2ContentScript = { + PageScript: "content/fido2/page-script.js", + PageScriptAppend: "content/fido2/page-script-append-mv2.js", + ContentScript: "content/fido2/content-script.js", +} as const; + +export const Fido2ContentScriptId = { + PageScript: "fido2-page-script-registration", + ContentScript: "fido2-content-script-registration", +} as const; diff --git a/apps/browser/src/vault/fido2/enums/fido2-port-name.enum.ts b/apps/browser/src/vault/fido2/enums/fido2-port-name.enum.ts new file mode 100644 index 0000000000..7836247425 --- /dev/null +++ b/apps/browser/src/vault/fido2/enums/fido2-port-name.enum.ts @@ -0,0 +1,3 @@ +export const Fido2PortName = { + InjectedScript: "fido2-injected-content-script-port", +} as const; diff --git a/apps/browser/src/vault/services/abstractions/fido2.service.ts b/apps/browser/src/vault/services/abstractions/fido2.service.ts deleted file mode 100644 index 138b538b15..0000000000 --- a/apps/browser/src/vault/services/abstractions/fido2.service.ts +++ /dev/null @@ -1,4 +0,0 @@ -export abstract class Fido2Service { - init: () => Promise; - injectFido2ContentScripts: (sender: chrome.runtime.MessageSender) => Promise; -} diff --git a/apps/browser/src/vault/services/fido2.service.spec.ts b/apps/browser/src/vault/services/fido2.service.spec.ts deleted file mode 100644 index 1db2bdfb77..0000000000 --- a/apps/browser/src/vault/services/fido2.service.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { BrowserApi } from "../../platform/browser/browser-api"; - -import Fido2Service from "./fido2.service"; - -describe("Fido2Service", () => { - let fido2Service: Fido2Service; - let tabMock: chrome.tabs.Tab; - let sender: chrome.runtime.MessageSender; - - beforeEach(() => { - fido2Service = new Fido2Service(); - tabMock = { id: 123, url: "https://bitwarden.com" } as chrome.tabs.Tab; - sender = { tab: tabMock }; - jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation(); - }); - - afterEach(() => { - jest.resetModules(); - jest.clearAllMocks(); - }); - - describe("injectFido2ContentScripts", () => { - const fido2ContentScript = "content/fido2/content-script.js"; - const defaultExecuteScriptOptions = { runAt: "document_start" }; - - it("accepts an extension message sender and injects the fido2 scripts into the tab of the sender", async () => { - await fido2Service.injectFido2ContentScripts(sender); - - expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabMock.id, { - file: fido2ContentScript, - ...defaultExecuteScriptOptions, - }); - }); - }); -}); diff --git a/apps/browser/src/vault/services/fido2.service.ts b/apps/browser/src/vault/services/fido2.service.ts deleted file mode 100644 index 98b440b109..0000000000 --- a/apps/browser/src/vault/services/fido2.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { BrowserApi } from "../../platform/browser/browser-api"; - -import { Fido2Service as Fido2ServiceInterface } from "./abstractions/fido2.service"; - -export default class Fido2Service implements Fido2ServiceInterface { - async init() { - const tabs = await BrowserApi.tabsQuery({}); - tabs.forEach((tab) => { - if (tab.url?.startsWith("https")) { - // 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.injectFido2ContentScripts({ tab } as chrome.runtime.MessageSender); - } - }); - - BrowserApi.addListener(chrome.runtime.onConnect, (port) => { - if (port.name === "fido2ContentScriptReady") { - port.postMessage({ command: "fido2ContentScriptInit" }); - } - }); - } - - /** - * Injects the FIDO2 content script into the current tab. - * @param {chrome.runtime.MessageSender} sender - * @returns {Promise} - */ - async injectFido2ContentScripts(sender: chrome.runtime.MessageSender): Promise { - await BrowserApi.executeScriptInTab(sender.tab.id, { - file: "content/fido2/content-script.js", - frameId: sender.frameId, - runAt: "document_start", - }); - } -} diff --git a/apps/browser/test.setup.ts b/apps/browser/test.setup.ts index 1031268186..4800b4c17f 100644 --- a/apps/browser/test.setup.ts +++ b/apps/browser/test.setup.ts @@ -68,6 +68,8 @@ const tabs = { const scripting = { executeScript: jest.fn(), + registerContentScripts: jest.fn(), + unregisterContentScripts: jest.fn(), }; const windows = { @@ -124,6 +126,19 @@ const offscreen = { }, }; +const permissions = { + contains: jest.fn((permissions, callback) => { + callback(true); + }), +}; + +const webNavigation = { + onCommitted: { + addListener: jest.fn(), + removeListener: jest.fn(), + }, +}; + // set chrome global.chrome = { i18n, @@ -137,4 +152,6 @@ global.chrome = { privacy, extension, offscreen, + permissions, + webNavigation, } as any; diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index 694246f59a..505f1533ae 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -9,6 +9,7 @@ "allowJs": true, "sourceMap": true, "baseUrl": ".", + "lib": ["ES2021.String"], "paths": { "@bitwarden/admin-console": ["../../libs/admin-console/src"], "@bitwarden/angular/*": ["../../libs/angular/src/*"], diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 3b5724b198..2756ab4395 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -166,8 +166,6 @@ const mainConfig = { "content/notificationBar": "./src/autofill/content/notification-bar.ts", "content/contextMenuHandler": "./src/autofill/content/context-menu-handler.ts", "content/content-message-handler": "./src/autofill/content/content-message-handler.ts", - "content/fido2/trigger-fido2-content-script-injection": - "./src/vault/fido2/content/trigger-fido2-content-script-injection.ts", "content/fido2/content-script": "./src/vault/fido2/content/content-script.ts", "content/fido2/page-script": "./src/vault/fido2/content/page-script.ts", "notification/bar": "./src/autofill/notification/bar.ts", @@ -277,6 +275,8 @@ if (manifestVersion == 2) { mainConfig.entry.background = "./src/platform/background.ts"; mainConfig.entry["content/lp-suppress-import-download-script-append-mv2"] = "./src/tools/content/lp-suppress-import-download-script-append.mv2.ts"; + mainConfig.entry["content/fido2/page-script-append-mv2"] = + "./src/vault/fido2/content/page-script-append.mv2.ts"; configs.push(mainConfig); } else { diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.ts b/libs/common/src/vault/services/fido2/fido2-client.service.ts index bfc8cbe915..4e0aab017a 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.ts @@ -48,20 +48,26 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { ) {} async isFido2FeatureEnabled(hostname: string, origin: string): Promise { - const userEnabledPasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$); const isUserLoggedIn = (await this.authService.getAuthStatus()) !== AuthenticationStatus.LoggedOut; + if (!isUserLoggedIn) { + return false; + } const neverDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); const isExcludedDomain = neverDomains != null && hostname in neverDomains; + if (isExcludedDomain) { + return false; + } const serverConfig = await firstValueFrom(this.configService.serverConfig$); const isOriginEqualBitwardenVault = origin === serverConfig.environment?.vault; + if (isOriginEqualBitwardenVault) { + return false; + } - return ( - userEnabledPasskeys && isUserLoggedIn && !isExcludedDomain && !isOriginEqualBitwardenVault - ); + return await firstValueFrom(this.vaultSettingsService.enablePasskeys$); } async createCredential( @@ -70,6 +76,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { abortController = new AbortController(), ): Promise { const parsedOrigin = parse(params.origin, { allowPrivateDomains: true }); + const enableFido2VaultCredentials = await this.isFido2FeatureEnabled( parsedOrigin.hostname, params.origin, @@ -346,7 +353,7 @@ function setAbortTimeout( ); } - return window.setTimeout(() => abortController.abort(), clampedTimeout); + return self.setTimeout(() => abortController.abort(), clampedTimeout); } /** diff --git a/libs/common/src/vault/services/fido2/fido2-utils.spec.ts b/libs/common/src/vault/services/fido2/fido2-utils.spec.ts new file mode 100644 index 0000000000..a05eab5230 --- /dev/null +++ b/libs/common/src/vault/services/fido2/fido2-utils.spec.ts @@ -0,0 +1,40 @@ +import { Fido2Utils } from "./fido2-utils"; + +describe("Fido2 Utils", () => { + const asciiHelloWorldArray = [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]; + const b64HelloWorldString = "aGVsbG8gd29ybGQ="; + + describe("fromBufferToB64(...)", () => { + it("should convert an ArrayBuffer to a b64 string", () => { + const buffer = new Uint8Array(asciiHelloWorldArray).buffer; + const b64String = Fido2Utils.fromBufferToB64(buffer); + expect(b64String).toBe(b64HelloWorldString); + }); + + it("should return an empty string when given an empty ArrayBuffer", () => { + const buffer = new Uint8Array([]).buffer; + const b64String = Fido2Utils.fromBufferToB64(buffer); + expect(b64String).toBe(""); + }); + + it("should return null when given null input", () => { + const b64String = Fido2Utils.fromBufferToB64(null); + expect(b64String).toBeNull(); + }); + }); + + describe("fromB64ToArray(...)", () => { + it("should convert a b64 string to an Uint8Array", () => { + const expectedArray = new Uint8Array(asciiHelloWorldArray); + + const resultArray = Fido2Utils.fromB64ToArray(b64HelloWorldString); + + expect(resultArray).toEqual(expectedArray); + }); + + it("should return null when given null input", () => { + const expectedArray = Fido2Utils.fromB64ToArray(null); + expect(expectedArray).toBeNull(); + }); + }); +}); diff --git a/libs/common/src/vault/services/fido2/fido2-utils.ts b/libs/common/src/vault/services/fido2/fido2-utils.ts index a2de137550..13c9762135 100644 --- a/libs/common/src/vault/services/fido2/fido2-utils.ts +++ b/libs/common/src/vault/services/fido2/fido2-utils.ts @@ -1,14 +1,20 @@ -import { Utils } from "../../../platform/misc/utils"; - export class Fido2Utils { static bufferToString(bufferSource: BufferSource): string { - const buffer = Fido2Utils.bufferSourceToUint8Array(bufferSource); + let buffer: Uint8Array; + if (bufferSource instanceof ArrayBuffer || bufferSource.buffer === undefined) { + buffer = new Uint8Array(bufferSource as ArrayBuffer); + } else { + buffer = new Uint8Array(bufferSource.buffer); + } - return Utils.fromBufferToUrlB64(buffer); + return Fido2Utils.fromBufferToB64(buffer) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=/g, ""); } static stringToBuffer(str: string): Uint8Array { - return Utils.fromUrlB64ToArray(str); + return Fido2Utils.fromB64ToArray(Fido2Utils.fromUrlB64ToB64(str)); } static bufferSourceToUint8Array(bufferSource: BufferSource) { @@ -23,4 +29,52 @@ export class Fido2Utils { private static isArrayBuffer(bufferSource: BufferSource): bufferSource is ArrayBuffer { return bufferSource instanceof ArrayBuffer || bufferSource.buffer === undefined; } + + static fromB64toUrlB64(b64Str: string) { + return b64Str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); + } + + static fromBufferToB64(buffer: ArrayBuffer): string { + if (buffer == null) { + return null; + } + + let binary = ""; + const bytes = new Uint8Array(buffer); + for (let i = 0; i < bytes.byteLength; i++) { + binary += String.fromCharCode(bytes[i]); + } + return globalThis.btoa(binary); + } + + static fromB64ToArray(str: string): Uint8Array { + if (str == null) { + return null; + } + + const binaryString = globalThis.atob(str); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } + + static fromUrlB64ToB64(urlB64Str: string): string { + let output = urlB64Str.replace(/-/g, "+").replace(/_/g, "/"); + switch (output.length % 4) { + case 0: + break; + case 2: + output += "=="; + break; + case 3: + output += "="; + break; + default: + throw new Error("Illegal base64url string!"); + } + + return output; + } } From d5f503a0d6b855442ce55b9c042357393d7394bf Mon Sep 17 00:00:00 2001 From: Will Martin Date: Thu, 18 Apr 2024 13:23:35 -0400 Subject: [PATCH 31/86] [CL-18] toast component and service (#6490) Update toast styles and new service to CL. --- apps/browser/src/_locales/en/messages.json | 3 + .../foreground-platform-utils.service.ts | 23 +--- apps/browser/src/popup/app.component.ts | 17 ++- apps/browser/src/popup/app.module.ts | 5 +- apps/browser/src/popup/scss/plugins.scss | 98 -------------- apps/browser/src/popup/scss/popup.scss | 1 - .../src/popup/services/services.module.ts | 16 +-- apps/desktop/src/app/app.component.ts | 38 +----- apps/desktop/src/locales/en/messages.json | 3 + apps/desktop/src/scss/plugins.scss | 95 -------------- apps/desktop/src/scss/styles.scss | 1 - apps/web/src/app/app.component.ts | 39 +----- apps/web/src/app/shared/shared.module.ts | 3 - apps/web/src/locales/en/messages.json | 3 + apps/web/src/scss/styles.scss | 3 - apps/web/src/scss/toasts.scss | 117 ----------------- .../src/components/toastr.component.ts | 98 -------------- libs/angular/src/jslib.module.ts | 7 +- .../abstractions/platform-utils.service.ts | 5 + libs/components/src/index.ts | 1 + libs/components/src/toast/index.ts | 2 + .../components/src/toast/toast.component.html | 24 ++++ libs/components/src/toast/toast.component.ts | 66 ++++++++++ libs/components/src/toast/toast.module.ts | 39 ++++++ libs/components/src/toast/toast.service.ts | 57 ++++++++ libs/components/src/toast/toast.spec.ts | 16 +++ libs/components/src/toast/toast.stories.ts | 124 ++++++++++++++++++ libs/components/src/toast/toast.tokens.css | 4 + libs/components/src/toast/toastr.component.ts | 26 ++++ libs/components/src/toast/toastr.css | 23 ++++ libs/components/src/toast/utils.ts | 14 ++ libs/components/src/tw-theme.css | 3 + 32 files changed, 440 insertions(+), 534 deletions(-) delete mode 100644 apps/browser/src/popup/scss/plugins.scss delete mode 100644 apps/desktop/src/scss/plugins.scss delete mode 100644 apps/web/src/scss/toasts.scss delete mode 100644 libs/angular/src/components/toastr.component.ts create mode 100644 libs/components/src/toast/index.ts create mode 100644 libs/components/src/toast/toast.component.html create mode 100644 libs/components/src/toast/toast.component.ts create mode 100644 libs/components/src/toast/toast.module.ts create mode 100644 libs/components/src/toast/toast.service.ts create mode 100644 libs/components/src/toast/toast.spec.ts create mode 100644 libs/components/src/toast/toast.stories.ts create mode 100644 libs/components/src/toast/toast.tokens.css create mode 100644 libs/components/src/toast/toastr.component.ts create mode 100644 libs/components/src/toast/toastr.css create mode 100644 libs/components/src/toast/utils.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 36e3ce65a8..8c81088fc5 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3000,6 +3000,9 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, diff --git a/apps/browser/src/platform/services/platform-utils/foreground-platform-utils.service.ts b/apps/browser/src/platform/services/platform-utils/foreground-platform-utils.service.ts index 8cf1a8d3e4..24aa45d5c3 100644 --- a/apps/browser/src/platform/services/platform-utils/foreground-platform-utils.service.ts +++ b/apps/browser/src/platform/services/platform-utils/foreground-platform-utils.service.ts @@ -1,13 +1,10 @@ -import { SecurityContext } from "@angular/core"; -import { DomSanitizer } from "@angular/platform-browser"; -import { ToastrService } from "ngx-toastr"; +import { ToastService } from "@bitwarden/components"; import { BrowserPlatformUtilsService } from "./browser-platform-utils.service"; export class ForegroundPlatformUtilsService extends BrowserPlatformUtilsService { constructor( - private sanitizer: DomSanitizer, - private toastrService: ToastrService, + private toastService: ToastService, clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void, biometricCallback: () => Promise, win: Window & typeof globalThis, @@ -21,20 +18,6 @@ export class ForegroundPlatformUtilsService extends BrowserPlatformUtilsService text: string | string[], options?: any, ): void { - if (typeof text === "string") { - // Already in the correct format - } else if (text.length === 1) { - text = text[0]; - } else { - let message = ""; - text.forEach( - (t: string) => - (message += "

" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "

"), - ); - text = message; - options.enableHtml = true; - } - this.toastrService.show(text, title, options, "toast-" + type); - // noop + this.toastService._showToast({ type, title, text, options }); } } diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index c224e652f6..2aba93ac95 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -1,19 +1,18 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { NavigationEnd, Router, RouterOutlet } from "@angular/router"; -import { ToastrService } from "ngx-toastr"; import { filter, concatMap, Subject, takeUntil, firstValueFrom, map } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; +import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components"; import { BrowserApi } from "../platform/browser/browser-api"; import { ZonedMessageListenerService } from "../platform/browser/zoned-message-listener.service"; import { BrowserStateService } from "../platform/services/abstractions/browser-state.service"; -import { ForegroundPlatformUtilsService } from "../platform/services/platform-utils/foreground-platform-utils.service"; import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service"; import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; @@ -35,7 +34,6 @@ export class AppComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); constructor( - private toastrService: ToastrService, private broadcasterService: BroadcasterService, private authService: AuthService, private i18nService: I18nService, @@ -46,9 +44,10 @@ export class AppComponent implements OnInit, OnDestroy { private cipherService: CipherService, private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, - private platformUtilsService: ForegroundPlatformUtilsService, + private platformUtilsService: PlatformUtilsService, private dialogService: DialogService, private browserMessagingApi: ZonedMessageListenerService, + private toastService: ToastService, ) {} async ngOnInit() { @@ -83,10 +82,10 @@ export class AppComponent implements OnInit, OnDestroy { if (msg.command === "doneLoggingOut") { this.authService.logOut(async () => { if (msg.expired) { - this.showToast({ - type: "warning", + this.toastService.showToast({ + variant: "warning", title: this.i18nService.t("loggedOut"), - text: this.i18nService.t("loginExpired"), + message: this.i18nService.t("loginExpired"), }); } @@ -116,7 +115,7 @@ export class AppComponent implements OnInit, OnDestroy { // eslint-disable-next-line @typescript-eslint/no-floating-promises this.showNativeMessagingFingerprintDialog(msg); } else if (msg.command === "showToast") { - this.showToast(msg); + this.toastService._showToast(msg); } else if (msg.command === "reloadProcess") { const forceWindowReload = this.platformUtilsService.isSafari() || diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index d179868448..5718542b01 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -11,11 +11,10 @@ import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component"; -import { BitwardenToastModule } from "@bitwarden/angular/components/toastr.component"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe"; import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; -import { AvatarModule, ButtonModule } from "@bitwarden/components"; +import { AvatarModule, ButtonModule, ToastModule } from "@bitwarden/components"; import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; @@ -87,7 +86,7 @@ import "../platform/popup/locales"; imports: [ A11yModule, AppRoutingModule, - BitwardenToastModule.forRoot({ + ToastModule.forRoot({ maxOpened: 2, autoDismiss: true, closeButton: true, diff --git a/apps/browser/src/popup/scss/plugins.scss b/apps/browser/src/popup/scss/plugins.scss deleted file mode 100644 index e1e386d62d..0000000000 --- a/apps/browser/src/popup/scss/plugins.scss +++ /dev/null @@ -1,98 +0,0 @@ -@import "~ngx-toastr/toastr"; - -@import "variables.scss"; -@import "buttons.scss"; - -// Toaster - -.toast-container { - .toast-close-button { - @include themify($themes) { - color: themed("toastTextColor"); - } - font-size: 18px; - margin-right: 4px; - } - - .ngx-toastr { - @include themify($themes) { - color: themed("toastTextColor"); - } - align-items: center; - background-image: none !important; - border-radius: $border-radius; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.35); - display: flex; - padding: 15px; - - .toast-close-button { - position: absolute; - right: 5px; - top: 0; - } - - &:hover { - box-shadow: 0 0 10px rgba(0, 0, 0, 0.6); - } - - .icon i::before { - float: left; - font-style: normal; - font-family: $icomoon-font-family; - font-size: 25px; - line-height: 20px; - padding-right: 15px; - } - - .toast-message { - p { - margin-bottom: 0.5rem; - - &:last-child { - margin-bottom: 0; - } - } - } - - &.toast-danger, - &.toast-error { - @include themify($themes) { - background-color: themed("dangerColor"); - } - - .icon i::before { - content: map_get($icons, "error"); - } - } - - &.toast-warning { - @include themify($themes) { - background-color: themed("warningColor"); - } - - .icon i::before { - content: map_get($icons, "exclamation-triangle"); - } - } - - &.toast-info { - @include themify($themes) { - background-color: themed("infoColor"); - } - - .icon i:before { - content: map_get($icons, "info-circle"); - } - } - - &.toast-success { - @include themify($themes) { - background-color: themed("successColor"); - } - - .icon i:before { - content: map_get($icons, "check"); - } - } - } -} diff --git a/apps/browser/src/popup/scss/popup.scss b/apps/browser/src/popup/scss/popup.scss index 0d7e428138..850ef96c64 100644 --- a/apps/browser/src/popup/scss/popup.scss +++ b/apps/browser/src/popup/scss/popup.scss @@ -8,7 +8,6 @@ @import "buttons.scss"; @import "misc.scss"; @import "modal.scss"; -@import "plugins.scss"; @import "environment.scss"; @import "pages.scss"; @import "@angular/cdk/overlay-prebuilt.css"; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 4906198047..f3be8490c1 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -1,7 +1,5 @@ import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core"; -import { DomSanitizer } from "@angular/platform-browser"; import { Router } from "@angular/router"; -import { ToastrService } from "ngx-toastr"; import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards"; import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service"; @@ -83,7 +81,7 @@ import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vau import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { UnauthGuardService } from "../../auth/popup/services"; import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service"; @@ -259,15 +257,9 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: PlatformUtilsService, - useExisting: ForegroundPlatformUtilsService, - }), - safeProvider({ - provide: ForegroundPlatformUtilsService, - useClass: ForegroundPlatformUtilsService, - useFactory: (sanitizer: DomSanitizer, toastrService: ToastrService) => { + useFactory: (toastService: ToastService) => { return new ForegroundPlatformUtilsService( - sanitizer, - toastrService, + toastService, (clipboardValue: string, clearMs: number) => { void BrowserApi.sendMessage("clearClipboard", { clipboardValue, clearMs }); }, @@ -284,7 +276,7 @@ const safeProviders: SafeProvider[] = [ window, ); }, - deps: [DomSanitizer, ToastrService], + deps: [ToastService], }), safeProvider({ provide: PasswordGenerationServiceAbstraction, diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index b2b44e6b21..ad99a3a447 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -3,14 +3,11 @@ import { NgZone, OnDestroy, OnInit, - SecurityContext, Type, ViewChild, ViewContainerRef, } from "@angular/core"; -import { DomSanitizer } from "@angular/platform-browser"; import { Router } from "@angular/router"; -import { IndividualConfig, ToastrService } from "ngx-toastr"; import { firstValueFrom, Subject, takeUntil } from "rxjs"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; @@ -49,7 +46,7 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { DeleteAccountComponent } from "../auth/delete-account.component"; import { LoginApprovalComponent } from "../auth/login/login-approval.component"; @@ -129,9 +126,8 @@ export class AppComponent implements OnInit, OnDestroy { private cipherService: CipherService, private authService: AuthService, private router: Router, - private toastrService: ToastrService, + private toastService: ToastService, private i18nService: I18nService, - private sanitizer: DomSanitizer, private ngZone: NgZone, private vaultTimeoutService: VaultTimeoutService, private vaultTimeoutSettingsService: VaultTimeoutSettingsService, @@ -294,7 +290,7 @@ export class AppComponent implements OnInit, OnDestroy { ); break; case "showToast": - this.showToast(message); + this.toastService._showToast(message); break; case "copiedToClipboard": if (!message.clearing) { @@ -674,34 +670,6 @@ export class AppComponent implements OnInit, OnDestroy { }); } - private showToast(msg: any) { - let message = ""; - - const options: Partial = {}; - - if (typeof msg.text === "string") { - message = msg.text; - } else if (msg.text.length === 1) { - message = msg.text[0]; - } else { - msg.text.forEach( - (t: string) => - (message += "

" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "

"), - ); - options.enableHtml = true; - } - if (msg.options != null) { - if (msg.options.trustedHtml === true) { - options.enableHtml = true; - } - if (msg.options.timeout != null && msg.options.timeout > 0) { - options.timeOut = msg.options.timeout; - } - } - - this.toastrService.show(message, msg.title, options, "toast-" + msg.type); - } - private routeToVault(action: string, cipherType: CipherType) { if (!this.router.url.includes("vault")) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 3d2b40ac62..ff9cbc97cc 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/scss/plugins.scss b/apps/desktop/src/scss/plugins.scss deleted file mode 100644 index c156456809..0000000000 --- a/apps/desktop/src/scss/plugins.scss +++ /dev/null @@ -1,95 +0,0 @@ -@import "~ngx-toastr/toastr"; - -@import "variables.scss"; - -.toast-container { - .toast-close-button { - @include themify($themes) { - color: themed("toastTextColor"); - } - font-size: 18px; - margin-right: 4px; - } - - .ngx-toastr { - @include themify($themes) { - color: themed("toastTextColor"); - } - align-items: center; - background-image: none !important; - border-radius: $border-radius; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.35); - display: flex; - padding: 15px; - - .toast-close-button { - position: absolute; - right: 5px; - top: 0; - } - - &:hover { - box-shadow: 0 0 10px rgba(0, 0, 0, 0.6); - } - - .icon i::before { - float: left; - font-style: normal; - font-family: $icomoon-font-family; - font-size: 25px; - line-height: 20px; - padding-right: 15px; - } - - .toast-message { - p { - margin-bottom: 0.5rem; - - &:last-child { - margin-bottom: 0; - } - } - } - - &.toast-danger, - &.toast-error { - @include themify($themes) { - background-color: themed("dangerColor"); - } - - .icon i::before { - content: map_get($icons, "error"); - } - } - - &.toast-warning { - @include themify($themes) { - background-color: themed("warningColor"); - } - - .icon i::before { - content: map_get($icons, "exclamation-triangle"); - } - } - - &.toast-info { - @include themify($themes) { - background-color: themed("infoColor"); - } - - .icon i:before { - content: map_get($icons, "info-circle"); - } - } - - &.toast-success { - @include themify($themes) { - background-color: themed("successColor"); - } - - .icon i:before { - content: map_get($icons, "check"); - } - } - } -} diff --git a/apps/desktop/src/scss/styles.scss b/apps/desktop/src/scss/styles.scss index 033a0f8b67..54c1385dcf 100644 --- a/apps/desktop/src/scss/styles.scss +++ b/apps/desktop/src/scss/styles.scss @@ -11,7 +11,6 @@ @import "buttons.scss"; @import "misc.scss"; @import "modal.scss"; -@import "plugins.scss"; @import "environment.scss"; @import "header.scss"; @import "left-nav.scss"; diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 7a3b34969a..1da2d94c15 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -1,9 +1,7 @@ import { DOCUMENT } from "@angular/common"; -import { Component, Inject, NgZone, OnDestroy, OnInit, SecurityContext } from "@angular/core"; -import { DomSanitizer } from "@angular/platform-browser"; +import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core"; import { NavigationEnd, Router } from "@angular/router"; import * as jq from "jquery"; -import { IndividualConfig, ToastrService } from "ngx-toastr"; import { Subject, switchMap, takeUntil, timer } from "rxjs"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; @@ -29,7 +27,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { PolicyListService } from "./admin-console/core/policy-list.service"; import { @@ -68,14 +66,13 @@ export class AppComponent implements OnDestroy, OnInit { private cipherService: CipherService, private authService: AuthService, private router: Router, - private toastrService: ToastrService, + private toastService: ToastService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private ngZone: NgZone, private vaultTimeoutService: VaultTimeoutService, private cryptoService: CryptoService, private collectionService: CollectionService, - private sanitizer: DomSanitizer, private searchService: SearchService, private notificationsService: NotificationsService, private stateService: StateService, @@ -209,7 +206,7 @@ export class AppComponent implements OnDestroy, OnInit { break; } case "showToast": - this.showToast(message); + this.toastService._showToast(message); break; case "convertAccountToKeyConnector": // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. @@ -327,34 +324,6 @@ export class AppComponent implements OnDestroy, OnInit { }, IdleTimeout); } - private showToast(msg: any) { - let message = ""; - - const options: Partial = {}; - - if (typeof msg.text === "string") { - message = msg.text; - } else if (msg.text.length === 1) { - message = msg.text[0]; - } else { - msg.text.forEach( - (t: string) => - (message += "

" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "

"), - ); - options.enableHtml = true; - } - if (msg.options != null) { - if (msg.options.trustedHtml === true) { - options.enableHtml = true; - } - if (msg.options.timeout != null && msg.options.timeout > 0) { - options.timeOut = msg.options.timeout; - } - } - - this.toastrService.show(message, msg.title, options, "toast-" + msg.type); - } - private idleStateChanged() { if (this.isIdle) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. diff --git a/apps/web/src/app/shared/shared.module.ts b/apps/web/src/app/shared/shared.module.ts index bc775f07e2..1b04583a39 100644 --- a/apps/web/src/app/shared/shared.module.ts +++ b/apps/web/src/app/shared/shared.module.ts @@ -4,7 +4,6 @@ import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { RouterModule } from "@angular/router"; import { InfiniteScrollModule } from "ngx-infinite-scroll"; -import { ToastrModule } from "ngx-toastr"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { @@ -52,7 +51,6 @@ import "./locales"; ReactiveFormsModule, InfiniteScrollModule, RouterModule, - ToastrModule, JslibModule, // Component library modules @@ -90,7 +88,6 @@ import "./locales"; ReactiveFormsModule, InfiniteScrollModule, RouterModule, - ToastrModule, JslibModule, // Component library diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 7632392c23..f14574508c 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, diff --git a/apps/web/src/scss/styles.scss b/apps/web/src/scss/styles.scss index 98b3512ba5..8fbea200a9 100644 --- a/apps/web/src/scss/styles.scss +++ b/apps/web/src/scss/styles.scss @@ -43,8 +43,6 @@ @import "~bootstrap/scss/_utilities"; @import "~bootstrap/scss/_print"; -@import "~ngx-toastr/toastr"; - @import "./base"; @import "./buttons"; @import "./callouts"; @@ -54,5 +52,4 @@ @import "./pages"; @import "./plugins"; @import "./tables"; -@import "./toasts"; @import "./vault-filters"; diff --git a/apps/web/src/scss/toasts.scss b/apps/web/src/scss/toasts.scss deleted file mode 100644 index 6685de6449..0000000000 --- a/apps/web/src/scss/toasts.scss +++ /dev/null @@ -1,117 +0,0 @@ -.toast-container { - .toast-close-button { - font-size: 18px; - margin-right: 4px; - } - - .ngx-toastr { - align-items: center; - background-image: none !important; - border-radius: $border-radius; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.35); - display: flex; - padding: 15px; - - .toast-close-button { - position: absolute; - right: 5px; - top: 0; - } - - &:hover { - box-shadow: 0 0 10px rgba(0, 0, 0, 0.6); - } - - .icon i::before { - float: left; - font-style: normal; - font-family: $icomoon-font-family; - font-size: 25px; - line-height: 20px; - padding-right: 15px; - } - - .toast-message { - p { - margin-bottom: 0.5rem; - - &:last-child { - margin-bottom: 0; - } - } - } - - &.toast-danger, - &.toast-error { - @include themify($themes) { - background-color: themed("danger"); - } - - &, - &:before, - & .toast-close-button { - @include themify($themes) { - color: themed("textDangerColor") !important; - } - } - - .icon i::before { - content: map_get($icons, "error"); - } - } - - &.toast-warning { - @include themify($themes) { - background-color: themed("warning"); - } - - &, - &:before, - & .toast-close-button { - @include themify($themes) { - color: themed("textWarningColor") !important; - } - } - - .icon i::before { - content: map_get($icons, "exclamation-triangle"); - } - } - - &.toast-info { - @include themify($themes) { - background-color: themed("info"); - } - - &, - &:before, - & .toast-close-button { - @include themify($themes) { - color: themed("textInfoColor") !important; - } - } - - .icon i:before { - content: map_get($icons, "info-circle"); - } - } - - &.toast-success { - @include themify($themes) { - background-color: themed("success"); - } - - &, - &:before, - & .toast-close-button { - @include themify($themes) { - color: themed("textSuccessColor") !important; - } - } - - .icon i:before { - content: map_get($icons, "check"); - } - } - } -} diff --git a/libs/angular/src/components/toastr.component.ts b/libs/angular/src/components/toastr.component.ts deleted file mode 100644 index bfe20ed866..0000000000 --- a/libs/angular/src/components/toastr.component.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { animate, state, style, transition, trigger } from "@angular/animations"; -import { CommonModule } from "@angular/common"; -import { Component, ModuleWithProviders, NgModule } from "@angular/core"; -import { - DefaultNoComponentGlobalConfig, - GlobalConfig, - Toast as BaseToast, - ToastPackage, - ToastrService, - TOAST_CONFIG, -} from "ngx-toastr"; - -@Component({ - selector: "[toast-component2]", - template: ` - -
- -
-
-
- {{ title }} [{{ duplicatesCount + 1 }}] -
-
-
- {{ message }} -
-
-
-
-
- `, - animations: [ - trigger("flyInOut", [ - state("inactive", style({ opacity: 0 })), - state("active", style({ opacity: 1 })), - state("removed", style({ opacity: 0 })), - transition("inactive => active", animate("{{ easeTime }}ms {{ easing }}")), - transition("active => removed", animate("{{ easeTime }}ms {{ easing }}")), - ]), - ], - preserveWhitespaces: false, -}) -export class BitwardenToast extends BaseToast { - constructor( - protected toastrService: ToastrService, - public toastPackage: ToastPackage, - ) { - super(toastrService, toastPackage); - } -} - -export const BitwardenToastGlobalConfig: GlobalConfig = { - ...DefaultNoComponentGlobalConfig, - toastComponent: BitwardenToast, -}; - -@NgModule({ - imports: [CommonModule], - declarations: [BitwardenToast], - exports: [BitwardenToast], -}) -export class BitwardenToastModule { - static forRoot(config: Partial = {}): ModuleWithProviders { - return { - ngModule: BitwardenToastModule, - providers: [ - { - provide: TOAST_CONFIG, - useValue: { - default: BitwardenToastGlobalConfig, - config: config, - }, - }, - ], - }; - } -} diff --git a/libs/angular/src/jslib.module.ts b/libs/angular/src/jslib.module.ts index 64fb44e3b8..5f1bf796aa 100644 --- a/libs/angular/src/jslib.module.ts +++ b/libs/angular/src/jslib.module.ts @@ -2,10 +2,9 @@ import { CommonModule, DatePipe } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { AutofocusDirective } from "@bitwarden/components"; +import { AutofocusDirective, ToastModule } from "@bitwarden/components"; import { CalloutComponent } from "./components/callout.component"; -import { BitwardenToastModule } from "./components/toastr.component"; import { A11yInvalidDirective } from "./directives/a11y-invalid.directive"; import { A11yTitleDirective } from "./directives/a11y-title.directive"; import { ApiActionDirective } from "./directives/api-action.directive"; @@ -34,7 +33,7 @@ import { IconComponent } from "./vault/components/icon.component"; @NgModule({ imports: [ - BitwardenToastModule.forRoot({ + ToastModule.forRoot({ maxOpened: 5, autoDismiss: true, closeButton: true, @@ -77,7 +76,7 @@ import { IconComponent } from "./vault/components/icon.component"; A11yTitleDirective, ApiActionDirective, AutofocusDirective, - BitwardenToastModule, + ToastModule, BoxRowDirective, CalloutComponent, CopyTextDirective, diff --git a/libs/common/src/platform/abstractions/platform-utils.service.ts b/libs/common/src/platform/abstractions/platform-utils.service.ts index d518a17f7b..f2dff46c78 100644 --- a/libs/common/src/platform/abstractions/platform-utils.service.ts +++ b/libs/common/src/platform/abstractions/platform-utils.service.ts @@ -28,6 +28,11 @@ export abstract class PlatformUtilsService { abstract getApplicationVersionNumber(): Promise; abstract supportsWebAuthn(win: Window): boolean; abstract supportsDuo(): boolean; + /** + * @deprecated use `@bitwarden/components/ToastService.showToast` instead + * + * Jira: [CL-213](https://bitwarden.atlassian.net/browse/CL-213) + */ abstract showToast( type: "error" | "success" | "warning" | "info", title: string, diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index 139e69ebb6..527d5f3615 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -29,6 +29,7 @@ export * from "./section"; export * from "./select"; export * from "./table"; export * from "./tabs"; +export * from "./toast"; export * from "./toggle-group"; export * from "./typography"; export * from "./utils/i18n-mock.service"; diff --git a/libs/components/src/toast/index.ts b/libs/components/src/toast/index.ts new file mode 100644 index 0000000000..f0b5540219 --- /dev/null +++ b/libs/components/src/toast/index.ts @@ -0,0 +1,2 @@ +export * from "./toast.module"; +export * from "./toast.service"; diff --git a/libs/components/src/toast/toast.component.html b/libs/components/src/toast/toast.component.html new file mode 100644 index 0000000000..f301995d0a --- /dev/null +++ b/libs/components/src/toast/toast.component.html @@ -0,0 +1,24 @@ +
+
+ +
+ {{ variant | i18n }} +

{{ title }}

+

+ {{ m }} +

+
+ +
+
+
diff --git a/libs/components/src/toast/toast.component.ts b/libs/components/src/toast/toast.component.ts new file mode 100644 index 0000000000..4a31e00586 --- /dev/null +++ b/libs/components/src/toast/toast.component.ts @@ -0,0 +1,66 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; + +import { IconButtonModule } from "../icon-button"; +import { SharedModule } from "../shared"; + +export type ToastVariant = "success" | "error" | "info" | "warning"; + +const variants: Record = { + success: { + icon: "bwi-check", + bgColor: "tw-bg-success-600", + }, + error: { + icon: "bwi-error", + bgColor: "tw-bg-danger-600", + }, + info: { + icon: "bwi-info-circle", + bgColor: "tw-bg-info-600", + }, + warning: { + icon: "bwi-exclamation-triangle", + bgColor: "tw-bg-warning-600", + }, +}; + +@Component({ + selector: "bit-toast", + templateUrl: "toast.component.html", + standalone: true, + imports: [SharedModule, IconButtonModule], +}) +export class ToastComponent { + @Input() variant: ToastVariant = "info"; + + /** + * The message to display + * + * Pass an array to render multiple paragraphs. + **/ + @Input({ required: true }) + message: string | string[]; + + /** An optional title to display over the message. */ + @Input() title: string; + + /** + * The percent width of the progress bar, from 0-100 + **/ + @Input() progressWidth = 0; + + /** Emits when the user presses the close button */ + @Output() onClose = new EventEmitter(); + + protected get iconClass(): string { + return variants[this.variant].icon; + } + + protected get bgColor(): string { + return variants[this.variant].bgColor; + } + + protected get messageArray(): string[] { + return Array.isArray(this.message) ? this.message : [this.message]; + } +} diff --git a/libs/components/src/toast/toast.module.ts b/libs/components/src/toast/toast.module.ts new file mode 100644 index 0000000000..bf39a0be9a --- /dev/null +++ b/libs/components/src/toast/toast.module.ts @@ -0,0 +1,39 @@ +import { CommonModule } from "@angular/common"; +import { ModuleWithProviders, NgModule } from "@angular/core"; +import { DefaultNoComponentGlobalConfig, GlobalConfig, TOAST_CONFIG } from "ngx-toastr"; + +import { ToastComponent } from "./toast.component"; +import { BitwardenToastrComponent } from "./toastr.component"; + +@NgModule({ + imports: [CommonModule, ToastComponent], + declarations: [BitwardenToastrComponent], + exports: [BitwardenToastrComponent], +}) +export class ToastModule { + static forRoot(config: Partial = {}): ModuleWithProviders { + return { + ngModule: ToastModule, + providers: [ + { + provide: TOAST_CONFIG, + useValue: { + default: BitwardenToastrGlobalConfig, + config: config, + }, + }, + ], + }; + } +} + +export const BitwardenToastrGlobalConfig: GlobalConfig = { + ...DefaultNoComponentGlobalConfig, + toastComponent: BitwardenToastrComponent, + tapToDismiss: false, + timeOut: 5000, + extendedTimeOut: 2000, + maxOpened: 5, + autoDismiss: true, + progressBar: true, +}; diff --git a/libs/components/src/toast/toast.service.ts b/libs/components/src/toast/toast.service.ts new file mode 100644 index 0000000000..8bbff02c41 --- /dev/null +++ b/libs/components/src/toast/toast.service.ts @@ -0,0 +1,57 @@ +import { Injectable } from "@angular/core"; +import { IndividualConfig, ToastrService } from "ngx-toastr"; + +import type { ToastComponent } from "./toast.component"; +import { calculateToastTimeout } from "./utils"; + +export type ToastOptions = { + /** + * The duration the toast will persist in milliseconds + **/ + timeout?: number; +} & Pick; + +/** + * Presents toast notifications + **/ +@Injectable({ providedIn: "root" }) +export class ToastService { + constructor(private toastrService: ToastrService) {} + + showToast(options: ToastOptions) { + const toastrConfig: Partial = { + payload: { + message: options.message, + variant: options.variant, + title: options.title, + }, + timeOut: + options.timeout != null && options.timeout > 0 + ? options.timeout + : calculateToastTimeout(options.message), + }; + + this.toastrService.show(null, options.title, toastrConfig); + } + + /** + * @deprecated use `showToast` instead + * + * Converts options object from PlatformUtilsService + **/ + _showToast(options: { + type: "error" | "success" | "warning" | "info"; + title: string; + text: string | string[]; + options?: { + timeout?: number; + }; + }) { + this.showToast({ + message: options.text, + variant: options.type, + title: options.title, + timeout: options.options?.timeout, + }); + } +} diff --git a/libs/components/src/toast/toast.spec.ts b/libs/components/src/toast/toast.spec.ts new file mode 100644 index 0000000000..92d8071dc5 --- /dev/null +++ b/libs/components/src/toast/toast.spec.ts @@ -0,0 +1,16 @@ +import { calculateToastTimeout } from "./utils"; + +describe("Toast default timer", () => { + it("should have a minimum of 5000ms", () => { + expect(calculateToastTimeout("")).toBe(5000); + expect(calculateToastTimeout([""])).toBe(5000); + expect(calculateToastTimeout(" ")).toBe(5000); + }); + + it("should return an extra second for each 120 words", () => { + expect(calculateToastTimeout("foo ".repeat(119))).toBe(5000); + expect(calculateToastTimeout("foo ".repeat(120))).toBe(6000); + expect(calculateToastTimeout("foo ".repeat(240))).toBe(7000); + expect(calculateToastTimeout(["foo ".repeat(120), " \n foo ".repeat(120)])).toBe(7000); + }); +}); diff --git a/libs/components/src/toast/toast.stories.ts b/libs/components/src/toast/toast.stories.ts new file mode 100644 index 0000000000..d209453d85 --- /dev/null +++ b/libs/components/src/toast/toast.stories.ts @@ -0,0 +1,124 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { action } from "@storybook/addon-actions"; +import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { ButtonModule } from "../button"; +import { I18nMockService } from "../utils/i18n-mock.service"; + +import { ToastComponent } from "./toast.component"; +import { BitwardenToastrGlobalConfig, ToastModule } from "./toast.module"; +import { ToastOptions, ToastService } from "./toast.service"; + +const toastServiceExampleTemplate = ` + +`; +@Component({ + selector: "toast-service-example", + template: toastServiceExampleTemplate, +}) +export class ToastServiceExampleComponent { + @Input() + toastOptions: ToastOptions; + + constructor(protected toastService: ToastService) {} +} + +export default { + title: "Component Library/Toast", + component: ToastComponent, + + decorators: [ + moduleMetadata({ + imports: [CommonModule, BrowserAnimationsModule, ButtonModule], + declarations: [ToastServiceExampleComponent], + }), + applicationConfig({ + providers: [ + ToastModule.forRoot().providers, + { + provide: I18nService, + useFactory: () => { + return new I18nMockService({ + close: "Close", + success: "Success", + error: "Error", + warning: "Warning", + }); + }, + }, + ], + }), + ], + args: { + onClose: action("emit onClose"), + variant: "info", + progressWidth: 50, + title: "", + message: "Hello Bitwarden!", + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + }, + }, +} as Meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ({ + props: args, + template: ` +
+ + + + +
+ `, + }), +}; + +/** + * Avoid using long messages in toasts. + */ +export const LongContent: Story = { + ...Default, + args: { + title: "Foo", + message: [ + "Lorem ipsum dolor sit amet, consectetur adipisci", + "Lorem ipsum dolor sit amet, consectetur adipisci", + ], + }, +}; + +export const Service: Story = { + render: (args) => ({ + props: { + toastOptions: args, + }, + template: ` + + `, + }), + args: { + title: "", + message: "Hello Bitwarden!", + variant: "error", + timeout: BitwardenToastrGlobalConfig.timeOut, + } as ToastOptions, + parameters: { + chromatic: { disableSnapshot: true }, + docs: { + source: { + code: toastServiceExampleTemplate, + }, + }, + }, +}; diff --git a/libs/components/src/toast/toast.tokens.css b/libs/components/src/toast/toast.tokens.css new file mode 100644 index 0000000000..2ff9e99ae5 --- /dev/null +++ b/libs/components/src/toast/toast.tokens.css @@ -0,0 +1,4 @@ +:root { + --bit-toast-width: 19rem; + --bit-toast-width-full: 96%; +} diff --git a/libs/components/src/toast/toastr.component.ts b/libs/components/src/toast/toastr.component.ts new file mode 100644 index 0000000000..70085dfc47 --- /dev/null +++ b/libs/components/src/toast/toastr.component.ts @@ -0,0 +1,26 @@ +import { animate, state, style, transition, trigger } from "@angular/animations"; +import { Component } from "@angular/core"; +import { Toast as BaseToastrComponent } from "ngx-toastr"; + +@Component({ + template: ` + + `, + animations: [ + trigger("flyInOut", [ + state("inactive", style({ opacity: 0 })), + state("active", style({ opacity: 1 })), + state("removed", style({ opacity: 0 })), + transition("inactive => active", animate("{{ easeTime }}ms {{ easing }}")), + transition("active => removed", animate("{{ easeTime }}ms {{ easing }}")), + ]), + ], + preserveWhitespaces: false, +}) +export class BitwardenToastrComponent extends BaseToastrComponent {} diff --git a/libs/components/src/toast/toastr.css b/libs/components/src/toast/toastr.css new file mode 100644 index 0000000000..fabf8caf10 --- /dev/null +++ b/libs/components/src/toast/toastr.css @@ -0,0 +1,23 @@ +@import "~ngx-toastr/toastr"; +@import "./toast.tokens.css"; + +/* Override all default styles from `ngx-toaster` */ +.toast-container .ngx-toastr { + all: unset; + display: block; + width: var(--bit-toast-width); + + /* Needed to make hover states work in Electron, since the toast appears in the draggable region. */ + -webkit-app-region: no-drag; +} + +/* Disable hover styles */ +.toast-container .ngx-toastr:hover { + box-shadow: none; +} + +.toast-container.toast-bottom-full-width .ngx-toastr { + width: var(--bit-toast-width-full); + margin-left: auto; + margin-right: auto; +} diff --git a/libs/components/src/toast/utils.ts b/libs/components/src/toast/utils.ts new file mode 100644 index 0000000000..4c8323f396 --- /dev/null +++ b/libs/components/src/toast/utils.ts @@ -0,0 +1,14 @@ +/** + * Given a toast message, calculate the ideal timeout length following: + * a minimum of 5 seconds + 1 extra second per 120 additional words + * + * @param message the toast message to be displayed + * @returns the timeout length in milliseconds + */ +export const calculateToastTimeout = (message: string | string[]): number => { + const paragraphs = Array.isArray(message) ? message : [message]; + const numWords = paragraphs + .map((paragraph) => paragraph.split(/\s+/).filter((word) => word !== "")) + .flat().length; + return 5000 + Math.floor(numWords / 120) * 1000; +}; diff --git a/libs/components/src/tw-theme.css b/libs/components/src/tw-theme.css index 0087af28ae..72e8e1e5e8 100644 --- a/libs/components/src/tw-theme.css +++ b/libs/components/src/tw-theme.css @@ -171,6 +171,9 @@ @import "./popover/popover.component.css"; @import "./search/search.component.css"; +@import "./toast/toast.tokens.css"; +@import "./toast/toastr.css"; + /** * tw-break-words does not work with table cells: * https://github.com/tailwindlabs/tailwindcss/issues/835 From ce75f7b5659aeafe036edf4e27ee8ba1c7608913 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Thu, 18 Apr 2024 14:06:31 -0400 Subject: [PATCH 32/86] Vault/pm-7580/resolve-cipher-update-race (#8806) * Resolve updated values from updates Uses the now returned updated values from cipher service to guarantee-return the updated cipher for CLI edits * Use updated cipher for creation * Use updated cipher for editing collections * Await async methods Cipher data more closely approximates server responses. TODO: this should really use actual response types --- apps/cli/src/commands/edit.command.ts | 6 +-- apps/cli/src/vault/create.command.ts | 6 +-- .../src/vault/abstractions/cipher.service.ts | 37 ++++++++++++++-- .../src/vault/services/cipher.service.spec.ts | 43 +++++++------------ .../src/vault/services/cipher.service.ts | 31 ++++++++----- 5 files changed, 73 insertions(+), 50 deletions(-) diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 3d4f9529ad..e64ff8b551 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -86,8 +86,7 @@ export class EditCommand { cipherView = CipherExport.toView(req, cipherView); const encCipher = await this.cipherService.encrypt(cipherView); try { - await this.cipherService.updateWithServer(encCipher); - const updatedCipher = await this.cipherService.get(cipher.id); + const updatedCipher = await this.cipherService.updateWithServer(encCipher); const decCipher = await updatedCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), ); @@ -111,8 +110,7 @@ export class EditCommand { cipher.collectionIds = req; try { - await this.cipherService.saveCollectionsWithServer(cipher); - const updatedCipher = await this.cipherService.get(cipher.id); + const updatedCipher = await this.cipherService.saveCollectionsWithServer(cipher); const decCipher = await updatedCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), ); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index b813227109..78ee04e73c 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -80,8 +80,7 @@ export class CreateCommand { private async createCipher(req: CipherExport) { const cipher = await this.cipherService.encrypt(CipherExport.toView(req)); try { - await this.cipherService.createWithServer(cipher); - const newCipher = await this.cipherService.get(cipher.id); + const newCipher = await this.cipherService.createWithServer(cipher); const decCipher = await newCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(newCipher), ); @@ -142,12 +141,11 @@ export class CreateCommand { } try { - await this.cipherService.saveAttachmentRawWithServer( + const updatedCipher = await this.cipherService.saveAttachmentRawWithServer( cipher, fileName, new Uint8Array(fileBuf).buffer, ); - const updatedCipher = await this.cipherService.get(cipher.id); const decCipher = await updatedCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), ); diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 501fd87665..22e2c54a59 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -47,8 +47,24 @@ export abstract class CipherService { updateLastUsedDate: (id: string) => Promise; updateLastLaunchedDate: (id: string) => Promise; saveNeverDomain: (domain: string) => Promise; - createWithServer: (cipher: Cipher, orgAdmin?: boolean) => Promise; - updateWithServer: (cipher: Cipher, orgAdmin?: boolean, isNotClone?: boolean) => Promise; + /** + * Create a cipher with the server + * + * @param cipher The cipher to create + * @param orgAdmin If true, the request is submitted as an organization admin request + * + * @returns A promise that resolves to the created cipher + */ + createWithServer: (cipher: Cipher, orgAdmin?: boolean) => Promise; + /** + * Update a cipher with the server + * @param cipher The cipher to update + * @param orgAdmin If true, the request is submitted as an organization admin request + * @param isNotClone If true, the cipher is not a clone and should be treated as a new cipher + * + * @returns A promise that resolves to the updated cipher + */ + updateWithServer: (cipher: Cipher, orgAdmin?: boolean, isNotClone?: boolean) => Promise; shareWithServer: ( cipher: CipherView, organizationId: string, @@ -70,7 +86,14 @@ export abstract class CipherService { data: ArrayBuffer, admin?: boolean, ) => Promise; - saveCollectionsWithServer: (cipher: Cipher) => Promise; + /** + * Save the collections for a cipher with the server + * + * @param cipher The cipher to save collections for + * + * @returns A promise that resolves when the collections have been saved + */ + saveCollectionsWithServer: (cipher: Cipher) => Promise; /** * Bulk update collections for many ciphers with the server * @param orgId @@ -84,7 +107,13 @@ export abstract class CipherService { collectionIds: CollectionId[], removeCollections: boolean, ) => Promise; - upsert: (cipher: CipherData | CipherData[]) => Promise; + /** + * Update the local store of CipherData with the provided data. Values are upserted into the existing store. + * + * @param cipher The cipher data to upsert. Can be a single CipherData object or an array of CipherData objects. + * @returns A promise that resolves to a record of updated cipher store, keyed by their cipher ID. Returns all ciphers, not just those updated + */ + upsert: (cipher: CipherData | CipherData[]) => Promise>; replace: (ciphers: { [id: string]: CipherData }) => Promise; clear: (userId: string) => Promise; moveManyWithServer: (ids: string[], folderId: string) => Promise; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 28c4bfc653..9b03753118 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -174,23 +174,20 @@ describe("Cipher Service", () => { it("should call apiService.postCipherAdmin when orgAdmin param is true and the cipher orgId != null", async () => { const spy = jest .spyOn(apiService, "postCipherAdmin") - .mockImplementation(() => Promise.resolve(cipherObj)); - // 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 - cipherService.createWithServer(cipherObj, true); + .mockImplementation(() => Promise.resolve(cipherObj.toCipherData())); + await cipherService.createWithServer(cipherObj, true); const expectedObj = new CipherCreateRequest(cipherObj); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(expectedObj); }); + it("should call apiService.postCipher when orgAdmin param is true and the cipher orgId is null", async () => { cipherObj.organizationId = null; const spy = jest .spyOn(apiService, "postCipher") - .mockImplementation(() => Promise.resolve(cipherObj)); - // 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 - cipherService.createWithServer(cipherObj, true); + .mockImplementation(() => Promise.resolve(cipherObj.toCipherData())); + await cipherService.createWithServer(cipherObj, true); const expectedObj = new CipherRequest(cipherObj); expect(spy).toHaveBeenCalled(); @@ -201,10 +198,8 @@ describe("Cipher Service", () => { cipherObj.collectionIds = ["123"]; const spy = jest .spyOn(apiService, "postCipherCreate") - .mockImplementation(() => Promise.resolve(cipherObj)); - // 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 - cipherService.createWithServer(cipherObj); + .mockImplementation(() => Promise.resolve(cipherObj.toCipherData())); + await cipherService.createWithServer(cipherObj); const expectedObj = new CipherCreateRequest(cipherObj); expect(spy).toHaveBeenCalled(); @@ -214,10 +209,8 @@ describe("Cipher Service", () => { it("should call apiService.postCipher when orgAdmin and collectionIds logic is false", async () => { const spy = jest .spyOn(apiService, "postCipher") - .mockImplementation(() => Promise.resolve(cipherObj)); - // 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 - cipherService.createWithServer(cipherObj); + .mockImplementation(() => Promise.resolve(cipherObj.toCipherData())); + await cipherService.createWithServer(cipherObj); const expectedObj = new CipherRequest(cipherObj); expect(spy).toHaveBeenCalled(); @@ -229,10 +222,8 @@ describe("Cipher Service", () => { it("should call apiService.putCipherAdmin when orgAdmin and isNotClone params are true", async () => { const spy = jest .spyOn(apiService, "putCipherAdmin") - .mockImplementation(() => Promise.resolve(cipherObj)); - // 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 - cipherService.updateWithServer(cipherObj, true, true); + .mockImplementation(() => Promise.resolve(cipherObj.toCipherData())); + await cipherService.updateWithServer(cipherObj, true, true); const expectedObj = new CipherRequest(cipherObj); expect(spy).toHaveBeenCalled(); @@ -243,10 +234,8 @@ describe("Cipher Service", () => { cipherObj.edit = true; const spy = jest .spyOn(apiService, "putCipher") - .mockImplementation(() => Promise.resolve(cipherObj)); - // 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 - cipherService.updateWithServer(cipherObj); + .mockImplementation(() => Promise.resolve(cipherObj.toCipherData())); + await cipherService.updateWithServer(cipherObj); const expectedObj = new CipherRequest(cipherObj); expect(spy).toHaveBeenCalled(); @@ -257,10 +246,8 @@ describe("Cipher Service", () => { cipherObj.edit = false; const spy = jest .spyOn(apiService, "putPartialCipher") - .mockImplementation(() => Promise.resolve(cipherObj)); - // 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 - cipherService.updateWithServer(cipherObj); + .mockImplementation(() => Promise.resolve(cipherObj.toCipherData())); + await cipherService.updateWithServer(cipherObj); const expectedObj = new CipherPartialRequest(cipherObj); expect(spy).toHaveBeenCalled(); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 7d06b3185f..4a13196c9c 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -573,7 +573,7 @@ export class CipherService implements CipherServiceAbstraction { await this.domainSettingsService.setNeverDomains(domains); } - async createWithServer(cipher: Cipher, orgAdmin?: boolean): Promise { + async createWithServer(cipher: Cipher, orgAdmin?: boolean): Promise { let response: CipherResponse; if (orgAdmin && cipher.organizationId != null) { const request = new CipherCreateRequest(cipher); @@ -588,10 +588,16 @@ export class CipherService implements CipherServiceAbstraction { cipher.id = response.id; const data = new CipherData(response, cipher.collectionIds); - await this.upsert(data); + const updated = await this.upsert(data); + // No local data for new ciphers + return new Cipher(updated[cipher.id as CipherId]); } - async updateWithServer(cipher: Cipher, orgAdmin?: boolean, isNotClone?: boolean): Promise { + async updateWithServer( + cipher: Cipher, + orgAdmin?: boolean, + isNotClone?: boolean, + ): Promise { let response: CipherResponse; if (orgAdmin && isNotClone) { const request = new CipherRequest(cipher); @@ -605,7 +611,9 @@ export class CipherService implements CipherServiceAbstraction { } const data = new CipherData(response, cipher.collectionIds); - await this.upsert(data); + const updated = await this.upsert(data); + // updating with server does not change local data + return new Cipher(updated[cipher.id as CipherId], cipher.localData); } async shareWithServer( @@ -732,11 +740,13 @@ export class CipherService implements CipherServiceAbstraction { return new Cipher(cData); } - async saveCollectionsWithServer(cipher: Cipher): Promise { + async saveCollectionsWithServer(cipher: Cipher): Promise { const request = new CipherCollectionsRequest(cipher.collectionIds); const response = await this.apiService.putCipherCollections(cipher.id, request); const data = new CipherData(response); - await this.upsert(data); + const updated = await this.upsert(data); + // Collection updates don't change local data + return new Cipher(updated[cipher.id as CipherId], cipher.localData); } /** @@ -782,9 +792,9 @@ export class CipherService implements CipherServiceAbstraction { await this.encryptedCiphersState.update(() => ciphers); } - async upsert(cipher: CipherData | CipherData[]): Promise { + async upsert(cipher: CipherData | CipherData[]): Promise> { const ciphers = cipher instanceof CipherData ? [cipher] : cipher; - await this.updateEncryptedCipherState((current) => { + return await this.updateEncryptedCipherState((current) => { ciphers.forEach((c) => (current[c.id as CipherId] = c)); return current; }); @@ -796,12 +806,13 @@ export class CipherService implements CipherServiceAbstraction { private async updateEncryptedCipherState( update: (current: Record) => Record, - ) { + ): Promise> { await this.clearDecryptedCiphersState(); - await this.encryptedCiphersState.update((current) => { + const [, updatedCiphers] = await this.encryptedCiphersState.update((current) => { const result = update(current ?? {}); return result; }); + return updatedCiphers; } async clear(userId?: string): Promise { From 40ba15c07e1a5f719727c62bad2dbc5a4b272101 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Thu, 18 Apr 2024 14:24:16 -0500 Subject: [PATCH 33/86] [SM-956] Secret Manager: Integrations Page (#8701) * add navigation item for integrations and SDKs page * Initial routing to Integrations & SDKs page * Initial add of integrations component * Initial add of SDKs component * add secret manage integration images * remove integration & sdk components in favor of a single component * add integration & integration grid components * add integrations & sdks * rename page & components to integration after design discussion * add external rel attribute for SDK links * remove ts extension * refactor: use pseudo element to cover as a link * refactor: change secondaryText to linkText to align with usage * update icon for integrations * add new badge option for integration cards * hardcode integration/sdk names * add dark mode images for integrations and sdks * update integration/sdk card with dark mode image when applicable * refactor integration types to be an enum * fix enum typings in integration grid test --------- Co-authored-by: Robyn MacCallum --- .../secrets-manager/integrations/ansible.svg | 4 + .../integrations/github-white.svg | 10 + .../secrets-manager/integrations/github.svg | 3 + .../integrations/gitlab-white.svg | 3 + .../secrets-manager/integrations/gitlab.svg | 6 + .../secrets-manager/sdks/c-plus-plus.png | Bin 0 -> 12445 bytes .../images/secrets-manager/sdks/c-sharp.svg | 7 + .../src/images/secrets-manager/sdks/go.svg | 7 + .../secrets-manager/sdks/java-white.svg | 15 ++ .../src/images/secrets-manager/sdks/java.svg | 15 ++ .../src/images/secrets-manager/sdks/php.svg | 4 + .../images/secrets-manager/sdks/python.svg | 19 ++ .../src/images/secrets-manager/sdks/ruby.png | Bin 0 -> 19253 bytes .../src/images/secrets-manager/sdks/wasm.svg | 11 ++ apps/web/src/locales/en/messages.json | 52 ++++++ .../integration-card.component.html | 29 +++ .../integration-card.component.spec.ts | 174 ++++++++++++++++++ .../integration-card.component.ts | 93 ++++++++++ .../integration-grid.component.html | 15 ++ .../integration-grid.component.spec.ts | 81 ++++++++ .../integration-grid.component.ts | 15 ++ .../integrations-routing.module.ts | 17 ++ .../integrations/integrations.component.html | 16 ++ .../integrations.component.spec.ts | 77 ++++++++ .../integrations/integrations.component.ts | 113 ++++++++++++ .../integrations/integrations.module.ts | 15 ++ .../integrations/models/integration.ts | 21 +++ .../layout/navigation.component.html | 6 + .../app/secrets-manager/sm-routing.module.ts | 8 + libs/common/src/enums/index.ts | 1 + .../common/src/enums/integration-type.enum.ts | 4 + 31 files changed, 841 insertions(+) create mode 100644 apps/web/src/images/secrets-manager/integrations/ansible.svg create mode 100644 apps/web/src/images/secrets-manager/integrations/github-white.svg create mode 100644 apps/web/src/images/secrets-manager/integrations/github.svg create mode 100644 apps/web/src/images/secrets-manager/integrations/gitlab-white.svg create mode 100644 apps/web/src/images/secrets-manager/integrations/gitlab.svg create mode 100644 apps/web/src/images/secrets-manager/sdks/c-plus-plus.png create mode 100644 apps/web/src/images/secrets-manager/sdks/c-sharp.svg create mode 100644 apps/web/src/images/secrets-manager/sdks/go.svg create mode 100644 apps/web/src/images/secrets-manager/sdks/java-white.svg create mode 100644 apps/web/src/images/secrets-manager/sdks/java.svg create mode 100644 apps/web/src/images/secrets-manager/sdks/php.svg create mode 100644 apps/web/src/images/secrets-manager/sdks/python.svg create mode 100644 apps/web/src/images/secrets-manager/sdks/ruby.png create mode 100644 apps/web/src/images/secrets-manager/sdks/wasm.svg create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.spec.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.spec.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations-routing.module.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/integrations/models/integration.ts create mode 100644 libs/common/src/enums/integration-type.enum.ts diff --git a/apps/web/src/images/secrets-manager/integrations/ansible.svg b/apps/web/src/images/secrets-manager/integrations/ansible.svg new file mode 100644 index 0000000000..7a32617ab2 --- /dev/null +++ b/apps/web/src/images/secrets-manager/integrations/ansible.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/web/src/images/secrets-manager/integrations/github-white.svg b/apps/web/src/images/secrets-manager/integrations/github-white.svg new file mode 100644 index 0000000000..030c7c6723 --- /dev/null +++ b/apps/web/src/images/secrets-manager/integrations/github-white.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/web/src/images/secrets-manager/integrations/github.svg b/apps/web/src/images/secrets-manager/integrations/github.svg new file mode 100644 index 0000000000..99e3ffbbe2 --- /dev/null +++ b/apps/web/src/images/secrets-manager/integrations/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/web/src/images/secrets-manager/integrations/gitlab-white.svg b/apps/web/src/images/secrets-manager/integrations/gitlab-white.svg new file mode 100644 index 0000000000..7f7bf006ee --- /dev/null +++ b/apps/web/src/images/secrets-manager/integrations/gitlab-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/web/src/images/secrets-manager/integrations/gitlab.svg b/apps/web/src/images/secrets-manager/integrations/gitlab.svg new file mode 100644 index 0000000000..9bbc28c37a --- /dev/null +++ b/apps/web/src/images/secrets-manager/integrations/gitlab.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/web/src/images/secrets-manager/sdks/c-plus-plus.png b/apps/web/src/images/secrets-manager/sdks/c-plus-plus.png new file mode 100644 index 0000000000000000000000000000000000000000..bac17e2ae270435654cf2c206938ce51ae4b97a9 GIT binary patch literal 12445 zcmV;OFk;V%P)4Tx04R}lkijd1aTv!xV_I0DmMCclDK%QNgX@em?XYHRxai%x88!3k-6S{p z1Dqr`H@P_PB5~lX6mmiiJ5#%`UeCK>8+q#K`{~>BJiq7nK#NmTDr*PUas?wgGwGgR zSadr|*lEX!i+)Ydt3V_YmJ+_TIm)-#EWH`EX2nkW#&@osjo{_$%i&Ou`di-=9jSC) z7yXd*>&hsA%_|()EGUb&gGrpCt>Cu@9Kt%*g0!q>vx`ihnkeiZg38~jWI zuVpv%uN|!Io#|YBPP9*QfG{(-iHpXF5+Ff{DW?DF@mG6-tfSb%V3iCj6l6j`97UG2 zaEKf_V|avpcqRSTt$*LJ?x|jwIFP5F>fX{l6bGSex9$y>f|_W$e28)3;J5|*4%Ks-bgNuq~}3g?|aImy98j0b|kk~uM) z{0+c@qH>~#hi3xGNRGQ~1U9od$H}4l|7u~*Wtmwf^vrbkRPAR!GgDphTlMWquij&T zIzc1AaU3&mTgq^jvpobHGlT2)a_V zaKKOugo(hkjd88zYWaMb45lLYqTMMa!!dab!+mUHnC)9<^)GQJE)DA+1Ss+RgR{%I z#BiLfAU1gcj%+d5IL%#-=pZ1+K{3c=t81F2ZrU|vip@a~>J=aaDDeUWyrXPqZ%!Bn zjBIoLbkb1(5B(Tm(rqmJ(zdw+fAug48jWWJDDij()RW}qd=}S-wJBEMy6qWH;=39L z3HS}mm{)F{HTbftacDT-AV7)d8(dvV_2tjwo9ir!zXNa!F~W__5|^TS6G+Q3@R7ZJ+RroRcj_ zF@-)p%Em{@GS_h3_gd`ie*f`N20@=JC7utuz}&3~{a6+@;s(YjA0+R$Byr2-m|Vsz z_;gnPM89PkBpFfS@z~JQx8XXDRjve%o55qU0Z$4$$!9XPdfBGG_ACf^VI)!jN<1DK zvR=$^qoDFE5i4 zM|2J{@pi-;v9tSsFOC8Wvy_y09=6MzO(}g?nQa3`f7HVy#2t-+Ju`oAdX`J7az3sMC7z1_ zlMAV{W0R+0XWM|FwQw<7Pz1xn_dFRVTeXRbpSj%Llz1*cHGNysIJuQwiEEo-E(8ze zAZaYiEu`0vZr=qc@tVNs#jUBG%&hgF7~~23r&u+C)&CwbC=|!RE=I*H+44gF%>Vh* zFE=4TJJU^YT>mk9^Mz(0Q@?`K%U97S!a)$~bsGdYhN+Nq%*R$4^WnCceJgJhJm?-% z;x%Y;BIZ@tA?11C*kyQoz%4x|4U(Z>Z$=;+Ckw0b_~Emgy@UG~CEktppS2}Mdj0R932ggPH0z=0KaK)1=1w)LB) z-JRpZoCKeA&=T+UFN?#f4JGqsAbShn#F2t;DM=NOt3mtbF>ub@2Pay-?g>o`<CNSeSd1`moV!@3Q<|%9=$<#a4O~{^q%Kd`50n~Y@@|y`E>g{X`KIwo_NoH zxi3s(Z2KpMk*&eq%64wGCk-qr1hBK_a3I@o=d8Y)Vi<-MRpA>IR@B6s8=Lfyjl=6Y zz&y~XUi7Dg5x`EH1e|>SmN|V-h_#xcB;Ndw&$Y9tG$>ceJdIn>GO>bB%k2#UWW-~? zRWq>kqgew{5L;YMRK%OU?L>sa+H5geyID*LC$5g>!_w+yzUfC^I)DvFvlcx9oF3oVE4R$*{|EWuE!0SfLT8Z81ZIr zO`L;PQr9C0&4Z%3X(fe*Kq1bs)-aqUcFUY07JnDc@5IAtpf%c{qRw3ZuQk~~XR<B zgT?QYA6S|EbyU8a!EdsTQ}a3TUj970xz48jI~r}AkECnnxER6dOSscKR0$nhg+M3# z@6b{McQn^Pvrq+Sl>CUGnyf68RGXpJXo1XPJ!F;Yp}5)vRXV@Vu7uyn9Ms~8@kZ1- zw;A<+=KHe1J|o_OJ?Uy=q47D!#w|r@f@oh>LQH8#;t1CFt_bMaJ`B3F358Y>N>IxA zA(^W)>jk4zxHrQujXua?i=Ej&)>r3$J|W&*RJ&l~tZ1~6 zx#xdX3F0r!a8$d$XB6~Cvh{2qE;uBtI91Im(Zkt{YDmeegGxFYUT-4~5^w}ui1MGm z@!e>=5^uqd^hl%L_yMlrrt@7;v85NPlEHvZkua=V6!h&NJhC~%ldKmn7i!_u)hbBH zsRaxE4t6JFm`zHVa@9vOdf`qeKaMxz&5XU+o8gqZu~@-j3)~6*?V73Jf!-})1deba zDsdl8l67EwP7NGPEQfM!aIXnbz$3@P$shZCE1@4fpM-ya30EQ;?0ar=z@2FkK-+ZJDq1E z-6x$|hrp9V+TwIFIP@#UW!J(F7mI_LfMtwEK4i<(zPaA?YOjcg8k)ncN;VEhx}Xzk zZWW<|iG$n1echV}<(;LIX;tvkg_0oMh~W|~%E)2cr*%Q?XP!BQXVI?5vZ`$B@%%QZ zNJrLf69>0}2Ya;urNY0*0N%6}-*NBn5e37#HiLb*{yUIVj)(L%zOxW;ddvWYxfXBH zz(P+8>G6n%qF=pnzKhzPf=9Ty_nqM|?ZFNxNuu-=Z%M`@(lR?v6hcaVoh0k$s8VK& z&2}NL_zx-TC!BZm{C0jGk9aZ;HsD6HxVP^WK^3eePP-RQ z=nTiNRKo6a#VD#K>Cul(&Mz^=Wfz2)EY{W7(BnO6Y>!5~8K0iNUnWMsG z^Os2A@~oJ6Xj;FPn=q&~YN7Dx{p4B3B9BEzzgHgZ0>8wU!JhcidI4^cR|#qiM*YdO zYpR0E8Wq-s{M#MQ__%5K!)@ZZ-J@RqJifWkB1=K0x3}jmwa;*k9A-V-5y+a3I?oYE zDbT_fzvV-d4zq3cq{M{xVcFS<7@oxOU*6{Sb)WVmuPQr&j zd7n+i7h5FWuHTOXS2j%zwEj&0F&fLousO+2Zc-A%lw zZT;@$t(z7bHM%8F-(wX0WMu@`qd1MSj^%Z(Sa^Ho}-+1o{^Yg5t&6?U6=4dROa zD>*74;$PJH7lBDARBl0)6bDh%ED)~RVA3DEoXM8dXalxw9kj$7*}DZy8cL^^f#~=o z&nfra0o8g7T*$2PTM-Oqi}gZwK3pj*3-w#3PBIEg;tl8&0ngnp*<(m2O)Wg{S))3l z4y;^=%l0w(IAAYbD=;PIm4uk^Y)%L+=!i$o>1JYs6E%9&1r`C)1k)bs0OcrVmRoM{ zNnr}AYK=!PXEJ(|CB!Egc$lc$=Xf6GBZz2WYB?+z--S2Td4i~v$P1yyt5x&HbmaZU zN44H)`86@kxaZOp4U*2=%@f{H4jST-5pKcwPS6t7=}{*v1jr%#OL)$wQg}ZLG25)R z<5#l{yU!;vc@@>}pUL?NnxL6#P8!-4qTAYq%Kd~c&BkQ}h%(;^cejBb&X>3xr(r;= zm1WaP%8VDX3p99LM^MwshUK(>4+`S-?-+rKh^UQAd!q~W^RYo~pahjEok7J)?zp1r zI`({KuDPUE7x3LmSIaGV;x$Kg=+8gU!BsqHxBw&YBp&gkACm zT_xQkhoUl`d%IEddt&RA zJL<%Vz_Y{esJG*wGhf%=QcFkhPMSoz7@sk6p>*cArnS;hIkH#{=$S zElJ`%g;Pwiii8nMuEa`DGU}*IL%Vf#M$cpmLs3en{|!5QL!z6DN?PaJ)7hp)JI|}G zmDiCOnIu(6N{)EM{@egOw-cK?hi6wrEqYp!ju*{bM6)w7G(!HrJXQdG&{WvSUM929Xr;`k+>;Xf$su21l&!>^xOi zzi#tqdCPFk_%Bqqeju>|o*2>whT^t;pd8W7`oZ2*%c}3=l+`+Opo$TYd?iJ^$54%f z&|OKj3BE?cT}GXgfpn#{CWt+pha+wU%y{@NXpNgP{&Fl5?$=8N)`ef4wPh4nOMY7E z2v^AvuNw+LcW)=`(N2iA1+q%@u;Irn7=zQ#as694>La}RCFE9F7wtR;`x5d=-;+%L za~CAZRQ$m{ZYz3n7b}m$=(x}xR~8>TuHS)C^k-9X=i@WbtNYsh9Po&8l~`raEQ68_kYR8&g>ht z2EKrltUWjUoP}n$y5M2FcRV?mRA_zu>$8f4Yn8qzs;JWxUT^gP&fz_TH_ap?-FqS< zogI22CG*d{S*Qifv%~wcS|dAU;}O%?b$co4?4UOK6g~0oY@va>@br7Tq?p8Vlr_lq^6K|LRul?*e zUH}=z`bJQT?`L|k4!%OE7gyYl;~CcOulyU9eHX{zaY4{;p4<*I(Xa6aq6n0b+~Jf8 zIDS<+Yns#P)zG132#j@d)R3HCZJqY%Z_I`4a?$rCfA%9$gVjDtgSOX%GtDeTsSvU) zFE#g{v&AqF$@aQ5J$9`T~v2Fi<>Ol{~=3QKL5a*9+)K1w(rbTrWtuWf@JHXq>Pa+2{`H8|RNCgI_7 zCjJVD!&*Ugo#kvsuDL?1r~2-Fh`VTs7u{AE%Z)>LZYKp-YcLwmX6C}RvT8N%%hJsg zA2+IKiPr(o@B-}$DvhENfOdqJ84WeY z6T&{KWND37cQQREq`Xcq%J%mWEPhQ^l)$$^O<93tXT)sqL>k-KU_cICA?^T~{B9Ioen zzfvIN0tZ>yL`l4G+|>-UtJVoqL`-KgThR`N zoNt&t*!vDx`OG~I<5ug<@cy3UTb}*jh^|hLC+_LuT#X@F#w>ek5K8YjzGbrc@Kq?N z^!}wA_#S)}$0iTCF}bcILnzROXRCjG*ZcuSrbAHlsDUBOH)3q0}A47V#q^=X(&t=@P#{hGS4x=xhs z=u>b6o;YnV4;zTw$_|t>n=S0c)GPYb{1T202~&eC@S9D6!a}V1ijsJ&0MskU8UK&E zM9k|-_BHeQ#MDre#d7^#lu6G*NVpOIiBth)ph%b~O5zy>u(6?1`d6N~pt!_*`ce{G zRja*u9cN=@psfu7gPw!12qUONoNo_`q*De%F-1u{1FA+3vSl@N|~<2=q{jD#wDGigFu2o1O4`p>robrC>fEojKnADo81^6t~nv%O;@ znVSw>hPQXcIgRV+!NM=kIP$LdoAYo_*fev5!{5fY0tsiH*oSDPLr(Ok}!RVPVGsnrnfQP(Bw7V4^X#q``Ajsz}GE#~!$`MZ|fHO3r zOqXw0SyQ7uayCv;SzW_V;vA=jI=s51Lou6(W+qUng`ud1Z{S3jo+yc@6@ZQtVk|`| zXHdbb_s0sjj$_XyrP|VS@_a)K)~bY>3MFp5kP%Rh(@+0z$%>F1zAUmRiC3Wwyqsh+ zG~C!0RSf2%AxBRgsy??|Ou1}Lyqcl3*;wCC2TcYgnDDKo2{S`@gc=x`f462m1xkYP zL`giN>qH2S*c0%Sc-i@dx)T=?G)A+@-}mDbVALtVf*UZQNI(s4@qrtgH$nhj0%Z^- z@hWl2LTnT$0{p`6cUL6*5?|(4H>j+sHJ^%4v=x<>d$w`nR&GrMwpr!4{=@S>oe?6U zl%P=3!}liO;`@;(iDyI7mDie}U4ShGb#5I}Z?3q!?)Fn(z0qhqmzc_B=H@F|mJPh_ z3VTnoS~XCGFAsG{9fU=OgB%yG0dNnm*$Xnpan`j4elH#GJe>ua5 zc%`1ggcB|Cu9fNQ4J8UevF#H>+QNw|RbVum;X=|C zQ%Xi=U^hYNsHI8>FgD1K(f>8Ao@0x{3TSN#H*j%cTvEN8$+vGAraZX(aZTEL&oGYOVo-DA!L$aQ(;ZSu|ex@0Uhd zyR?p=yPr)ozUYZZ>h@cz@XR3(jBbmg#q**hU#rLv_L* z_g8ph@*f0QLL%fnZ{lD$^v?6RDdbqwm$wBU)TyQX?CQtmo2Cypb#Cq0*15NNh$(*4 z+f@!~GWhX%FQ9j42af{kMK)Llb&iAx0hsV|1VpG>31bIGLyrzE;bM9r)aXo(g8ALA zMMMaE`S-`+jVZ%Hqw=ljE+P1k=+=rU_jR#hUd-9_5*8)@r10|3f#e^*aHsL4Q0e;m z60=zclbkC|bq5q*hW*RK{UCnRY0U) znkj>s^be-Mq#?m6ekK{>k)CrgyB3P7g`tn7sh%7a9s=*q84FiFn-6~;)z_~CBxIjD zb^x5-I1M)b?GcCw4cx7zj;+GwyI&ovIJ{(nwOiZBpxS_u%v6(fbYeQojQ@i=MK;y% z1@o*0ZreZoKyM(UAaQx6wV>r$&4InLQD|{>;iO^k+3OQw#)P|}O>@`J=j>_t>J!ze z%jS*g#k2@hv6rq@fX;;X0mbFqo@%;X!CA41M}UKego+D!qc55m4V~NEY}-SqA!JCyqFp?vVTXOQ}=Ijo!d39pB6gG)yA?wY-7q%DYV;f zPe%y~%&*5C&17#s-dBC_bP6ipZKjf%@lDBWu2`)$)0s<2mhbi+v}NSvhWz!NA5ay8 zbl2;|95qpUAw|-WcYwuegJ~On0x%g>P*Gw7ZFL=RHpv{oi=TM-+Rw_%uig2}J_VY! zQd(`c`ca%r(^l*_Cb0K7Esm=Qto!C9WEEA}gQN8$eFRjMGxZjbK>X?_L`}Sk>RR@v zBgf5$PG5*Ln#{NC9K65l3|vSnv^O686bS-p1(mS!D+e!4n@t9F)k>%;F@nYH*ixlP z>L^O$8H`55(Q_A#yZ0YqipwjV?tYnXOjq>!ju1HZAmSwT3`-MHpJZ zH32ICyWAdtD=OmQI*z@Va)teN??E-1w8G17+-;XQ$vLI49;YqTi35RU+mAx(HCH5% zu_ltr1Sa(K^k`Kq8(6CD-6}|9(OPaMzr8EZQ|SCKw&u#`g5$7Fr0oW z&;*$YY$!h{P$xLz+0b3d$}yr=X6xmw99{(qGYYFd5FM@ zuYQAXj;44R`!>;7Rt9=)7#_l-^h+cMDve;KpMoG>ola*vej&kJP*Um_v&3WpPqb$( zcv5=GXK&LL1@)Mm)x~^y%=cxoSyfPj)|?bdEks6X(E3rp`+YzQfMP+W3rQ(lN_w`M z1I{m`v$yg~AMb-OjS8keGRW>lKmCZnd*7Xb6^;(ceOU&pB^)YCIkfw3!V!=`hsklU0c~!MvM>t1dlB+dS z|M3G5o7Z&YbA%B8e((PKG`zL_NImBKr|w3I#14HOs{hw(jS!AUOe(eK`w9%+P<7L} zO+1uIu5~ky`rulv-g+)6)smT4fEB#G#EB<)coK)j3e9})C#W-+>ThU!k_L_A8Um|! z9LF`?Z-E-=92e4N(?AU>L{@365P=&oG7qfZa^SY1Mz@KFq{##~E~8+_HeXCmWl}P- z))^vUb$2=((4f#qNP1}lYpTf}TsK!u4|zSTik81_@V zcjdaf>Uz-YAw3|T>=cskw)wC&)>=?jW;k{(PN_GVJRinyn-hNl$YP%yw7)lJ91tBP z)On9UHO}N-{$w9~aUjwA#C)DYz4)05j0O(E!g2i%4Jz`X@%%Cmwvu40gflP zCj$S;qOzsBYP1Es1daYa0!H@W>*xvg}+BZy{gzO)Y**|^5fOlFJuY+|xL zE$3P!$GY&QXq#_CX5+au4}(ypXCWt*8vandq$rvakrmwh@@&hEvC_8g=JqHg?S(D0UZjANW$4HWcrDEL~x0v zP+9T(?|0s?%-A)>|%TAvA);^&eTLB|y*odmtvpZCOx{h(}O;`rT9G zPhO1wxwx#t?RBCUTZQPPT>6(0@YZu9aKDR=ehu5uV6wu7U8i6*-g_iB4T8eSiTGLA zv#{%>Up%R=$HW7Ok--?|Hyrgwc#^SL<8-?-3bwuWIEY;>^K%h79F0%N^`-&%QE0I|k<_MtwoZpP@K-T6$*uY}jP9Dturr-`)* zy(xDASd}B-=$kp7CgL6ONUmd7jYEAG`#ddx*yGKcIHdm2y-o9`cAUjt%iqhNUtSAq z{&fPj?ui4d0Il%;UQ!=@V;B?eu{?sK-BE9H@Sb=i_i?MA1D4(4O+myQm#9&n`^O=$ z?QMKi znLpx@(u|Ag1+4tPNWh+a6i&=(4C!$P%zpG9cxGg86hA}JKM}dI8a?d#EfvU}xdz&t zMN|X6i^`y6iunaNxGJ8nl=&qdDb?i3sO!Gp8z5s|$6t8gBDftfZ~WM>ZuKMK!-G0Q zWT;@Y58Tu+22}d{?_;U(Esku*l5+9zn<@cDfQV+SWe}bJA#B;gDpg8NZSKZksx$*wsfQ~R}L@yZ7r40=1+OGa0uCwsI^d|-|ujW_4*~ zWV@JNM9KC$;6^mh@GZ!=S7Bca>Wtvu`Hpz@vXbd1C*O=apCj#F^z%9bo!T~o-W^-P zxWUmdAi8b6J-beAnxRAzzs-UQxPk0OUd}CtxQrq=ol*c7(+i=vQUY?6ymuz%DE8GQ zaA?_S-ji;SAipi#AmLlSj$8RGE>72CrMlemjC#}~KsIMOwP}t!qv6mlD!ksnWFI7E zbF`Trf>YKAoTh42a{O1I^vw09BdQ|_u?%KQ{fI@Z$kw2oX;yI!q~VB`Q(Oi4Wpz+g zr3EwIw5QG)0!$_f<}OA33jcwlF+Sh6^&jy_eIJTxg?APfqLIM`IQ?wq3_K)f`biP+0|FO8E}IYg-a6-pyc}i`SmMML&ZO0fs^Si?AGTgYOnw?Zr85%R;lR-3D(5 zPK`#hnZ5|RG_QS&tvC6vyhOBX1?a+O!m-6k{>zrXoWwceHLU-mt2=>(dlRK!W}~(j7q^m`)&7;-0W2gnUeVc*)KMh`{xQ#u`W^qK)6V`Gtk_ll|oXklGLfPs@}Hedf8T85PTqU4z$;xHY)o?jmgHY3W=3WD1u3hcsDSg z5EBVj`9fTHzlj745k*VTLN-Exsli*|o58{^$J+zOMh`{*Q<}uHH_LVLvv-kvlkHyg zQw#{mz6H*@5)Q?rilMZglu_Emvm-Ko)flh=k^iLEkL;KT@)NarvC0K-c(EkbaZN!S z9K^FDFlNOxl;2y4Q&4Fry|mX${GVvKl6ecy<31OERa~oqIM4B1tJ|%ivW{MYjt|D- z+7t9oIWL9 zee3*N4x_KUh;lFF`cX?EH<)P4#sP}=!CRmYkA>5(5)I8k?I`gYHvb=2-w7rTuOD%L z#Su`lrllKJ*$>}qaZUX#Ft)|M(#q_II`EzaC7vTl$FJ)KtYs6DZ?vPw)X!xEjsPoL z42J`!&CBJI`Z*vaUQ>XNTlEZzpRGdzc55maeRc~1sNaM`J95i^#3xkS?k(`3Rv`y| z)XR=Q=8;rrKWqmmRXSY%4McA$erCtU;U^LELN)!N>K9<|QZ#KvoihaJi02HM#{Oet z+T$TF9=;pfCm+)#Pa|&n2g5{Rad8@^!yTB!F2W{3Gv5aaAf1&K&{MQioY)YuK^{V zH<&;9L37a7qV{WMDJG-vCMglc)gs|O1%qZ4oLofC=BV=k0ZKd%K)WT%MAUw5VAdl6 z|9s0c$#{^>N!dL-YVeGfj96+l{PC|373*uVUM z0LmhXB2eP--;4~5UNsv#Dp~)b9S9LS2JpHLtU@*Dn{hO!=W#qXB_$q@VBVC}=(R0T zOp2`i=Hi-2Z0CQ3WbZ*ZyGAJ1b#B5|0zWA6eBFqnV|t)=toJaRKl(Z=Ueafn!(g$^ z351nYUdk_$L@4q6)lj3?K8YjXDjfa#`72Ae@?qd23J0teqgH0O>M4-Glz9GZhzT3j zV6FKZ%74C!*`g$jAY3(ydBxx;w+%-*JSw2h9|%z5`2*#~c|45TuVvh8=sh1tzyQh1 zGAMt_egmvxDI8i_(zuB9w{H=k#Pcn_O{YG7eP3WrpWqa4WYYn8e|iMZ0&j99=Oi)oQNfJ7OuBUgc*@i@Zm!3~=q;qdBAM}erH bFcA3v??;y!V7JTB00000NkvXXu0mjfP1!2= literal 0 HcmV?d00001 diff --git a/apps/web/src/images/secrets-manager/sdks/c-sharp.svg b/apps/web/src/images/secrets-manager/sdks/c-sharp.svg new file mode 100644 index 0000000000..f0da2234f1 --- /dev/null +++ b/apps/web/src/images/secrets-manager/sdks/c-sharp.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/apps/web/src/images/secrets-manager/sdks/go.svg b/apps/web/src/images/secrets-manager/sdks/go.svg new file mode 100644 index 0000000000..d335346e71 --- /dev/null +++ b/apps/web/src/images/secrets-manager/sdks/go.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/apps/web/src/images/secrets-manager/sdks/java-white.svg b/apps/web/src/images/secrets-manager/sdks/java-white.svg new file mode 100644 index 0000000000..082897c081 --- /dev/null +++ b/apps/web/src/images/secrets-manager/sdks/java-white.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/apps/web/src/images/secrets-manager/sdks/java.svg b/apps/web/src/images/secrets-manager/sdks/java.svg new file mode 100644 index 0000000000..085cedd07c --- /dev/null +++ b/apps/web/src/images/secrets-manager/sdks/java.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/apps/web/src/images/secrets-manager/sdks/php.svg b/apps/web/src/images/secrets-manager/sdks/php.svg new file mode 100644 index 0000000000..5e36980ca0 --- /dev/null +++ b/apps/web/src/images/secrets-manager/sdks/php.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/web/src/images/secrets-manager/sdks/python.svg b/apps/web/src/images/secrets-manager/sdks/python.svg new file mode 100644 index 0000000000..36f5cd7f05 --- /dev/null +++ b/apps/web/src/images/secrets-manager/sdks/python.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/src/images/secrets-manager/sdks/ruby.png b/apps/web/src/images/secrets-manager/sdks/ruby.png new file mode 100644 index 0000000000000000000000000000000000000000..2dac1c8da81848acf1994ce7d9446f8f3ac5a70a GIT binary patch literal 19253 zcmZ^J1#}#}vgVj6X0~HyW@ct)rWnRBX687tV`k==)<5fDe6E|5bxNKm zs4Pkzo;KbKa_6l|EEjPU0S$1i@f6sM(EIr40^qG*Ux@-VScj1%`s3<0!{O z*G)iv88{ps8K5*(Axepng3;B)H`=ny*oog{HK_bN?1=zbA!e-8iz0RZM-7yyumAp9?^4ng}L9Y_Em3!bWMtt10Fcl?O+dPft%~PYz~B7i$(aK0ZDcR(2M4 zcBa1?Os?J_H#09LkSoQ%jr?Cbl9sL(EDej_z(kwPn>i#=C!GEmqs{*|&?R6!A4wfL-e>m*y?1C)+tK@%W>ivJ2|3mU0rXb5d{q#Tj z?cam)uiC$(B8(`=@?Rq?jMy}ko&x}g0^}scG`%3!I}x(1bo34b&;3(Z!0kR4ZZw^R zxERfO2zpuHV8!qu5u?P=o+4v{@~&qf5D;x3t8?|o~go zcl7rRyYYusoPao}vZQ(Oa9_7wpaxcuYm7RVJQI%*gx1&RybAOVRa=&|m$Tz@5#t;` z>^Kpv^sMfErpbjGig(8>hE7;C=bXjT`j6sh2#?+9GVlQboT;8A!lw9OK-LLY^>42( zhGd2FUt~N2K0g;goGWro8QR)az+|0D7X-EFGCH2X$2_~3*f#2OQupcr+gYx7$--GK zr=?C#eZho3D@aI-0%%-N^4#7~qzb19#h$BcKiw#wS(vyOIUw?mkYiA3G+QG|rJurQn z1GoOT)O~D=Dm1Tnf3Cb%s-OilDzeT#p|=KDtKNegzMK! zoxMe4I;|`3{?%Lp7PHi7_=g<^c?g*wb}$Mx)wY79{eYAUTeNxs`1uEq!7+#j;| z?Q8QqUx~Q6h5?~6>im@Xl)`vOQ-kL7@S#(lTeynC8DHg(FPP7cXg=RH9R@M5c!DNq zgpWoK9X&Z7Hcv8K8;YgVYt#0m!V?{EMVs@1Jn@av{S$1&%yA`(gw+YG5Q)LiztX{Y zD7wd?K_wWFKV5~2II|sEE*N++Yg#?*&sBMXHe0rsig1P>&W*bKZVpma5AAHjP`muj zbHL9iZ@90xg@r1>>WpiFad0%6n3Bqnym^oN>mFHLFap=ndxTZUtSXzu}l!md1PTJ z;emjgyaNncIgYJb4m5lM2B3br>-j3MsOzLN>;MHubtgHSA>&U5JbrpTb=_~9)ttP0+8wvppx>xx?8QZ0#;LdW?IQ&;>+x{s zo+T*4+d5D%TLU_?MV|}#UAsLEy6QfsXLDQvJD|zqlb^J~8=1waO$RgqxGuwcQi#x# zn>5lG#n-0#(bBsmVwWmux}QqDWYmmeJ3joQ%QCxCv|$bPjRUJ)8!VWCi4o`Y(MnoO4Fz_MF4(MshD|BkDki_T4<*MpX}p^cED(dL^p1&-GxWp#lJ zQS6A~twJK`WYC5r7*AR;Wo-v4o{?!bbgBKo2o)Tx^U+!X;!Q=6n@g6_Ztb!*ndkM| zdF-26?b4Rxc5uIgu}VrRN}k3xE!{Id{^DB;os+~$&y};cOcl;<=Y{I)<)w4k@>L!hAW1`S zdcnN5L}v}-J}RJW|)?Y{TDNv+WTdbO-FU{N7sF_xH19=<8@cpBk8}jNaG~lXBvDz1L^OK{hRaW? zZBV8g)OmnBI4&qoHBYMd*Ril?;g5x~Hi^x-Gd2hsF1h#x*4e{u;k)I6Gq`|{mQ@*% zYuuvIV=*24&mL8E{z-aHkFeU1+zhX_20rq{gD?snk*sPojTu#=*fbRSO_U!Aum#&G z@^>Qzgp=AD3UXn+oqmTW~X5e|1mfQr+# zL$%kJiT+ZB^^`}wU=zJtx$}F~trE_%u?IJ3`G~{#?)W`l093R7I$B}i3WuUop2^+1 zNT=D}3^V>iqC^djF96S3!0vfQzw`1Nj#3Wehg-x;oL;K znOX#Cj}*lAeX9LKvZFTWGBl4~y?=vjBuC;A*xA&umea=S5%g;p41(Kg#8Q2D-JIx$ z3*1TJc~f+Kb>%4`SY4Z^#TcgNll@L#6d^6}fJR_5f9WDm#8f0+wUnSrKmUhR0F$KH zZCv;5anHva7VVlXbL3MZ{ZjQ5`2ID^uC9e|Ugy~R>ZQ7Bv(82DtgibpGbhKLK@Vg* zHvU}VB2a*y%#1D4qO_f1l3grGSuRA!!BtwzHPBx=_O8{-04SmA5m8+Ceo+9m-y(WI z8nw0iC)&kcP~c(NFa}duc)9)dHvNJ8%T_^7A9e76up>=(T+ztZus3`ugy|sIn3GIN zfXSdgc_{~q?4JA}zDop$_Mq&qGK4Ond)H#K&@FGpp+CX-wrINt;=r z>25J)R47_ayWOSdrwAPL5Fs*TY+uUCtgiP=H-Q&GM_TK;v{L9aVZ`Z`gd-Y^v zy0n^@RtGlJ@MJH5fG$XSOy4(DKI}f~%+})|oQ)U>Nk3T)I*zU@i@h_m znKeY$sp+NbfC^dbh$>b_43;=Rj!I<#d0Co54_vw8dsTfnF5>mkwKzVKZtImpVx=q-u3V{Z*Os4H@Q41nQ;9iG3A=lC{%^A#cjH!n|h<_dY7Wi z$G+(5t`D+9*rhtY=x^>pFi!f~v{4nBMl0c;@!_N=^S85oBqo zk34;&br3>ttAWQIgHhj2+Yw7(s8X)6Rd1B@>AP1Rxv8u^U{b*ZC65w~kIwP*uuZw3 z-pQ+c@`|DxKFSo~hcYQttpb$Hi4 zgU<@XpZO`31Ikp9hM6)IR2J2Ujv{PlHHhh_E^>j*IQlWayCP-~458Je;stKtTACMJ zrf1%X#3)xt_eR=RTwcqd== zZ)b4|Spd|cTB7^;jP8jJ-aFz`clJbzCofj|cSlz0GoO{Nh)CVP54M7~e*L^+q4vg7 zRU+GJ1Oy6UJY3Zn`IYU`IW^Q<`j>Hy^kvSL*Djtoxiu1Pv*TQ3D}c4y<_e>aZoZ*; zK$H&g&awYo7Z@nME+q7xIPtz82w4t%hg;h6%FE;(gf0SMCfJWllz*bdhR0+aGv>Mq zaEuhU7O02{F?dcK>V3NJ8~HAYkro?L0-g} z@kN5Cb4w5OE(titdI@nx#;vc5qLZ!zsEqihH0~5Bn{z*mXMd;bdihOat?#4LdcRR{ z?j0*Uem;`q`{!q*(ev6w<%#l)PpLQ>-PbRmj7|H|Lf0(YqbO;V>+zj%&GfA~{#jW9 zl}kMF!RdOKQFztfbAa-;y+eEBjIGEe(S?ILyd2>e3e@?^N5`_pyv=0yN_)?yOxd< zo$sYOHWiH9$Y;LukYO>Q)yt+Bq>?N(#&EPS;fiMs3C?_D&EB5JlkK;Q&#q=@qR;eo zV~=n^Vklv8jAOmlgU%X_oDMc#jSi5pMLy&c$Zg-(_`^IjCGp zl9tP(o6QFud$S+l!1MC=`Q4hYoL|z!Yey@g`+;x%-N!w-f036zh!_< zU31xmma?qHK+xJMxr&~Bk}SIg5;SUg$rHmkyHSgOAkfOU3ys$GK?@Ju@(wYI$y%C4 z{K!u7AcsNY!+|;28l+ajU*`_FzPl<-!xvsXcMbwP4$&m1s>%7nDNp2{05TkVwT`0e zWcJUO)|~Q@o3JtFUcF3!&XxHFPK+ONL!ux8qB6UHmpnqBCseQ&i&uQ+>w!P$*|nwJ z?NHi7wfK`DlvJCvp18t8oRU1pbb+r&G=3-jhoNF+~G>H1F)j5MXNd%tS)uT znkqcCN+}t2Mc*|KQWRur!Vk7H@g%Ycd|}zXYi8-b(a9JVe)3)dTe!}0ZV8^~>W*o( znBsh|5MyH#Tp6sr7^srQQsL4&U=F?ev=TCNv+=H6nufgQj*E%=XUyk*M88@AM%q2S zYzj|BCEk|P9p8az!v6tMoluIegwRa{<52Qd7&K-*JMB z)h9>pE`)3qm(JH5)c4eb-H*H2*^O+2gvF6gF)iWfebG30Y*yZhA-nG=kIZM(>Umk= zL!umz@JJYGw>2JjG7n&E$49xA^Y1KHLOQMmxmpWp^scu0$dw912FH-s%iVn9je~?$ z*9cb%#aeEr0jCV&Q;3qvEb`bpnwUGe?OI;8ixdfhte~Lz_Tt-`ve-oLk|b#>M&m~6{E}4nSRn~^7G~jqGI5Qtf%ZK;oWfutdvI3H)>fu zd~2QPzKpE&8v|X7#oWHVS!b`dZ*bwhg5igz(h|d(nx_;HV2XbJT+I@jk|ISe6(#h3 z;`C^_%_+{Vv}Ms8&B6=lm@m*M^^hOeprbP29CwzlS{y0iJWxY37>*|4tH<98aM;-v zI*%do)mmu?KDxU_U~3LllP0k6v-GprZh)lM9kS_gtphWh;R925#GW~#gX_ptLCpBN z7ss(gPKU20CN34f)#C93w=(%7w#$?0kz8}Ov3nEBIp~jrD4r{~v125zq%qgHr5ZU0 zz7I%a3KBI=vthbRRQXuK`8vNS{GI+aN~T8e)u~oCNbRm^K%sUm-j6L+S(w^XJzleP zhg**d%2w4{2y@_|HK^-ve-uGr9R^jfzz1lpA>LuF+qQU=!Hs0JP`DkscBrBn-RT}a z;(p+RILW7uUaP~snOLAO96dv<^B5syaY{7bNd({|uHtK^!oxqsY-m^b1^705*RHJ! zrfbKE7KS~^dyA>u=0JL}wov?8o{|ykzGGPGo+5hLS(1NSopP-Thk`Y<*Tp7v-Gx6{ zEZO)Ki~wVl%sknxnbC9}c)3Z@`m$qbXXW~$dXs(16}xYYtn zOjPHHT0N(z8}voyfTyaUj_eMWLzZsj?8Z%k4L9{JSNp-FRfm|`);b!l1GQROcb0VD zo#};Ku#XOy{d9DKE+*>+cj_xVqa~VHe|jdGUR2=hIY*(tx}fYP8|*c!l(pu-eR}tM zW>)`~K5?kAz}v{&7JgaFhde$P}dT?#KTEM;^f&`(aedx6~9d*lJ~e>n3=_` zMPz$!67DjMss-Mak*!U7T!&;KR|aDKcP2$=l@+2iMyVRs^t4#}bd;62mZryFuq{FJ zMqd4J>!XKndBKIlnhh$SJ$7^7WKyrozh*0;^FQ;9g;3?RyWK=@;Ggfps6OoGye2g^ z28*&0tU=D8tok$$iJaFCyzU)tgTVTZN8@|Ym?;e*a3ZtIkKPmS2B5C%^M?;(JEzrR z^$^!&MWOJo1um*&wP1)wFA^r};-Mr->CfUWH@P!JKc-||r2`mIQ`o@Jtz0#}O{-T! zAAfENAB@;JkPD`&|G6{Wp^*E$n+s;zA{< z-OCWrUL=P+YUv$pC)68$+?QYHj|Oxu_TK%wonP)QnnLocjt~-g@9@eL?|Mr4b3X;) zo0prhmS;JJHU!a;|Co~Q=FKVov%R%~cd^mC^K75yyQE7N%cTm_j5f=)^`{Kg#B3RcFPKy zk+fBT)WT@a;DI@;S&wg?SK&|isEv|f)Jv~=*O^q|i_m7!)I62jkn?^_wh)Dm;cO)_ z-+|AlP_fIrs82I-@J(~iwVAgeF}0O~QNEWjN5K`d3VFL&?fUu=h%5ZtM-_B6HHYstFx{By1a$_{7j~8r@lS<;zm8t6mS)sB5eJ}p=updsjc9aEyK+180At%^I$ z;LjHQTIa#mq3ba?botXjzOx0SZaw2QjNQFwpJ)tRHC;Xw@>s3vc6qq#-hH0bOXOA| zy0IWC<6=YSozXtjvV<;{SB>&*PsGDkEkb78H#$XuR+uei` zAgaEUA${liK6?`Q8l($RtJPuMBVgd*&nS9P&@gi0N)?S&a9qY{Nts;dw}Crmz^L@; z2G}tLPUcqGJc^m$8PFYWACENJ5-fkD>ZMp4T}sP*S`Z5K-G%qB9~qSeul-KQBmCS{ zPR-2j{Cipn6N%s#e=zAZtjlZ|Btgxem}W@aHmKbgCjx;pgMEFOw7g4Twt~p5b+z>! zfq>{dug7$EM9~3mPkUS+jL&aTl`{6r4r}g`i*+M;oy!EdcRkkYg#zMHgs*DHnmOj3 zny@71F;t+Rq*Ap>&8w0DIjOmp9^dP5+n~K*%6x|?TGnohU*5Y34KG5QfUd`_a_Jbr zERAh&uUhbqWT61_vjv%Xz68+#w}Et8rU8!nQmhZ14veMmMP*}IcHHI{V6eJn${Oz~YWdI^B&F4+i&-{i6 z(1=}b77Swis@_nGW68HapdD6tJ`$IAC=DTcxcj)dV;r=Uc=tgwQ+T8UT#oo`2xO(1HN$+9fk;_Pb=ora09 z@}6@BMN3VEr&O+W=GnR}HIyn4=O#~&FKh=^`}g?uPHf<~ z!}CK;t%j)v&s(S6=k<#OdQ5bs_Ck6p2YOSd69jQ}y0&0>0~VTw5Z;ggO57P!w}*ny zC|~yvSLKU!y{paDW#hh&rCiZPHp^$f>@&>%s$y-@NcXEy)HL_YTOjQyT#0hK;PaVU zM|iQG2=3}qcRl!`4R;c$YI%&VtRnTjoTrRajXyd6>lgkt=p2L|@=lduA(+F3#1L*_u>7J&kF!1E~V0@S6 z5jsUDYLo)%&ZqPF&{ z73i}Him2$8!Y8^|uLHhGr|05Z5Qh#( z{f=gwh)5T+zn-!0%7#@gi-NGk7SeVVV3HSf0@@1VO;DYvaQGpM_ob{mN}`VTBL&Hg zW|$MLQRz8C!-X3aq9-sbV>3HHwzW6!CyJ6HG@yh-4^Ss0w|+ved`kR0 z&6=wP|`kTC^*?I-);-uzJNjmo@1KVoJD$b+v>_M7#-4P`C9CasE77e` zyp&RCMjk|zK-oONgn*&Oww}N_5Lr@wT(PuI?IZ^PW;ODUYro_fc)jMm1dF_2dTvj3 z4o(D|1bFVCZua(N(!rF^PF#D*WKqa37Nc`wXGBe)6@Q)8dnvcK0xe;p;C@6n_~E=*ydbC7n#w+wi~!nGqFZU>cN;_J(kd?6Ikd4rNTgpc*(9SZvp zipdVW3VaW{Cnf2rxY_UE#zvQ|`yqv(S-p-?*zOrb05m1uO{xx|9uT4e0MX`*u2wz z1WgU?R~=Dc`bjDXq{$x{^n$^!Fk6tm1*rrM&}erV>2SLE2OWEng4k;!xf86|*wGG> znEuz_Gya%!9+bIqW^`W>ibU_i4HLI^A9cN}mOb>{7>U#W)b8{Gis84Std=cd5BvxAvqX_;@P#4Jg=3kr=%L64LQDj z6}a~mIZ4RBq2&m2p5@y$6`k>7bG_+TLbm;0|2fL=sUj8i(*sRnnAg{z#zlf`?Y^nW z122QcfY1`Ua`lfmtw>*C!RcO_S$b6m?W%%iKGVG$*4fho*V{4Zox6mP&X^*Zk);UR zgYB@>BB8EpD0xldk(;50NW~L<-q4)FEdCyK$Ubuc_hG3xoS8VrA-Y^u-3r-*(7Z-J z3Ysnyy{Xl^{mk_t2G6#c!1qD{A0ZZr>dvG=G^fj)R_9-${R|fGHgu_Y(kKwJ@k#iN z&p!0@AsyW3;VUJVJ!vM;3IJMQ*{anEx>HU7a8 zq0_1+URu|c>xpo;F81N?uORJ4mu6A8N`Nu=4bxVvlhj1SXIaY=5#HwMxJ{8UuBo0tkYM&v?i1EGT7MExs z=lAB0Uz{4T^rF88Vx3vJu>4zf(pxPB+pXyAa7KVlHK{ddQ#AL8@P3lOdF|%43%*B4 z83HTJos6O@PMmMh@hM&uWG2~PQ|7UOf|dceJj_tBHSd;NLV#3;%qD`|g*3PQg;OZ` zyXBa674(SHbORJJLUv=(@5lCCEV2-c3DRYPgtAgwTH<1Pe`k}my_Nai#9JO6huP=# z4*@;NkETbvA7`HHSDqci$u&MGJ#1`jTQ*ow!2?8j_zZ!OQaTRe#6Ks33jwyO;yxAF zJRDjUxg5&+re=mXP&C?C5kf3C73z8&K^!#;iTghcv<-9{xo@$4uLoX?T^(4nsyVEj zGg#l!iNumfFGuIj?P5HK*+*T|8w-!8iavM~D6?q5_k4oy11}UPJ>|z&&A8E(HS*fb zlZ}o|?f=*i8+)1UeoX^kmIMq57hFyIZXPMWPX@a0k1@Ns3vUn=QLlvNX3TyZUhM%n zafm6TaMpAW`laot{a;^$nNk@wod?vp;|CR(FxTrB;H5;WeohOH`-bE~gd^(Mh$z@4 zDQujAC){j(BVtzUWiCUb`M~GLGiUi`_we5Edj>(y15h@MN*#hXe;Vsq!I_664YODf zYKE}I@-jhngE6yOg|V}X2kVzfs40`A4h(@#VK4cF!pDmyJ}{!u;IM!wq9gHqq9HgEiXjf zr_{n%p4Hct4%Nb==PHa^w05wC$&>On-m)3~#vW7Vkum3Z3oV~){Ps2%(q;43x8k92 z*Y(<&x6FTIIdYogt+6?1<@S?lU(h2(MWw1eXJ&+7>cV6?upfAjSR53T#0XlRD~-OA ze(UlwwIj4c*c4EdfinGqJ8Gu1v`wA*REwhd1$r8BkO_)zAl*c9q+$ZsVs4wOdT5A! z4znc^)y?E_x270|0d(IJ@G!yCJ(K{(oG+W#{h7*rg)Mo0u%n2(z<@47 z+H(4^7LD}HDMW6=Q4I?!79iXVBe8K`meUXU_^4*?C9>)P<&xr3Rs#l@Mxpa=Wb;Yx zzFVGefO>K3*xR66}kcIWGjy%mzElodTzw{XwdW5Dg}?u(r7tkB{5 zg(k1aD^sV@nio z#>)4WDj;^KLAG)8&Rr+!y{yVP&;^4xt^Q^0upCJuIY8QyM?~PT+`xm>UvWz1b4YMv1hUb1$ z_ja>!)0oX2XC~(O`EnHx*w?f37rpL^3rd(}de-CNs`R0OoYHqP!v3C@dfy)tuL^vh zDpXY>b<&Xl==~s5A&)W$oV^VlUcFK`-?5FH4XlP=&coJ2%>hEj#b{QGT z8oV82Bh6`qd(ca7+$y*9w6jfey<-h9BP0#1R8HM*WE-rc z3<9qg&eKKSI6yDexb5`aQxv8!#mh7B_}$x1m4%j+q?ITy{Hx5H~~ zEKh1J_j*&4R&kL1h&0Bwr`(ZnqWnHDXV^mI2Vr7H;SC&}dj-8hIkB2W78V0T6A*dB zcBdFY(s-GaUY!fhQ?!t(8CQb{67kcc3V`lho0SlCI5hs?NbeVRA0o;Y%_8O0r%Q6{ zs}XrK4rQ+JCgjT9)-V%T1jTxPC1<+S<2wl9<}tVL8>QB6C)};+nQ^TjhfLKSF3AP~ z3z6%m8;;=LVGoy?8fx4b8etzEk2 z97^ZB8R{4bCnTp#1*-ZZGW6)e4Lv~LmYD~j|0a4GFChxUF?7h-Eb7n67i`cJ7wUpD zAhq4n)+=D+Dt?|5wE~(`LS^Ic?;C9xII*}SEY3R(&1=vquFk#=us}H9PW7_APyf14 zoiYHewlvner%*WEr<|ijf`bcRP03n#5%`iJ)JB?zk_rv&IN{2?Mp9D+a-GZlpOtz1 zH`lW+0_hU8JfIX!pQkprk54HUhov0?)kNY}chJ@Q&Sk=D8d9HM3{i9IF7!GxoRurY z>i5eeT|HLp1sD)apR-AWt(pUJ0R(S27t7~DpzGqyBUT1pGC@adHJC4rVnPc0jXi1< zh@wNJ;2nHF7C~-`2F?t+u)W1RVW%T70Ut+>+Cg&vJoh#~Hg!&X!>peEDCYX${?}2K z3G~uhf#@PFRVN#qnv3|AM2#_p<2lOdzuRSz1%)%r>AWJ#V7z>?_8OXEjSDnY{GUGX zyi&4vbm43tq?QswQNfK*ecq9%Hmk!8Pvq^{^4thxVu=I$0GB)j{x|*WI)RJZO<^;O z4!?-p`eN1NigGYLX#vJbxf<~sWFDm|vVL#Xd0!35<02RTW*+taBNUS1#t)USVC$9< zi6jColxWQ)O)#oj1i91o3p;%mkD{u1+Nn+ZzUpnW66{WtLyDMY<}xfXQF23M+jg&m zohowA?q}@WYK0GB&w|Y7ahdf#gAI_KFa^!xd{#gi6fk}O-loBMl@B*tGDRMlekKH~ z_u7^n@=#2ZKOlkt5WDH!52f$nO8bYC2J$4r5ZY!4$&r><*3+QcM?3m*W)I) zINE!NFSrkOwB4^!0gkf?`z|0meXo}!9W=&F(%tC5WQ=$IkyK=O%W*mGuNIEN)n5qiNjp9#2!vJ9zp*=K!s6)03`;v#(wFza zYZhY&P%4(!V+umyO>0#iSrmX??|l{T2mZmXl9nnGhZ6K=ab#~M2vn8|%RhdUpS7`jWPuX;ynNm== zcGFa>k*x6NJgBhU_xRld7G`C*A)6B83hlDTZIHKpm45tgx8~pd=vwW$PS4XCuVf)K z2ekoeN(~--T%gl(wrBX7qEO@xnEg{YxGWLd?k&uwX56pV#a9KpzV5fLwEsX2H#x<4)0#D;VbwEOh!1y> z_uak?jY@?p*@-M*ZozglNqF$eptf6iJ6hXSbzvJJ=xRYJS!m?2WEh_q_4Q4pHM{0; zsp&S7NIe{1R!X%!n0{y(fhKc{6>bulR$;?xE7Qk!^Wt z5+`-}&kfolm3XKT8gvBAIQH_)LK)FxjO-}&oQWUO8UaRr%k#V$&=|~3_C3GH)wBGB z_C}S+QnzlkiH&JGc|&m3!y-qIe~|2MORm_IT3d%YRyM;nm00=4LRUmIL`e+OSg-X& zn0f96`i)olo+~ePXT*lP3=LMu%ju!W!%EBGaRyl&CyYe>QDWyxZmEkWRy20y;Np3& z3K8r*Y+{^{Z20L7x6nhT>PkVdxzwK>^b^|S=7(r`Fc1q}cK$amED=9fY zQ)b-gN}qjv8o5LN;ns(MxTVWaRuTOSGA+2RN@F)b`-wab==&wWxlkfYeXKz*)Z=$BDdWl4i(4g z^a;){Q5}LxtHKD)@~4ZujKVMMVRFBSiK7+g4RM0)<#sQtp&xQT5&ul-7$?W8Rh|nE zdA8#n_)DXeb_f(lo63`y#ZPqWT{c1t24V5u9-@#6Iojzg6|_5fjHOObdA%iwKJ67< zwmthQntY0E9Av#sz&7zs$5OdV+>=vMF)Zo|BU@gs)3ibTQ&FWd*ymsuG3eM@GC(#HIDxajJlZ{h$*a!6T0KSmY5k4XR5>&n=;2Da&#{5l> zF8!s;khl~Nq{TXjwXfv1@rBdn@!9HCQVdP{wPUPfyBKXj4;H9+`zGRG#`*#~pbd`o zh}V=?GH*PwsRmRZK~2mzS?h7?rPud}%R1qRv(qQTIoGOa-9e=-QQN?~(USLF@s63) z>R-Eb&YU{hG}HA`+)`S@p>rDmZiN2s=ki@&3obYxIG0%qB~mo>^6sC74YCh5Yx|Nl}+ss#OQSu8tkMjJ2CI^s>+2FtcL< zh|D1%|GO>wi(F47I%#HgF8}RJF!UExP?xlVFYu)r-jMAywoVi9E( z^rK7=0@5Fv`dr)#LBo7jEHPN85JnIiWM#fML6m)9V8V%TJF1e3?^-J6UtlY-%up^n zZ5jFAh{uGT9yWr_;*3pLqaP2cQ6Wniobis-6EI3&)`9kCOYJEYA!j5V$~)EopCzSRIv<{$ekoeqbYffUWs0Eo{(fd9KlZ`XqPd6(=EAM2I&UMsK4;E$;+A9fU` z$-6`AoOhT+14Qg?-ZDaa)(NR&{%aaqPO9cY$HFP4Q=#)>o=@Oju~2)DR|+ruG+0Gq z;$5l*bk3RbknwAmb4HVBMPGb}PXZ=%U|$-|x=+HQMzY5o9;_5UT4H8jdVX0Q6-Of! zbfyK*GH5nOv?h4>(l=Gy;vqBG6`r^3OGB{EN<;|k5i@@GR^C`R8VlznOxN(m4}^-@ zuXfPR+&iOZo%sT^iGUo8-pFbyiLh@nt(>aM6Xx%YK`KuqrVWn(jHK{KsautWB!!2; zEw7fp4sfzG#W{!L-aCB4R$_Cl=qfsePd@%RZnXnV;T3{yL7@aa)8e&tLH2pS`XG;I zBO@PIG=?NxsHQxAl;IE^?3ZS9O8;Vc#KGjr%QE)alI=g0U4FKrS4IlnsbdsdUkA!Q zC160IYxN*Oo));gQ*TQM#T}b9*0da=UqG_}lVYQ;9e#2*Z4jEL0OxqQb>)lHKi`m- zF{p5RiGBa-UO~=A$NgjNS$<<}uS0-O_+*hq#vM^`2FAm>QOQa?2*G!<-n90G43t`m@DD-yJwOm!=T9w9pp0Bh5TQ0@9z3aB zy1f$RPm|aZKVFn~xYGIi#wDhb-wrL`_M{S1**vj#wQMs+bM)v*4A?2=U2y5F z_F!(N>B$h2`6g~mu1Q3-G^RDlP0y%GDV*$0ZMDn&AcMxH8jnJQ;rDA0Yz>kQ)kl>8?z%-vY8SB-R)29#=K3 z#^uX|Y=&rH4N3)t8wHg6iwNYl*_b_F$|VD%a9cnHyk$GdZrnz;EKq}t!8;3IV9Dd9 z|NhP~If{ZcW}EN;obFFw75Jt10a|>RwIEI4lJ&FACYzDm_okq~-1e=5o5s&rAukZ_ zn9b{-!^M)47;Dq$ppl2_uG-oSpn~qX(eQofA~EpNp+!C&$%3RWj50G}8x;LeJf=7r z#Gl{i@o7yy1vflt>ZO|-y2D?&_oZck2q_v;VgkC#ukT8)zTt+iD!=kz8x5Iw>zMYyK41AxP zqpflEEzgo5@_3j~M*PGXx`}wfm@W@zQcZv<662b_Ck7n*hL)@a1Cu6*M;wfUrMKaR zPem6A!p^Y>0U^ri4>>ZeWQuZrE&4XCqV7ikQDIfK8l{1O*SGx}cDNZ~vAjr%_H@NZ^pn*Rd;Bs=VO)r~uqTWmyo<5^pCYpg&w>tn2WMHz$Xn6# zg#kaTgtSb?iiT!lVhQwiOJXvuacA>c{hw6K4p2BV`WT{%B7u?$1W2g2x(7PK$2Jvw zb3Xf*h^O?i-f6Az#dD974wwRzocnPq`tnNGpx;duA-vfHx!&L2?`nNWkkMB9AJjK1 z{7u6M^#ur&gROp)~b}MZT%5hS!ItfRYu>i)0Ui#`-@2s0mm0^^G8Q0#pQ%F%-Jk z5d^%sNpApRFXy20m7FBs*gN3nXe058CaW8csb-U{72-<%)Yz^_6zufvI`){fzv6!u z$vOv(w?t2dhsK2bl3(j0)_rk7j)eIIy9ZF;a%mE$XM$3hO+7lt6lE+cY$#Bka|k$% z<UX--5dteFx1#lD|8{C6cpdk)2Yw!q=(I~COEn!<+ zOrws^$Kg1{eC5b;_Y7Vzo#mmlmq1@D-NBr*M2%z~Nv;OTbE{W(G?#X8q&xo()%jMO zns{Nnt!?!1jmDl}+`iv@GzK)dThy6nwYK|^G=FgHDk+EgxTmo;kMGwGSF}#d%u;lI=A7j5*DX0-`hQPIFn)Ou2#j2%}jd?e^;+jvkoX8$Auyy_9O5Gsu9C~v!&*`(geQa>Q4MX!HR#g9cAhoZX6 z)FAW`&}e6173gC}SY&t5z)SvkQP9XUMdCEDHYwAyMwYo3pSMvJ(6<2y=D0t?z)8;j zv;Y7G-$_J4RIIcyJTef^ulNdwdzo|Zrd&Hm4GSM>i9`*P1=AEpUd_5zM|v(PXe zTQw3T5SWn_m90^T8U-we?;=O3v-KSB@~QGZg;M#HS^B^y!WQEr`=XtvE{2jq5o#bB zgx@o^H;SZk)Ma$`MW4>ln4rBvjqjSE$t@t&X;nOxF>W*qNQGHL7vhaB^TRN6lec^! zoq-+v9;_TRYYWPnXhtM@+yZdt@Q~~4+2X#mZ!cd|WO~J5J=u55YWCrP5 z`17Roj(R)!B9A{fE^m)o;jE_QhKXuV12hDEOAN&$f~Gf(QhKWjqX~-NOY*sB%RnyGLMH@dWQ&6uT#aiWV@qm1 zs3k99u813BgEEGK*DgF zYQ`^BjlW$*`=|qW^~w5@w=VU3ly?)&CPHBIdn$?B&@-64@`9c1&sPql`^Nb#f$e>I zytl{{>nOL?;UlXC{;EW79lRpDJ@^OtKzm-c2K6W<ZRREcD$ zgmp{#bb<`q`$6Bv9vdSFl@N*^1IYF{;WA~_k7a9S*~dDM58v<`eROsXVD(}5Fw^WI zte11)qa!6J4PZ?yDUkLG8D$jToT}EheOE+CeMvrXKqxD0*I_&yn zOgBH}PQkF`ig)j%HW3&f>7zxix(HXVX@f_SMTE2+#!vCR+b5Lt%Ge2LIM( z&1#N#yDk7I{~xbe_I}=0f1h>?7VM0>ifhX`lO&&7^}-M(Th@vbGwteJowwA`yZt=c zl#Zap@vkUb>a!hwqap-m)*+}>lU~~@sK$_$&sTYS6Dt;Naf(o0N{sK4Zfh!Q~uA@G`&P> zQIFzi0P2FrkMnB8peQtP;VcJR2E)frmD;6pJMH635vYzLK;Es+L~GC<_J>0-Di5B8 zwgp&IA1d`y7RxEyuB8GLX5$hh;%VZ-T{(FW3;>l^FAMQaniLh$o}d^LNP^ZqPgK*=I5h{D=2TB_d48i?Hh1}Mcf!9TV^N1!|cg!O!?$&6;B$2N|qx8fcm_GfjLnlYv)^y zg7HX%8ET0EVa=h*q@w-9ejU;kKmF-vqSf>58+w^E>Q!F5XhEr7l#zI56Ah)5F%WQu znrK3))c`+x$1YO4Jw?iKX37&_KTe(#H2iow0T}y=LTj4&=oCNWd#W#Oj}Mfq<9x(- z(hZC+tUl!GFZ7T0l+DvM2EcAg&+^L&1Tg{-D?EO)e=FD)>2tDh?|f?a0*|E=hGj%1 z+BclL?2=}?{Bpp%oZ>y3RO{kKECME3vQiG6nsv)K@09lGe4kX0w#f7n1^b++C{3R@ z?AzouUbeN9Sx6?4P=1Bjt03yma4SHP3Ebm+vkPeu5a~F3C`botsnwY6sg~vjT79$g zqoeaQ8b@G#BQ0H + + + + + + + + + + diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index f14574508c..845816562b 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7906,6 +7906,9 @@ "restrictedGroupAccessDesc": { "message": "You cannot add yourself to a group." }, + "unassignedItemsBannerSelfHost": { + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, "unassignedItemsBannerNotice": { "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." }, @@ -7962,6 +7965,55 @@ "errorAssigningTargetFolder": { "message": "Error assigning target folder." }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations":{ + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, "createNewClientToManageAsProvider": { "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." }, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.html new file mode 100644 index 0000000000..15b2519dae --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.html @@ -0,0 +1,29 @@ + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.spec.ts new file mode 100644 index 0000000000..94cec5f627 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.spec.ts @@ -0,0 +1,174 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { BehaviorSubject } from "rxjs"; + +import { SYSTEM_THEME_OBSERVABLE } from "../../../../../../../libs/angular/src/services/injection-tokens"; +import { ThemeType } from "../../../../../../../libs/common/src/platform/enums"; +import { ThemeStateService } from "../../../../../../../libs/common/src/platform/theming/theme-state.service"; + +import { IntegrationCardComponent } from "./integration-card.component"; + +describe("IntegrationCardComponent", () => { + let component: IntegrationCardComponent; + let fixture: ComponentFixture; + + const systemTheme$ = new BehaviorSubject(ThemeType.Light); + const usersPreferenceTheme$ = new BehaviorSubject(ThemeType.Light); + + beforeEach(async () => { + // reset system theme + systemTheme$.next(ThemeType.Light); + + await TestBed.configureTestingModule({ + declarations: [IntegrationCardComponent], + providers: [ + { + provide: ThemeStateService, + useValue: { selectedTheme$: usersPreferenceTheme$ }, + }, + { + provide: SYSTEM_THEME_OBSERVABLE, + useValue: systemTheme$, + }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(IntegrationCardComponent); + component = fixture.componentInstance; + + component.name = "Integration Name"; + component.image = "test-image.png"; + component.linkText = "Get started with integration"; + component.linkURL = "https://example.com/"; + + fixture.detectChanges(); + }); + + it("assigns link href", () => { + const link = fixture.nativeElement.querySelector("a"); + + expect(link.href).toBe("https://example.com/"); + }); + + it("renders card body", () => { + const name = fixture.nativeElement.querySelector("h3"); + const link = fixture.nativeElement.querySelector("a"); + + expect(name.textContent).toBe("Integration Name"); + expect(link.textContent.trim()).toBe("Get started with integration"); + }); + + it("assigns external rel attribute", () => { + component.externalURL = true; + fixture.detectChanges(); + + const link = fixture.nativeElement.querySelector("a"); + + expect(link.rel).toBe("noopener noreferrer"); + }); + + describe("new badge", () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date("2023-09-01")); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("shows when expiration is in the future", () => { + component.newBadgeExpiration = "2023-09-02"; + expect(component.showNewBadge()).toBe(true); + }); + + it("does not show when expiration is not set", () => { + expect(component.showNewBadge()).toBe(false); + }); + + it("does not show when expiration is in the past", () => { + component.newBadgeExpiration = "2023-08-31"; + expect(component.showNewBadge()).toBe(false); + }); + + it("does not show when expiration is today", () => { + component.newBadgeExpiration = "2023-09-01"; + expect(component.showNewBadge()).toBe(false); + }); + + it("does not show when expiration is invalid", () => { + component.newBadgeExpiration = "not-a-date"; + expect(component.showNewBadge()).toBe(false); + }); + }); + + describe("imageDarkMode", () => { + it("ignores theme changes when darkModeImage is not set", () => { + systemTheme$.next(ThemeType.Dark); + usersPreferenceTheme$.next(ThemeType.Dark); + + fixture.detectChanges(); + + expect(component.imageEle.nativeElement.src).toContain("test-image.png"); + }); + + describe("user prefers the system theme", () => { + beforeEach(() => { + component.imageDarkMode = "test-image-dark.png"; + }); + + it("sets image src to imageDarkMode", () => { + usersPreferenceTheme$.next(ThemeType.System); + systemTheme$.next(ThemeType.Dark); + + fixture.detectChanges(); + + expect(component.imageEle.nativeElement.src).toContain("test-image-dark.png"); + }); + + it("sets image src to light mode image", () => { + component.imageEle.nativeElement.src = "test-image-dark.png"; + + usersPreferenceTheme$.next(ThemeType.System); + systemTheme$.next(ThemeType.Light); + + fixture.detectChanges(); + + expect(component.imageEle.nativeElement.src).toContain("test-image.png"); + }); + }); + + describe("user prefers dark mode", () => { + beforeEach(() => { + component.imageDarkMode = "test-image-dark.png"; + }); + + it("updates image to dark mode", () => { + systemTheme$.next(ThemeType.Light); // system theme shouldn't matter + usersPreferenceTheme$.next(ThemeType.Dark); + + fixture.detectChanges(); + + expect(component.imageEle.nativeElement.src).toContain("test-image-dark.png"); + }); + }); + + describe("user prefers light mode", () => { + beforeEach(() => { + component.imageDarkMode = "test-image-dark.png"; + }); + + it("updates image to light mode", () => { + component.imageEle.nativeElement.src = "test-image-dark.png"; + + systemTheme$.next(ThemeType.Dark); // system theme shouldn't matter + usersPreferenceTheme$.next(ThemeType.Light); + + fixture.detectChanges(); + + expect(component.imageEle.nativeElement.src).toContain("test-image.png"); + }); + }); + }); +}); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.ts new file mode 100644 index 0000000000..bf5f5bd311 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-card/integration-card.component.ts @@ -0,0 +1,93 @@ +import { + AfterViewInit, + Component, + ElementRef, + Inject, + Input, + OnDestroy, + ViewChild, +} from "@angular/core"; +import { Observable, Subject, combineLatest, takeUntil } from "rxjs"; + +import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens"; +import { ThemeType } from "@bitwarden/common/platform/enums"; +import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; + +@Component({ + selector: "sm-integration-card", + templateUrl: "./integration-card.component.html", +}) +export class IntegrationCardComponent implements AfterViewInit, OnDestroy { + private destroyed$: Subject = new Subject(); + @ViewChild("imageEle") imageEle: ElementRef; + + @Input() name: string; + @Input() image: string; + @Input() imageDarkMode?: string; + @Input() linkText: string; + @Input() linkURL: string; + + /** Adds relevant `rel` attribute to external links */ + @Input() externalURL?: boolean; + + /** + * Date of when the new badge should be hidden. + * When omitted, the new badge is never shown. + * + * @example "2024-12-31" + */ + @Input() newBadgeExpiration?: string; + + constructor( + private themeStateService: ThemeStateService, + @Inject(SYSTEM_THEME_OBSERVABLE) + private systemTheme$: Observable, + ) {} + + ngAfterViewInit() { + combineLatest([this.themeStateService.selectedTheme$, this.systemTheme$]) + .pipe(takeUntil(this.destroyed$)) + .subscribe(([theme, systemTheme]) => { + // When the card doesn't have a dark mode image, exit early + if (!this.imageDarkMode) { + return; + } + + if (theme === ThemeType.System) { + // When the user's preference is the system theme, + // use the system theme to determine the image + const prefersDarkMode = + systemTheme === ThemeType.Dark || systemTheme === ThemeType.SolarizedDark; + + this.imageEle.nativeElement.src = prefersDarkMode ? this.imageDarkMode : this.image; + } else if (theme === ThemeType.Dark || theme === ThemeType.SolarizedDark) { + // When the user's preference is dark mode, use the dark mode image + this.imageEle.nativeElement.src = this.imageDarkMode; + } else { + // Otherwise use the light mode image + this.imageEle.nativeElement.src = this.image; + } + }); + } + + ngOnDestroy(): void { + this.destroyed$.next(); + this.destroyed$.complete(); + } + + /** Show the "new" badge when expiration is in the future */ + showNewBadge() { + if (!this.newBadgeExpiration) { + return false; + } + + const expirationDate = new Date(this.newBadgeExpiration); + + // Do not show the new badge for invalid dates + if (isNaN(expirationDate.getTime())) { + return false; + } + + return expirationDate > new Date(); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.html new file mode 100644 index 0000000000..a0c82d2f34 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.html @@ -0,0 +1,15 @@ +
    +
  • + +
  • +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.spec.ts new file mode 100644 index 0000000000..e74e057e06 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.spec.ts @@ -0,0 +1,81 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { SYSTEM_THEME_OBSERVABLE } from "../../../../../../../libs/angular/src/services/injection-tokens"; +import { IntegrationType } from "../../../../../../../libs/common/src/enums"; +import { ThemeType } from "../../../../../../../libs/common/src/platform/enums"; +import { ThemeStateService } from "../../../../../../../libs/common/src/platform/theming/theme-state.service"; +import { IntegrationCardComponent } from "../integration-card/integration-card.component"; +import { Integration } from "../models/integration"; + +import { IntegrationGridComponent } from "./integration-grid.component"; + +describe("IntegrationGridComponent", () => { + let component: IntegrationGridComponent; + let fixture: ComponentFixture; + const integrations: Integration[] = [ + { + name: "Integration 1", + image: "test-image1.png", + linkText: "Get started with integration 1", + linkURL: "https://example.com/1", + type: IntegrationType.Integration, + }, + { + name: "SDK 2", + image: "test-image2.png", + linkText: "View SDK 2", + linkURL: "https://example.com/2", + type: IntegrationType.SDK, + }, + ]; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [IntegrationGridComponent, IntegrationCardComponent], + providers: [ + { + provide: ThemeStateService, + useValue: mock(), + }, + { + provide: SYSTEM_THEME_OBSERVABLE, + useValue: of(ThemeType.Light), + }, + ], + }); + + fixture = TestBed.createComponent(IntegrationGridComponent); + component = fixture.componentInstance; + component.integrations = integrations; + fixture.detectChanges(); + }); + + it("lists all integrations", () => { + expect(component.integrations).toEqual(integrations); + + const cards = fixture.debugElement.queryAll(By.directive(IntegrationCardComponent)); + + expect(cards.length).toBe(integrations.length); + }); + + it("assigns the correct attributes to IntegrationCardComponent", () => { + expect(component.integrations).toEqual(integrations); + + const card = fixture.debugElement.queryAll(By.directive(IntegrationCardComponent))[1]; + + expect(card.componentInstance.name).toBe("SDK 2"); + expect(card.componentInstance.image).toBe("test-image2.png"); + expect(card.componentInstance.linkText).toBe("View SDK 2"); + expect(card.componentInstance.linkURL).toBe("https://example.com/2"); + }); + + it("assigns `externalURL` for SDKs", () => { + const card = fixture.debugElement.queryAll(By.directive(IntegrationCardComponent)); + + expect(card[0].componentInstance.externalURL).toBe(false); + expect(card[1].componentInstance.externalURL).toBe(true); + }); +}); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.ts new file mode 100644 index 0000000000..058d59d702 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integration-grid/integration-grid.component.ts @@ -0,0 +1,15 @@ +import { Component, Input } from "@angular/core"; + +import { IntegrationType } from "@bitwarden/common/enums"; + +import { Integration } from "../models/integration"; + +@Component({ + selector: "sm-integration-grid", + templateUrl: "./integration-grid.component.html", +}) +export class IntegrationGridComponent { + @Input() integrations: Integration[]; + + protected IntegrationType = IntegrationType; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations-routing.module.ts new file mode 100644 index 0000000000..91402113a9 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { IntegrationsComponent } from "./integrations.component"; + +const routes: Routes = [ + { + path: "", + component: IntegrationsComponent, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class IntegrationsRoutingModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.html new file mode 100644 index 0000000000..a2f2188861 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.html @@ -0,0 +1,16 @@ + + + + +
+

{{ "integrationsDesc" | i18n }}

+ +
+ +
+

+ {{ "sdks" | i18n }} +

+

{{ "sdksDesc" | i18n }}

+ +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts new file mode 100644 index 0000000000..10fbaa1f3f --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts @@ -0,0 +1,77 @@ +import { Component } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { SYSTEM_THEME_OBSERVABLE } from "../../../../../../libs/angular/src/services/injection-tokens"; +import { I18nService } from "../../../../../../libs/common/src/platform/abstractions/i18n.service"; +import { ThemeType } from "../../../../../../libs/common/src/platform/enums"; +import { ThemeStateService } from "../../../../../../libs/common/src/platform/theming/theme-state.service"; +import { I18nPipe } from "../../../../../../libs/components/src/shared/i18n.pipe"; + +import { IntegrationCardComponent } from "./integration-card/integration-card.component"; +import { IntegrationGridComponent } from "./integration-grid/integration-grid.component"; +import { IntegrationsComponent } from "./integrations.component"; + +@Component({ + selector: "app-header", + template: "
", +}) +class MockHeaderComponent {} + +@Component({ + selector: "sm-new-menu", + template: "
", +}) +class MockNewMenuComponent {} + +describe("IntegrationsComponent", () => { + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + IntegrationsComponent, + IntegrationGridComponent, + IntegrationCardComponent, + MockHeaderComponent, + MockNewMenuComponent, + I18nPipe, + ], + providers: [ + { + provide: I18nService, + useValue: mock({ t: (key) => key }), + }, + { + provide: ThemeStateService, + useValue: mock(), + }, + { + provide: SYSTEM_THEME_OBSERVABLE, + useValue: of(ThemeType.Light), + }, + ], + }).compileComponents(); + fixture = TestBed.createComponent(IntegrationsComponent); + fixture.detectChanges(); + }); + + it("divides Integrations & SDKS", () => { + const [integrationList, sdkList] = fixture.debugElement.queryAll( + By.directive(IntegrationGridComponent), + ); + + // Validate only expected names, as the data is constant + expect( + (integrationList.componentInstance as IntegrationGridComponent).integrations.map( + (i) => i.name, + ), + ).toEqual(["GitHub Actions", "GitLab CI/CD", "Ansible"]); + + expect( + (sdkList.componentInstance as IntegrationGridComponent).integrations.map((i) => i.name), + ).toEqual(["C#", "C++", "Go", "Java", "JS WebAssembly", "php", "Python", "Ruby"]); + }); +}); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts new file mode 100644 index 0000000000..f11048b6a3 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts @@ -0,0 +1,113 @@ +import { Component } from "@angular/core"; + +import { IntegrationType } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { Integration } from "./models/integration"; + +@Component({ + selector: "sm-integrations", + templateUrl: "./integrations.component.html", +}) +export class IntegrationsComponent { + private integrationsAndSdks: Integration[] = []; + + constructor(i18nService: I18nService) { + this.integrationsAndSdks = [ + { + name: "GitHub Actions", + linkText: i18nService.t("setUpGithubActions"), + linkURL: "https://bitwarden.com/help/github-actions-integration/", + image: "../../../../../../../images/secrets-manager/integrations/github.svg", + imageDarkMode: "../../../../../../../images/secrets-manager/integrations/github-white.svg", + type: IntegrationType.Integration, + }, + { + name: "GitLab CI/CD", + linkText: i18nService.t("setUpGitlabCICD"), + linkURL: "https://bitwarden.com/help/gitlab-integration/", + image: "../../../../../../../images/secrets-manager/integrations/gitlab.svg", + imageDarkMode: "../../../../../../../images/secrets-manager/integrations/gitlab-white.svg", + type: IntegrationType.Integration, + }, + { + name: "Ansible", + linkText: i18nService.t("setUpAnsible"), + linkURL: "https://bitwarden.com/help/ansible-integration/", + image: "../../../../../../../images/secrets-manager/integrations/ansible.svg", + type: IntegrationType.Integration, + }, + { + name: "C#", + linkText: i18nService.t("cSharpSDKRepo"), + linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/csharp", + image: "../../../../../../../images/secrets-manager/sdks/c-sharp.svg", + type: IntegrationType.SDK, + }, + { + name: "C++", + linkText: i18nService.t("cPlusPlusSDKRepo"), + linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/cpp", + image: "../../../../../../../images/secrets-manager/sdks/c-plus-plus.png", + type: IntegrationType.SDK, + }, + { + name: "Go", + linkText: i18nService.t("goSDKRepo"), + linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/go", + image: "../../../../../../../images/secrets-manager/sdks/go.svg", + type: IntegrationType.SDK, + }, + { + name: "Java", + linkText: i18nService.t("javaSDKRepo"), + linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/java", + image: "../../../../../../../images/secrets-manager/sdks/java.svg", + imageDarkMode: "../../../../../../../images/secrets-manager/sdks/java-white.svg", + type: IntegrationType.SDK, + }, + { + name: "JS WebAssembly", + linkText: i18nService.t("jsWebAssemblySDKRepo"), + linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/js", + image: "../../../../../../../images/secrets-manager/sdks/wasm.svg", + type: IntegrationType.SDK, + }, + { + name: "php", + linkText: i18nService.t("phpSDKRepo"), + linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/php", + image: "../../../../../../../images/secrets-manager/sdks/php.svg", + type: IntegrationType.SDK, + }, + { + name: "Python", + linkText: i18nService.t("pythonSDKRepo"), + linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/python", + image: "../../../../../../../images/secrets-manager/sdks/python.svg", + type: IntegrationType.SDK, + }, + { + name: "Ruby", + linkText: i18nService.t("rubySDKRepo"), + linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/ruby", + image: "../../../../../../../images/secrets-manager/sdks/ruby.png", + type: IntegrationType.SDK, + }, + ]; + } + + /** Filter out content for the integrations sections */ + get integrations(): Integration[] { + return this.integrationsAndSdks.filter( + (integration) => integration.type === IntegrationType.Integration, + ); + } + + /** Filter out content for the SDKs section */ + get sdks(): Integration[] { + return this.integrationsAndSdks.filter( + (integration) => integration.type === IntegrationType.SDK, + ); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts new file mode 100644 index 0000000000..0d26b626f1 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from "@angular/core"; + +import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; + +import { IntegrationCardComponent } from "./integration-card/integration-card.component"; +import { IntegrationGridComponent } from "./integration-grid/integration-grid.component"; +import { IntegrationsRoutingModule } from "./integrations-routing.module"; +import { IntegrationsComponent } from "./integrations.component"; + +@NgModule({ + imports: [SecretsManagerSharedModule, IntegrationsRoutingModule], + declarations: [IntegrationsComponent, IntegrationGridComponent, IntegrationCardComponent], + providers: [], +}) +export class IntegrationsModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/models/integration.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/models/integration.ts new file mode 100644 index 0000000000..51ca79b30f --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/models/integration.ts @@ -0,0 +1,21 @@ +import { IntegrationType } from "@bitwarden/common/enums"; + +/** Integration or SDK */ +export type Integration = { + name: string; + image: string; + /** + * Optional image shown in dark mode. + */ + imageDarkMode?: string; + linkURL: string; + linkText: string; + type: IntegrationType; + /** + * Shows the "New" badge until the defined date. + * When omitted, the badge is never shown. + * + * @example "2024-12-31" + */ + newBadgeExpiration?: string; +}; 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 c6c7bc6efb..e382fbd9a9 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 @@ -22,6 +22,12 @@ route="machine-accounts" [relativeTo]="route.parent" >
+ IntegrationsModule, + data: { + titleId: "integrations", + }, + }, { path: "trash", loadChildren: () => TrashModule, diff --git a/libs/common/src/enums/index.ts b/libs/common/src/enums/index.ts index 378af213e6..9ca806899a 100644 --- a/libs/common/src/enums/index.ts +++ b/libs/common/src/enums/index.ts @@ -3,6 +3,7 @@ export * from "./device-type.enum"; export * from "./event-system-user.enum"; export * from "./event-type.enum"; export * from "./http-status-code.enum"; +export * from "./integration-type.enum"; export * from "./native-messaging-version.enum"; export * from "./notification-type.enum"; export * from "./product-type.enum"; diff --git a/libs/common/src/enums/integration-type.enum.ts b/libs/common/src/enums/integration-type.enum.ts new file mode 100644 index 0000000000..acb9510697 --- /dev/null +++ b/libs/common/src/enums/integration-type.enum.ts @@ -0,0 +1,4 @@ +export enum IntegrationType { + Integration = "integration", + SDK = "sdk", +} From b26c9df056e0a0d665ac65271008d709b65933f1 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Fri, 19 Apr 2024 05:41:46 -0400 Subject: [PATCH 34/86] Fix migrated state service data (#8815) State service held data in an encrypted pair, with potentially both encrypted and decrypted values. We want the encrypted form for these disk migrations (decrypted would always be empty on disk anyways). --- ...e-cipher-service-to-state-provider.spec.ts | 24 +++++++++++-------- ...7-move-cipher-service-to-state-provider.ts | 9 ++++--- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.spec.ts b/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.spec.ts index 499cff1c89..f51699bc79 100644 --- a/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.spec.ts +++ b/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.spec.ts @@ -26,11 +26,13 @@ function exampleJSON() { }, }, ciphers: { - "cipher-id-10": { - id: "cipher-id-10", - }, - "cipher-id-11": { - id: "cipher-id-11", + encrypted: { + "cipher-id-10": { + id: "cipher-id-10", + }, + "cipher-id-11": { + id: "cipher-id-11", + }, }, }, }, @@ -150,11 +152,13 @@ describe("CipherServiceMigrator", () => { }, }, ciphers: { - "cipher-id-10": { - id: "cipher-id-10", - }, - "cipher-id-11": { - id: "cipher-id-11", + encrypted: { + "cipher-id-10": { + id: "cipher-id-10", + }, + "cipher-id-11": { + id: "cipher-id-11", + }, }, }, }, diff --git a/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.ts b/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.ts index e71d889bb7..80c776e1b6 100644 --- a/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/57-move-cipher-service-to-state-provider.ts @@ -4,7 +4,9 @@ import { Migrator } from "../migrator"; type ExpectedAccountType = { data: { localData?: unknown; - ciphers?: unknown; + ciphers?: { + encrypted: unknown; + }; }; }; @@ -37,7 +39,7 @@ export class CipherServiceMigrator extends Migrator<56, 57> { } //Migrate ciphers - const ciphers = account?.data?.ciphers; + const ciphers = account?.data?.ciphers?.encrypted; if (ciphers != null) { await helper.setToUser(userId, CIPHERS_DISK, ciphers); delete account.data.ciphers; @@ -68,7 +70,8 @@ export class CipherServiceMigrator extends Migrator<56, 57> { const ciphers = await helper.getFromUser(userId, CIPHERS_DISK); if (account.data && ciphers != null) { - account.data.ciphers = ciphers; + account.data.ciphers ||= { encrypted: null }; + account.data.ciphers.encrypted = ciphers; await helper.set(userId, account); } await helper.setToUser(userId, CIPHERS_DISK, null); From ec1973b334814f7b64be39a3072bcd857d014a17 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:39:43 +0000 Subject: [PATCH 35/86] Autosync the updated translations (#8826) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 40 ++++++++++---- apps/browser/src/_locales/az/messages.json | 54 +++++++++++++------ apps/browser/src/_locales/be/messages.json | 40 ++++++++++---- apps/browser/src/_locales/bg/messages.json | 40 ++++++++++---- apps/browser/src/_locales/bn/messages.json | 40 ++++++++++---- apps/browser/src/_locales/bs/messages.json | 40 ++++++++++---- apps/browser/src/_locales/ca/messages.json | 40 ++++++++++---- apps/browser/src/_locales/cs/messages.json | 40 ++++++++++---- apps/browser/src/_locales/cy/messages.json | 40 ++++++++++---- apps/browser/src/_locales/da/messages.json | 40 ++++++++++---- apps/browser/src/_locales/de/messages.json | 40 ++++++++++---- apps/browser/src/_locales/el/messages.json | 40 ++++++++++---- apps/browser/src/_locales/en_GB/messages.json | 40 ++++++++++---- apps/browser/src/_locales/en_IN/messages.json | 40 ++++++++++---- apps/browser/src/_locales/es/messages.json | 44 ++++++++++----- apps/browser/src/_locales/et/messages.json | 40 ++++++++++---- apps/browser/src/_locales/eu/messages.json | 40 ++++++++++---- apps/browser/src/_locales/fa/messages.json | 40 ++++++++++---- apps/browser/src/_locales/fi/messages.json | 40 ++++++++++---- apps/browser/src/_locales/fil/messages.json | 40 ++++++++++---- apps/browser/src/_locales/fr/messages.json | 48 ++++++++++++----- apps/browser/src/_locales/gl/messages.json | 52 ++++++++++++------ apps/browser/src/_locales/he/messages.json | 40 ++++++++++---- apps/browser/src/_locales/hi/messages.json | 40 ++++++++++---- apps/browser/src/_locales/hr/messages.json | 40 ++++++++++---- apps/browser/src/_locales/hu/messages.json | 40 ++++++++++---- apps/browser/src/_locales/id/messages.json | 40 ++++++++++---- apps/browser/src/_locales/it/messages.json | 40 ++++++++++---- apps/browser/src/_locales/ja/messages.json | 40 ++++++++++---- apps/browser/src/_locales/ka/messages.json | 40 ++++++++++---- apps/browser/src/_locales/km/messages.json | 40 ++++++++++---- apps/browser/src/_locales/kn/messages.json | 40 ++++++++++---- apps/browser/src/_locales/ko/messages.json | 40 ++++++++++---- apps/browser/src/_locales/lt/messages.json | 40 ++++++++++---- apps/browser/src/_locales/lv/messages.json | 40 ++++++++++---- apps/browser/src/_locales/ml/messages.json | 40 ++++++++++---- apps/browser/src/_locales/mr/messages.json | 40 ++++++++++---- apps/browser/src/_locales/my/messages.json | 40 ++++++++++---- apps/browser/src/_locales/nb/messages.json | 40 ++++++++++---- apps/browser/src/_locales/ne/messages.json | 40 ++++++++++---- apps/browser/src/_locales/nl/messages.json | 40 ++++++++++---- apps/browser/src/_locales/nn/messages.json | 40 ++++++++++---- apps/browser/src/_locales/or/messages.json | 40 ++++++++++---- apps/browser/src/_locales/pl/messages.json | 44 ++++++++++----- apps/browser/src/_locales/pt_BR/messages.json | 40 ++++++++++---- apps/browser/src/_locales/pt_PT/messages.json | 40 ++++++++++---- apps/browser/src/_locales/ro/messages.json | 40 ++++++++++---- apps/browser/src/_locales/ru/messages.json | 40 ++++++++++---- apps/browser/src/_locales/si/messages.json | 40 ++++++++++---- apps/browser/src/_locales/sk/messages.json | 40 ++++++++++---- apps/browser/src/_locales/sl/messages.json | 40 ++++++++++---- apps/browser/src/_locales/sr/messages.json | 40 ++++++++++---- apps/browser/src/_locales/sv/messages.json | 40 ++++++++++---- apps/browser/src/_locales/te/messages.json | 40 ++++++++++---- apps/browser/src/_locales/th/messages.json | 40 ++++++++++---- apps/browser/src/_locales/tr/messages.json | 40 ++++++++++---- apps/browser/src/_locales/uk/messages.json | 40 ++++++++++---- apps/browser/src/_locales/vi/messages.json | 54 +++++++++++++------ apps/browser/src/_locales/zh_CN/messages.json | 40 ++++++++++---- apps/browser/src/_locales/zh_TW/messages.json | 40 ++++++++++---- apps/browser/store/locales/gl/copy.resx | 2 +- 61 files changed, 1829 insertions(+), 629 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 1f7c5bbe98..b7e26f3362 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "تغيير كلمة المرور الرئيسية" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "عبارة بصمة الإصبع", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "أُضيف المجلد" }, - "changeMasterPass": { - "message": "تغيير كلمة المرور الرئيسية" - }, - "changeMasterPasswordConfirmation": { - "message": "يمكنك تغيير كلمة المرور الرئيسية من خزنة الويب في bitwarden.com. هل تريد زيارة الموقع الآن؟" - }, "twoStepLoginConfirmation": { "message": "تسجيل الدخول بخطوتين يجعل حسابك أكثر أمنا من خلال مطالبتك بالتحقق من تسجيل الدخول باستخدام جهاز آخر مثل مفتاح الأمان، تطبيق المصادقة، الرسائل القصيرة، المكالمة الهاتفية، أو البريد الإلكتروني. يمكن تمكين تسجيل الدخول بخطوتين على خزنة الويب bitwarden.com. هل تريد زيارة الموقع الآن؟" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 2111ea6704..20834af27f 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Ana parolu dəyişdir" }, + "continueToWebApp": { + "message": "Veb tətbiqlə davam edilsin?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Ana parolunuzu Bitwarden veb tətbiqində dəyişdirə bilərsiniz." + }, "fingerprintPhrase": { "message": "Barmaq izi ifadəsi", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Qovluq əlavə edildi" }, - "changeMasterPass": { - "message": "Ana parolu dəyişdir" - }, - "changeMasterPasswordConfirmation": { - "message": "Ana parolunuzu bitwarden.com veb anbarında dəyişdirə bilərsiniz. İndi saytı ziyarət etmək istəyirsiniz?" - }, "twoStepLoginConfirmation": { "message": "İki addımlı giriş, güvənlik açarı, kimlik doğrulayıcı tətbiq, SMS, telefon zəngi və ya e-poçt kimi digər cihazlarla girişinizi doğrulamanızı tələb edərək hesabınızı daha da güvənli edir. İki addımlı giriş, bitwarden.com veb anbarında qurula bilər. Veb saytı indi ziyarət etmək istəyirsiniz?" }, @@ -1045,7 +1045,7 @@ "message": "Bildiriş server URL-si" }, "iconsUrl": { - "message": "Nişan server URL-si" + "message": "İkon server URL-si" }, "environmentSaved": { "message": "Mühit URL-ləri saxlanıldı." @@ -1072,7 +1072,7 @@ "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "Avto-doldurma nişanı seçiləndə", + "message": "Avto-doldurma ikonu seçiləndə", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoad": { @@ -1109,7 +1109,7 @@ "message": "Anbarı açılan pəncərədə aç" }, "commandOpenSidebar": { - "message": "Anbar yan sətirdə aç" + "message": "Anbarı yan çubuqda aç" }, "commandAutofillDesc": { "message": "Hazırkı veb sayt üçün son istifadə edilən giriş məlumatlarını avto-doldur" @@ -1162,7 +1162,7 @@ "message": "Bu brauzer bu açılan pəncərədə U2F tələblərini emal edə bilmir. U2F istifadə edərək giriş etmək üçün bu açılan pəncərəni yeni bir pəncərədə açmaq istəyirsiniz?" }, "enableFavicon": { - "message": "Veb sayt nişanlarını göstər" + "message": "Veb sayt ikonlarını göstər" }, "faviconDesc": { "message": "Hər girişin yanında tanına bilən təsvir göstər." @@ -1724,7 +1724,7 @@ "message": "İcazə tələb xətası" }, "nativeMessaginPermissionSidebarDesc": { - "message": "Bu əməliyyatı kənar çubuqda icra edilə bilməz. Lütfən açılan pəncərədə yenidən sınayın." + "message": "Bu əməliyyat yan çubuqda icra edilə bilməz. Lütfən açılan pəncərədə yenidən sınayın." }, "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." @@ -1924,10 +1924,10 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { - "message": "Bir fayl seçmək üçün (mümkünsə) kənar çubuqdakı uzantını açın və ya bu bannerə klikləyərək yeni bir pəncərədə açın." + "message": "Bir fayl seçmək üçün (mümkünsə) yan çubuqdakı uzantını açın və ya bu bannerə klikləyərək yeni bir pəncərədə açın." }, "sendFirefoxFileWarning": { - "message": "Firefox istifadə edərək bir fayl seçmək üçün kənar çubuqdakı uzantını açın və ya bu bannerə klikləyərək yeni bir pəncərədə açın." + "message": "Firefox istifadə edərək bir fayl seçmək üçün yan çubuqdakı uzantını açın və ya bu bannerə klikləyərək yeni bir pəncərədə açın." }, "sendSafariFileWarning": { "message": "Safari istifadə edərək bir fayl seçmək üçün bu bannerə klikləyərək yeni bir pəncərədə açın." @@ -3000,16 +3000,36 @@ "message": "Kimlik məlumatlarını saxlama xətası. Detallar üçün konsolu yoxlayın.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Uğurlu" + }, "removePasskey": { "message": "Parolu sil" }, "passkeyRemoved": { "message": "Parol silindi" }, - "unassignedItemsBanner": { - "message": "Bildiriş: Təyin edilməmiş təşkilat elementləri artıq Bütün Anbarlar görünüşündə görünməyəndir və yalnız Admin Konsolu vasitəsilə əlçatandır. Bu elementləri görünən etmək üçün Admin Konsolundan bir kolleksiyaya təyin edin." + "unassignedItemsBannerNotice": { + "message": "Bildiriş: Təyin edilməyən təşkilat elementləri artıq Bütün Anbarlar görünüşündə görünən olmayacaq və yalnız Admin Konsolu vasitəsilə əlçatan olacaq." }, - "unassignedItemsBannerSelfHost": { - "message": "Bildiriş: 2 May 2024-cü ildən etibarən təyin edilməmiş təşkilat elementləri artıq Bütün Anbarlar görünüşündə görünməyən və yalnız Admin Konsolu vasitəsilə əlçatan olacaq. Bu elementləri görünən etmək üçün Admin Konsolundan bir kolleksiyaya təyin edin." + "unassignedItemsBannerSelfHostNotice": { + "message": "Bildiriş: 16 May 2024-cü il tarixindən etibarən, təyin edilməyən təşkilat elementləri Bütün Anbarlar görünüşündə görünən olmayacaq və yalnız Admin Konsolu vasitəsilə əlçatan olacaq." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Bu elementləri görünən etmək üçün", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "bir kolleksiyaya təyin edin.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Konsolu" + }, + "errorAssigningTargetCollection": { + "message": "Hədəf kolleksiyaya təyin etmə xətası." + }, + "errorAssigningTargetFolder": { + "message": "Hədəf qovluğa təyin etmə xətası." } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 08cb351abb..630ad48ee6 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Змяніць асноўны пароль" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Фраза адбітка пальца", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Папка дададзена" }, - "changeMasterPass": { - "message": "Змяніць асноўны пароль" - }, - "changeMasterPasswordConfirmation": { - "message": "Вы можаце змяніць свой асноўны пароль у вэб-сховішчы на bitwarden.com. Перайсці на вэб-сайт зараз?" - }, "twoStepLoginConfirmation": { "message": "Двухэтапны ўваход робіць ваш уліковы запіс больш бяспечным, патрабуючы пацвярджэнне ўваходу на іншай прыладзе з выкарыстаннем ключа бяспекі, праграмы аўтэнтыфікацыі, SMS, тэлефоннага званка або электроннай пошты. Двухэтапны ўваход уключаецца на bitwarden.com. Перайсці на вэб-сайт, каб зрабіць гэта?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 87dfc8d3be..edfcd8a9b4 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Промяна на главната парола" }, + "continueToWebApp": { + "message": "Продължаване към уеб приложението?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Може да промените главната си парола в уеб приложението на Битуорден." + }, "fingerprintPhrase": { "message": "Уникална фраза", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Добавена папка" }, - "changeMasterPass": { - "message": "Промяна на главната парола" - }, - "changeMasterPasswordConfirmation": { - "message": "Главната парола на трезор може да се промени чрез сайта bitwarden.com. Искате ли да го посетите?" - }, "twoStepLoginConfirmation": { "message": "Двустепенното вписване защитава регистрацията ви, като ви кара да потвърдите влизането си чрез устройство-ключ, приложение за удостоверение, мобилно съобщение, телефонно обаждане или електронна поща. Двустепенното вписване може да се включи чрез сайта bitwarden.com. Искате ли да го посетите?" }, @@ -3000,16 +3000,36 @@ "message": "Грешка при запазването на идентификационните данни. Вижте конзолата за подробности.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Премахване на секретния ключ" }, "passkeyRemoved": { "message": "Секретният ключ е премахнат" }, - "unassignedItemsBanner": { - "message": "Известие: неразпределените елементи на организацията вече не се виждат в изгледа с „Всички трезори“, а са достъпни само през Административната конзола. Добавете тези елементи към някоя колекция в Административната конзола, за да станат видими." + "unassignedItemsBannerNotice": { + "message": "Известие: неразпределените елементи в организацията вече няма да се виждат в изгледа с всички трезори, а са достъпни само през Административната конзола." }, - "unassignedItemsBannerSelfHost": { - "message": "Известие: от 2 май 2024г. неразпределените елементи на организациите вече няма се виждат в изгледа с „Всички трезори“, а ще бъдат достъпни само през Административната конзола. Добавете тези елементи към някоя колекция в Административната конзола, за да станат видими." + "unassignedItemsBannerSelfHostNotice": { + "message": "Известие: след 16 май 2024, неразпределените елементи в организацията вече няма да се виждат в изгледа с всички трезори, а ще бъдат достъпни само през Административната конзола." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Добавете тези елементи към колекция в", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "за да ги направите видими.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Административна конзола" + }, + "errorAssigningTargetCollection": { + "message": "Грешка при задаването на целева колекция." + }, + "errorAssigningTargetFolder": { + "message": "Грешка при задаването на целева папка." } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 1bdaeef7c6..2ab44bf7e1 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "মূল পাসওয়ার্ড পরিবর্তন" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "ফিঙ্গারপ্রিন্ট ফ্রেজ", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "ফোল্ডার জোড়া হয়েছে" }, - "changeMasterPass": { - "message": "মূল পাসওয়ার্ড পরিবর্তন" - }, - "changeMasterPasswordConfirmation": { - "message": "আপনি bitwarden.com ওয়েব ভল্ট থেকে মূল পাসওয়ার্ডটি পরিবর্তন করতে পারেন। আপনি কি এখনই ওয়েবসাইটটি দেখতে চান?" - }, "twoStepLoginConfirmation": { "message": "দ্বি-পদক্ষেপ লগইন অন্য ডিভাইসে আপনার লগইনটি যাচাই করার জন্য সিকিউরিটি কী, প্রমাণীকরণকারী অ্যাপ্লিকেশন, এসএমএস, ফোন কল বা ই-মেইল ব্যাবহারের মাধ্যমে আপনার অ্যাকাউন্টকে আরও সুরক্ষিত করে। bitwarden.com ওয়েব ভল্টে দ্বি-পদক্ষেপের লগইন সক্ষম করা যাবে। আপনি কি এখনই ওয়েবসাইটটি দেখতে চান?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 92a667afeb..8d2a25db9c 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index fc67602b60..30d4c4e636 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Canvia la contrasenya mestra" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Frase d'empremta digital", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Carpeta afegida" }, - "changeMasterPass": { - "message": "Canvia la contrasenya mestra" - }, - "changeMasterPasswordConfirmation": { - "message": "Podeu canviar la contrasenya mestra a la caixa forta web de bitwarden.com. Voleu visitar el lloc web ara?" - }, "twoStepLoginConfirmation": { "message": "L'inici de sessió en dues passes fa que el vostre compte siga més segur, ja que obliga a verificar el vostre inici de sessió amb un altre dispositiu, com ara una clau de seguretat, una aplicació autenticadora, un SMS, una trucada telefònica o un correu electrònic. Es pot habilitar l'inici de sessió en dues passes a la caixa forta web de bitwarden.com. Voleu visitar el lloc web ara?" }, @@ -3000,16 +3000,36 @@ "message": "S'ha produït un error en guardar les credencials. Consulteu la consola per obtenir més informació.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Suprimeix la clau de pas" }, "passkeyRemoved": { "message": "Clau de pas suprimida" }, - "unassignedItemsBanner": { - "message": "Nota: els elements de l'organització sense assignar ja no es veuran a la vista \"Totes les caixes fortes\" i només es veuran des de la consola d'administració. Assigneu-los-hi una col·lecció des de la consola per fer-los visibles." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index d989d25bf2..42a4f9edb1 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Změnit hlavní heslo" }, + "continueToWebApp": { + "message": "Pokračovat do webové aplikace?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Hlavní heslo můžete změnit ve webové aplikaci Bitwardenu." + }, "fingerprintPhrase": { "message": "Fráze otisku prstu", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Složka byla přidána" }, - "changeMasterPass": { - "message": "Změnit hlavní heslo" - }, - "changeMasterPasswordConfirmation": { - "message": "Hlavní heslo si můžete změnit na webové stránce bitwarden.com. Chcete tuto stránku nyní otevřít?" - }, "twoStepLoginConfirmation": { "message": "Dvoufázové přihlášení činí Váš účet mnohem bezpečnějším díky nutnosti po každém úspěšném přihlášení zadat ověřovací kód získaný z bezpečnostního klíče, aplikace, SMS, telefonního hovoru nebo e-mailu. Dvoufázové přihlášení lze aktivovat na webové stránce bitwarden.com. Chcete tuto stránku nyní otevřít?" }, @@ -3000,16 +3000,36 @@ "message": "Chyba při ukládání přihlašovacích údajů. Podrobnosti naleznete v konzoli.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Úspěch" + }, "removePasskey": { "message": "Odebrat přístupový klíč" }, "passkeyRemoved": { "message": "Přístupový klíč byl odebrán" }, - "unassignedItemsBanner": { - "message": "Upozornění: Nepřiřazené položky organizace již nejsou viditelné ve Vašem zobrazení všech trezorů a jsou nyní přístupné jen v konzoli správce. Přiřaďte tyto položky do kolekce z konzole pro správce, aby byly viditelné." + "unassignedItemsBannerNotice": { + "message": "Upozornění: Nepřiřazené položky organizace již nejsou viditelné ve vašem zobrazení všech trezorů a jsou nyní přístupné pouze v konzoli správce." }, - "unassignedItemsBannerSelfHost": { - "message": "Upozornění: Dne 2. května 2024 již nebudou nepřiřazené položky organizace viditelné v zobrazení Všechny trezory a budou přístupné jen prostřednictvím konzoly správce. Přiřaďte tyto položky do kolekce z konzoly pro správce, aby byly viditelné." + "unassignedItemsBannerSelfHostNotice": { + "message": "Upozornění: 16. květba 2024 již nebudou nepřiřazené položky organizace viditelné ve vašem zobrazení všech trezorů a budou přístupné pouze v konzoli správce." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Přiřadit tyto položky ke kolekci z", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "aby byly viditelné.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Konzole správce" + }, + "errorAssigningTargetCollection": { + "message": "Chyba při přiřazování cílové kolekce." + }, + "errorAssigningTargetFolder": { + "message": "Chyba při přiřazování cílové složky." } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 79178bc9d5..52d3cf7d56 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Newid y prif gyfrinair" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Ymadrodd unigryw", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Ffolder wedi'i hychwanegu" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index d808d97412..08aebe98e1 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Skift hovedadgangskode" }, + "continueToWebApp": { + "message": "Fortsæt til web-app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Hovedadgangskoden kan ændres via Bitwarden web-appen." + }, "fingerprintPhrase": { "message": "Fingeraftrykssætning", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Mappe tilføjet" }, - "changeMasterPass": { - "message": "Skift hovedadgangskode" - }, - "changeMasterPasswordConfirmation": { - "message": "Du kan ændre din hovedadgangskode i bitwarden.com web-boksen. Vil du besøge hjemmesiden nu?" - }, "twoStepLoginConfirmation": { "message": "To-trins login gør din konto mere sikker ved at kræve, at du verificerer dit login med en anden enhed, såsom en sikkerhedsnøgle, autentificeringsapp, SMS, telefonopkald eller e-mail. To-trins login kan aktiveres i bitwarden.com web-boksen. Vil du besøge hjemmesiden nu?" }, @@ -3000,16 +3000,36 @@ "message": "Fejl under import. Tjek konsollen for detaljer.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Gennemført" + }, "removePasskey": { "message": "Fjern adgangsnøgle" }, "passkeyRemoved": { "message": "Adgangsnøgle fjernet" }, - "unassignedItemsBanner": { - "message": "Bemærk: Utildelte organisationsemner er ikke længere synlige i Alle Bokse-visningen og er kun tilgængelige via Adminkonsollen. Føj disse emner til en samling fra Adminkonsollen for at gøre dem synlige." + "unassignedItemsBannerNotice": { + "message": "Bemærk: Utildelte organisationsemner er ikke længere synlige i Alle Bokse-visningen, men er kun tilgængelige via Admin-konsol." }, - "unassignedItemsBannerSelfHost": { - "message": "Bemærk: Pr. 2. maj 2024 vil utildelte organisationsemner ikke længere være synlige i Alle Bokse-visningen og vil kun være tilgængelige via Admin-konsollen. Tildel disse emner til en samling via Admin-konsollen for at gøre dem synlige." + "unassignedItemsBannerSelfHostNotice": { + "message": "Bemærk: Pr. 16. maj 2024 er utildelte organisationsemner er ikke længere synlige i Alle Bokse-visningen, men er kun tilgængelige via Admin-konsol." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Tildel disse emner til en samling via", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "for at gøre dem synlige.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin-konsol" + }, + "errorAssigningTargetCollection": { + "message": "Fejl ved tildeling af målsamling." + }, + "errorAssigningTargetFolder": { + "message": "Fejl ved tildeling af målmappe." } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index deb92e992d..4edca5557c 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Master-Passwort ändern" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerabdruck-Phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Ordner hinzugefügt" }, - "changeMasterPass": { - "message": "Master-Passwort ändern" - }, - "changeMasterPasswordConfirmation": { - "message": "Du kannst dein Master-Passwort im Bitwarden.com Web-Tresor ändern. Möchtest du die Seite jetzt öffnen?" - }, "twoStepLoginConfirmation": { "message": "Mit der Zwei-Faktor-Authentifizierung wird dein Konto zusätzlich abgesichert, da jede Anmeldung mit einem anderen Gerät wie einem Sicherheitsschlüssel, einer Authentifizierungs-App, einer SMS, einem Anruf oder einer E-Mail verifiziert werden muss. Die Zwei-Faktor-Authentifizierung kann im bitwarden.com Web-Tresor aktiviert werden. Möchtest du die Website jetzt öffnen?" }, @@ -3000,16 +3000,36 @@ "message": "Fehler beim Speichern der Zugangsdaten. Details in der Konsole.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Passkey entfernen" }, "passkeyRemoved": { "message": "Passkey entfernt" }, - "unassignedItemsBanner": { - "message": "Hinweis: Nicht zugeordnete Organisationseinträge sind nicht mehr in der Ansicht aller Tresore sichtbar und nur über die Administrator-Konsole zugänglich. Weise diese Einträge einer Sammlung aus der Administrator-Konsole zu, um sie sichtbar zu machen." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Hinweis: Ab dem 2. Mai 2024 werden nicht zugewiesene Organisationseinträge nicht mehr in der Ansicht aller Tresore sichtbar sein und sind nur über die Administrator-Konsole zugänglich. Weise diese Elemente einer Sammlung aus der Administrator-Konsole zu, um sie sichtbar zu machen." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Fehler beim Zuweisen der Ziel-Sammlung." + }, + "errorAssigningTargetFolder": { + "message": "Fehler beim Zuweisen des Ziel-Ordners." } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 36b14e447f..75cd5ef2fa 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Αλλαγή Κύριου Κωδικού" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Φράση Δακτυλικών Αποτυπωμάτων", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Προστέθηκε φάκελος" }, - "changeMasterPass": { - "message": "Αλλαγή Κύριου Κωδικού" - }, - "changeMasterPasswordConfirmation": { - "message": "Μπορείτε να αλλάξετε τον κύριο κωδικό στο bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" - }, "twoStepLoginConfirmation": { "message": "Η σύνδεση σε δύο βήματα καθιστά πιο ασφαλή τον λογαριασμό σας, απαιτώντας να επαληθεύσετε τα στοιχεία σας με μια άλλη συσκευή, όπως κλειδί ασφαλείας, εφαρμογή επαλήθευσης, μήνυμα SMS, τηλεφωνική κλήση ή email. Μπορείτε να ενεργοποιήσετε τη σύνδεση σε δύο βήματα στο bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 1ac55feb42..b4b7b314db 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organisation items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organisation items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organisation items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organisation items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index cbe214f0b3..6dd78dc292 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Added folder" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index ee5666f3cc..20b9a91814 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Cambiar contraseña maestra" }, + "continueToWebApp": { + "message": "¿Continuar a la aplicación web?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Puedes cambiar la contraseña maestra en la aplicación web de Bitwarden." + }, "fingerprintPhrase": { "message": "Frase de huella digital", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Carpeta añadida" }, - "changeMasterPass": { - "message": "Cambiar contraseña maestra" - }, - "changeMasterPasswordConfirmation": { - "message": "Puedes cambiar tu contraseña maestra en la caja fuerte web de bitwarden.com. ¿Quieres visitar ahora el sitio web?" - }, "twoStepLoginConfirmation": { "message": "La autenticación en dos pasos hace que tu cuenta sea mucho más segura, requiriendo que introduzcas un código de seguridad de una aplicación de autenticación cada vez que accedes. La autenticación en dos pasos puede ser habilitada en la caja fuerte web de bitwarden.com. ¿Quieres visitar ahora el sitio web?" }, @@ -3000,16 +3000,36 @@ "message": "Se produjo un error al guardar las credenciales. Revise la consola para obtener detalles.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { - "message": "Remove passkey" + "message": "Eliminar passkey" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Passkey eliminada" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index ea1758468e..c832528847 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Muuda ülemparooli" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Sõrmejälje fraas", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Kaust on lisatud" }, - "changeMasterPass": { - "message": "Muuda ülemparooli" - }, - "changeMasterPasswordConfirmation": { - "message": "Saad oma ülemparooli muuta bitwarden.com veebihoidlas. Soovid seda kohe teha?" - }, "twoStepLoginConfirmation": { "message": "Kaheastmeline kinnitamine aitab konto turvalisust tõsta. Lisaks paroolile pead kontole ligipääsemiseks kinnitama sisselogimise päringu SMS-ga, telefonikõnega, autentimise rakendusega või e-postiga. Kaheastmelist kinnitust saab sisse lülitada bitwarden.com veebihoidlas. Soovid seda kohe avada?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Eemalda pääsuvõti" }, "passkeyRemoved": { "message": "Pääsuvõti on eemaldatud" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 529a1e8127..9504f06c65 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Aldatu pasahitz nagusia" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Hatz-marka digitalaren esaldia", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Karpeta gehituta" }, - "changeMasterPass": { - "message": "Aldatu pasahitz nagusia" - }, - "changeMasterPasswordConfirmation": { - "message": "Zure pasahitz nagusia alda dezakezu bitwarden.com webgunean. Orain joan nahi duzu webgunera?" - }, "twoStepLoginConfirmation": { "message": "Bi urratseko saio hasiera dela eta, zure kontua seguruagoa da, beste aplikazio/gailu batekin saioa hastea eskatzen baitizu; adibidez, segurtasun-gako, autentifikazio-aplikazio, SMS, telefono dei edo email bidez. Bi urratseko saio hasiera bitwarden.com webgunean aktibatu daiteke. Orain joan nahi duzu webgunera?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 669eb151f4..c96c5c35cf 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "تغییر کلمه عبور اصلی" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "عبارت اثر انگشت", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "پوشه اضافه شد" }, - "changeMasterPass": { - "message": "تغییر کلمه عبور اصلی" - }, - "changeMasterPasswordConfirmation": { - "message": "شما می‌توانید کلمه عبور اصلی خود را در bitwarden.com تغییر دهید. آیا می‌خواهید از سایت بازدید کنید؟" - }, "twoStepLoginConfirmation": { "message": "ورود دو مرحله ای باعث می‌شود که حساب کاربری شما با استفاده از یک دستگاه دیگر مانند کلید امنیتی، برنامه احراز هویت، پیامک، تماس تلفنی و یا ایمیل، اعتبار خود را با ایمنی بیشتر اثبات کند. ورود دو مرحله ای می تواند در bitwarden.com فعال شود. آیا می‌خواهید از سایت بازدید کنید؟" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 17aea532ba..b3602afd82 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Vaihda pääsalasana" }, + "continueToWebApp": { + "message": "Avataanko verkkosovellus?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Voit vaihtaa pääsalasanasi Bitwardenin verkkosovelluksessa." + }, "fingerprintPhrase": { "message": "Tunnistelauseke", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Kansio lisätty" }, - "changeMasterPass": { - "message": "Vaihda pääsalasana" - }, - "changeMasterPasswordConfirmation": { - "message": "Voit vaihtaa pääsalasanasi bitwarden.com-verkkoholvissa. Haluatko käydä sivustolla nyt?" - }, "twoStepLoginConfirmation": { "message": "Kaksivaiheinen kirjautuminen parantaa tilisi suojausta vaatimalla kirjautumisen vahvistuksen salasanan lisäksi todennuslaitteen, ‑sovelluksen, tekstiviestin, puhelun tai sähköpostin avulla. Voit ottaa kaksivaiheisen kirjautumisen käyttöön bitwarden.com‑verkkoholvissa. Haluatko avata sen nyt?" }, @@ -3000,16 +3000,36 @@ "message": "Virhe tallennettaessa käyttäjätietoja. Näet isätietoja hallinnasta.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Poista suojausavain" }, "passkeyRemoved": { "message": "Suojausavain poistettiin" }, - "unassignedItemsBanner": { - "message": "Huomautus: Organisaatioiden kokoelmiin määrittämättömät kohteet eivät enää näy laitteiden \"Kaikki holvit\" -näkymissä, vaan ne ovat nähtävissä vain Hallintapaneelista. Määritä kohteet kokoelmiin Hallintapaneelista, jotta ne ovat jatkossakin käytettävissä kaikilta laitteilta." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Huomautus: 2.5.2024 alkaen kokoelmiin määrittämättömät organisaatioiden kohteet eivät enää näy laitteiden \"Kaikki holvit\" -näkymissä, vaan ne ovat nähtävissä vain Hallintapaneelista. Määritä kohteet kokoelmiin Hallintapaneelista, jotta ne ovat jatkossakin käytettävissä kaikilta laitteilta." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Määritä nämä kohteet kokoelmaan", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": ", jotta ne näkyvät.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Hallintapaneelista" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 42d5060e28..28418d984d 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Baguhin ang Master Password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Hulmabig ng Hilik ng Dako", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Idinagdag na folder" }, - "changeMasterPass": { - "message": "Palitan ang master password" - }, - "changeMasterPasswordConfirmation": { - "message": "Maaari mong palitan ang iyong master password sa bitwarden.com web vault. Gusto mo bang bisitahin ang website ngayon?" - }, "twoStepLoginConfirmation": { "message": "Ang two-step login ay nagpapagaan sa iyong account sa pamamagitan ng pag-verify sa iyong login sa isa pang device tulad ng security key, authenticator app, SMS, tawag sa telepono o email. Ang two-step login ay maaaring magawa sa bitwarden.com web vault. Gusto mo bang bisitahin ang website ngayon?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 6cced1cb0d..206f07800d 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Changer le mot de passe principal" }, + "continueToWebApp": { + "message": "Poursuivre vers l'application web ?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Vous pouvez modifier votre mot de passe principal sur l'application web de Bitwarden." + }, "fingerprintPhrase": { "message": "Phrase d'empreinte", "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." @@ -525,13 +531,13 @@ "message": "Impossible de scanner le QR code à partir de la page web actuelle" }, "totpCaptureSuccess": { - "message": "Clé de l'Authentificateur ajoutée" + "message": "Clé Authenticator ajoutée" }, "totpCapture": { "message": "Scanner le QR code de l'authentificateur à partir de la page web actuelle" }, "copyTOTP": { - "message": "Copier la clé de l'Authentificateur (TOTP)" + "message": "Copier la clé Authenticator (TOTP)" }, "loggedOut": { "message": "Déconnecté" @@ -557,12 +563,6 @@ "addedFolder": { "message": "Dossier ajouté" }, - "changeMasterPass": { - "message": "Changer le mot de passe principal" - }, - "changeMasterPasswordConfirmation": { - "message": "Vous pouvez changer votre mot de passe principal depuis le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" - }, "twoStepLoginConfirmation": { "message": "L'authentification à deux facteurs rend votre compte plus sûr en vous demandant de vérifier votre connexion avec un autre dispositif tel qu'une clé de sécurité, une application d'authentification, un SMS, un appel téléphonique ou un courriel. L'authentification à deux facteurs peut être configurée dans le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" }, @@ -659,7 +659,7 @@ "message": "Liste les éléments des cartes de paiement sur la Page d'onglet pour faciliter la saisie automatique." }, "showIdentitiesCurrentTab": { - "message": "Afficher les identités sur la page d'onglet" + "message": "Afficher les identités sur la Page d'onglet" }, "showIdentitiesCurrentTabDesc": { "message": "Liste les éléments d'identité sur la Page d'onglet pour faciliter la saisie automatique." @@ -802,7 +802,7 @@ "message": "En savoir plus" }, "authenticatorKeyTotp": { - "message": "Clé de l'Authentificateur (TOTP)" + "message": "Clé Authenticator (TOTP)" }, "verificationCodeTotp": { "message": "Code de vérification (TOTP)" @@ -3000,16 +3000,36 @@ "message": "Erreur lors de l'enregistrement des identifiants. Consultez la console pour plus de détails.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Retirer la clé d'identification (passkey)" }, "passkeyRemoved": { "message": "Clé d'identification (passkey) retirée" }, - "unassignedItemsBanner": { - "message": "Notice : les éléments d'organisation non assignés ne sont plus visibles dans la vue Tous les coffres et sont uniquement accessibles via la console d'administration. Assignez ces éléments à une collection à partir de la console d'administration pour les rendre visibles." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Remarque : au 2 mai 2024, les éléments d'organisation non assignés ne sont plus visibles dans votre vue Tous les coffres sur tous les appareils et sont uniquement accessibles via la Console d'administration. Assignez ces éléments à une collection à partir de la Console d'administration pour les rendre visibles." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 023e03b834..95b880d1a5 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -14,28 +14,28 @@ "message": "Log in or create a new account to access your secure vault." }, "createAccount": { - "message": "Create account" + "message": "Crea unha conta" }, "login": { - "message": "Log in" + "message": "Iniciar sesión" }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, "cancel": { - "message": "Cancel" + "message": "Cancelar" }, "close": { - "message": "Close" + "message": "Pechar" }, "submit": { "message": "Submit" }, "emailAddress": { - "message": "Email address" + "message": "Enderezo de correo electrónico" }, "masterPass": { - "message": "Master password" + "message": "Contrasinal mestre" }, "masterPassDesc": { "message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it." @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 1e633f5eb9..efb82e64ec 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "החלף סיסמה ראשית" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "סיסמת טביעת אצבע", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "נוספה תיקייה" }, - "changeMasterPass": { - "message": "החלף סיסמה ראשית" - }, - "changeMasterPasswordConfirmation": { - "message": "באפשרותך לשנות את הסיסמה הראשית שלך דרך הכספת באתר bitwarden.com. האם ברצונך לפתוח את האתר כעת?" - }, "twoStepLoginConfirmation": { "message": "התחברות בשני-שלבים הופכת את החשבון שלך למאובטח יותר בכך שאתה נדרש לוודא בכל כניסה בעזרת מכשיר אחר כדוגמת מפתח אבטחה, תוכנת אימות, SMS, שיחת טלפון, או אימייל. ניתן להפעיל את \"התחברות בשני-שלבים\" בכספת שבאתר bitwarden.com. האם ברצונך לפתוח את האתר כעת?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 44f645bc47..9c13fa6efa 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change Master Password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint Phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "जोड़ा गया फ़ोल्डर" }, - "changeMasterPass": { - "message": "Change Master Password" - }, - "changeMasterPasswordConfirmation": { - "message": "आप वेब वॉल्ट bitwarden.com पर अपना मास्टर पासवर्ड बदल सकते हैं।क्या आप अब वेबसाइट पर जाना चाहते हैं?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to enter a security code from an authenticator app whenever you log in. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index e74a72bc4f..ee4c4e7859 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Promjeni glavnu lozinku" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Jedinstvena fraza", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Mapa dodana" }, - "changeMasterPass": { - "message": "Promjeni glavnu lozinku" - }, - "changeMasterPasswordConfirmation": { - "message": "Svoju glavnu lozinku možeš promijeniti na web trezoru. Želiš li sada posjetiti bitwarden.com?" - }, "twoStepLoginConfirmation": { "message": "Prijava dvostrukom autentifikacijom čini tvoj račun još sigurnijim tako što će zahtijevati da potvrdiš prijavu putem drugog uređaja pomoću sigurnosnog koda, autentifikatorske aplikacije, SMS-om, pozivom ili e-poštom. Prijavu dvostrukom autentifikacijom možeš omogućiti na web trezoru. Želiš li sada posjetiti bitwarden.com?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index cadd72a475..7d6a8a208b 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Mesterjelszó módosítása" }, + "continueToWebApp": { + "message": "Tovább a webes alkalmazáshoz?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "A mesterjelszó a Bitwarden webalkalmazásban módosítható." + }, "fingerprintPhrase": { "message": "Ujjlenyomat kifejezés", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "A mappa hozzáadásra került." }, - "changeMasterPass": { - "message": "Mesterjelszó módosítása" - }, - "changeMasterPasswordConfirmation": { - "message": "Mesterjelszavadat a bitwarden.com webes széfén tudod megváltoztatni. Szeretnéd meglátogatni a most a weboldalt?" - }, "twoStepLoginConfirmation": { "message": "A kétlépcsős bejelentkezés biztonságosabbá teszi a fiókot azáltal, hogy ellenőrizni kell a bejelentkezést egy másik olyan eszközzel mint például biztonsági kulcs, hitelesítő alkalmazás, SMS, telefon hívás vagy email. A kétlépcsős bejelentkezést a bitwarden.com webes széfben lehet engedélyezni. Felkeressük a webhelyet most?" }, @@ -3000,16 +3000,36 @@ "message": "Hiba történt a hitelesítések mentésekor. A részletekért ellenőrizzük a konzolt.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Jelszó eltávolítása" }, "passkeyRemoved": { "message": "A jelszó eltávolításra került." }, - "unassignedItemsBanner": { - "message": "Megjegyzés: A nem hozzá nem rendelt szervezeti elemek már nem láthatók az Összes széf nézetben és csak az Adminisztrátori konzolon keresztül érhetők el. Rendeljük ezeket az elemeket egy gyűjteményhez az Adminisztrátor konzolból, hogy láthatóvá tegyük azokat." + "unassignedItemsBannerNotice": { + "message": "Megjegyzés: A nem hozzárendelt szervezeti elemek már nem láthatók az Összes széf nézetben a különböző eszközökön és csak az Adminisztrátori konzolon keresztül érhetők el." }, - "unassignedItemsBannerSelfHost": { - "message": "Figyelmeztetés: 2024. május 2-án a nem hozzá rendelt szervezeti elemek többé nem lesznek láthatók az Összes széf nézetben a különböző eszközökön és csak a Felügyeleti konzolon keresztül lesznek elérhetők. Rendeljük ezeket az elemeket egy gyűjteményhez az Adminisztrátori konzolból, hogy láthatóvá tegyük azokat." + "unassignedItemsBannerSelfHostNotice": { + "message": "Megjegyzés: 2024. május 16-tól a nem hozzárendelt szervezeti elemek többé nem lesznek láthatók az Összes széf nézetben a különböző eszközökön és csak az Adminisztrátori konzolon keresztül lesznek elérhetők." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Rendeljük hozzá ezeket az elemeket a gyűjteményhez", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "a láthatósághoz.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Adminisztrátori konzol" + }, + "errorAssigningTargetCollection": { + "message": "Hiba történt a célgyűjtemény hozzárendelése során." + }, + "errorAssigningTargetFolder": { + "message": "Hiba történt a célmappa hozzárendelése során." } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 9907a7520c..92b60324ad 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Ubah Kata Sandi Utama" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Frasa Sidik Jari", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Tambah Folder" }, - "changeMasterPass": { - "message": "Ubah Kata Sandi Utama" - }, - "changeMasterPasswordConfirmation": { - "message": "Anda dapat mengubah kata sandi utama Anda di brankas web bitwarden.com. Anda ingin mengunjungi situs web sekarang?" - }, "twoStepLoginConfirmation": { "message": "Info masuk dua langkah membuat akun Anda lebih aman dengan mengharuskan Anda memverifikasi info masuk Anda dengan peranti lain seperti kode keamanan, aplikasi autentikasi, SMK, panggilan telepon, atau email. Info masuk dua langkah dapat diaktifkan di brankas web bitwarden.com. Anda ingin mengunjungi situs web sekarang?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 65a5a1ad04..6887b134df 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Cambia password principale" }, + "continueToWebApp": { + "message": "Passa al sito web?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Puoi modificare la tua password principale sul sito web di Bitwarden." + }, "fingerprintPhrase": { "message": "Frase impronta", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Cartella aggiunta" }, - "changeMasterPass": { - "message": "Cambia password principale" - }, - "changeMasterPasswordConfirmation": { - "message": "Puoi cambiare la tua password principale sulla cassaforte online di bitwarden.com. Vuoi visitare ora il sito?" - }, "twoStepLoginConfirmation": { "message": "La verifica in due passaggi rende il tuo account più sicuro richiedendoti di verificare il tuo login usando un altro dispositivo come una chiave di sicurezza, app di autenticazione, SMS, telefonata, o email. Può essere abilitata nella cassaforte web su bitwarden.com. Vuoi visitare il sito?" }, @@ -3000,16 +3000,36 @@ "message": "Errore durante il salvataggio delle credenziali. Controlla la console per più dettagli.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Rimuovi passkey" }, "passkeyRemoved": { "message": "Passkey rimossa" }, - "unassignedItemsBanner": { - "message": "Avviso: gli elementi dell'organizzazione non assegnati non sono più visibili nella visualizzazione Tutte le Cassaforti su tutti i dispositivi e sono ora accessibili solo tramite la Console di amministrazione. Assegna questi elementi ad una raccolta dalla Console di amministrazione per renderli visibili." + "unassignedItemsBannerNotice": { + "message": "Avviso: gli elementi dell'organizzazione non assegnati non sono più visibili nella visualizzazione Tutte le Cassaforti su tutti i dispositivi e sono ora accessibili solo tramite la Console di amministrazione." }, - "unassignedItemsBannerSelfHost": { - "message": "Avviso: dal 2 maggio 2024, gli elementi dell'organizzazione non assegnati non saranno più visibili nella visualizzazione Tutte le Cassaforti su tutti i dispositivi e saranno accessibili solo tramite la Console di amministrazione. Assegna questi elementi ad una raccolta dalla Console di amministrazione per renderli visibili." + "unassignedItemsBannerSelfHostNotice": { + "message": "Avviso: dal 16 maggio 2024, gli elementi dell'organizzazione non assegnati non saranno più visibili nella visualizzazione Tutte le Cassaforti su tutti i dispositivi e saranno accessibili solo tramite la Console di amministrazione." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assegna questi elementi ad una raccolta dalla", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "per renderli visibili.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Console di amministrazione" + }, + "errorAssigningTargetCollection": { + "message": "Errore nell'assegnazione della raccolta di destinazione." + }, + "errorAssigningTargetFolder": { + "message": "Errore nell'assegnazione della cartella di destinazione." } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 05fb7fe5de..744c76a509 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "マスターパスワードの変更" }, + "continueToWebApp": { + "message": "ウェブアプリに進みますか?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Bitwarden ウェブアプリでマスターパスワードを変更できます。" + }, "fingerprintPhrase": { "message": "パスフレーズ", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "フォルダを追加しました" }, - "changeMasterPass": { - "message": "マスターパスワードの変更" - }, - "changeMasterPasswordConfirmation": { - "message": "マスターパスワードは bitwarden.com ウェブ保管庫で変更できます。ウェブサイトを開きますか?" - }, "twoStepLoginConfirmation": { "message": "2段階認証を使うと、ログイン時にセキュリティキーや認証アプリ、SMS、電話やメールでの認証を必要にすることでアカウントをさらに安全に出来ます。2段階認証は bitwarden.com ウェブ保管庫で有効化できます。ウェブサイトを開きますか?" }, @@ -3000,16 +3000,36 @@ "message": "資格情報の保存中にエラーが発生しました。詳細はコンソールを確認してください。", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "成功" + }, "removePasskey": { "message": "パスキーを削除" }, "passkeyRemoved": { "message": "パスキーを削除しました" }, - "unassignedItemsBanner": { - "message": "注意: 割り当てられていない組織項目は、すべての保管庫のビューでは表示されなくなり、管理コンソールからのみアクセスできます。 管理コンソールからコレクションにこれらのアイテムを割り当てると、表示するようにできます。" + "unassignedItemsBannerNotice": { + "message": "注意: 割り当てられていない組織アイテムは、すべての保管庫ビューでは表示されなくなり、管理コンソールからのみアクセスできるようになります。" }, - "unassignedItemsBannerSelfHost": { - "message": "お知らせ:2024年5月2日に、 割り当てられていない組織アイテムはデバイス間のすべての保管庫ビューに表示されなくなり、管理コンソールからのみアクセス可能になります。 管理コンソールからコレクションにこれらのアイテムを割り当てると、表示できるようになります。" + "unassignedItemsBannerSelfHostNotice": { + "message": "お知らせ:2024年5月16日に、 割り当てられていない組織アイテムは、すべての保管庫ビューに表示されなくなり、管理コンソールからのみアクセス可能になります。" + }, + "unassignedItemsBannerCTAPartOne": { + "message": "これらのアイテムのコレクションへの割り当てを", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "で実行すると表示できるようになります。", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "管理コンソール" + }, + "errorAssigningTargetCollection": { + "message": "ターゲットコレクションの割り当てに失敗しました。" + }, + "errorAssigningTargetFolder": { + "message": "ターゲットフォルダーの割り当てに失敗しました。" } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index d67b88ba9c..ab7b84d22e 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 023e03b834..67e1f24787 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 61cfadc762..9b363cba1f 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ ಬದಲಾಯಿಸಿ" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಫ್ರೇಸ್", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "ಫೋಲ್ಡರ್ ಸೇರಿಸಿ" }, - "changeMasterPass": { - "message": "ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ ಬದಲಾಯಿಸಿ" - }, - "changeMasterPasswordConfirmation": { - "message": "ನಿಮ್ಮ ಮಾಸ್ಟರ್ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ನೀವು bitwarden.com ವೆಬ್ ವಾಲ್ಟ್‌ನಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು. ನೀವು ಈಗ ವೆಬ್‌ಸೈಟ್‌ಗೆ ಭೇಟಿ ನೀಡಲು ಬಯಸುವಿರಾ?" - }, "twoStepLoginConfirmation": { "message": "ಭದ್ರತಾ ಕೀ, ದೃಢೀಕರಣ ಅಪ್ಲಿಕೇಶನ್, ಎಸ್‌ಎಂಎಸ್, ಫೋನ್ ಕರೆ ಅಥವಾ ಇಮೇಲ್‌ನಂತಹ ಮತ್ತೊಂದು ಸಾಧನದೊಂದಿಗೆ ನಿಮ್ಮ ಲಾಗಿನ್ ಅನ್ನು ಪರಿಶೀಲಿಸುವ ಅಗತ್ಯವಿರುವ ಎರಡು ಹಂತದ ಲಾಗಿನ್ ನಿಮ್ಮ ಖಾತೆಯನ್ನು ಹೆಚ್ಚು ಸುರಕ್ಷಿತಗೊಳಿಸುತ್ತದೆ. ಬಿಟ್ವಾರ್ಡೆನ್.ಕಾಮ್ ವೆಬ್ ವಾಲ್ಟ್ನಲ್ಲಿ ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಬಹುದು. ನೀವು ಈಗ ವೆಬ್‌ಸೈಟ್‌ಗೆ ಭೇಟಿ ನೀಡಲು ಬಯಸುವಿರಾ?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index c71fbdf7a8..3e4f5769c0 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "마스터 비밀번호 변경" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "지문 구절", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "폴더 추가함" }, - "changeMasterPass": { - "message": "마스터 비밀번호 변경" - }, - "changeMasterPasswordConfirmation": { - "message": "bitwarden.com 웹 보관함에서 마스터 비밀번호를 바꿀 수 있습니다. 지금 웹 사이트를 방문하시겠습니까?" - }, "twoStepLoginConfirmation": { "message": "2단계 인증은 보안 키, 인증 앱, SMS, 전화 통화 등의 다른 기기로 사용자의 로그인 시도를 검증하여 사용자의 계정을 더욱 안전하게 만듭니다. 2단계 인증은 bitwarden.com 웹 보관함에서 활성화할 수 있습니다. 지금 웹 사이트를 방문하시겠습니까?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 0fc146c250..c690c2727b 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Keisti pagrindinį slaptažodį" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Pirštų atspaudų frazė", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Katalogas pridėtas" }, - "changeMasterPass": { - "message": "Keisti pagrindinį slaptažodį" - }, - "changeMasterPasswordConfirmation": { - "message": "Pagrindinį slaptažodį galite pakeisti „bitwarden.com“ žiniatinklio saugykloje. Ar norite dabar apsilankyti svetainėje?" - }, "twoStepLoginConfirmation": { "message": "Prisijungus dviem veiksmais, jūsų paskyra tampa saugesnė, reikalaujant patvirtinti prisijungimą naudojant kitą įrenginį, pvz., saugos raktą, autentifikavimo programėlę, SMS, telefono skambutį ar el. paštą. Dviejų žingsnių prisijungimą galima įjungti „bitwarden.com“ interneto saugykloje. Ar norite dabar apsilankyti svetainėje?" }, @@ -3000,16 +3000,36 @@ "message": "Klaida išsaugant kredencialus. Išsamesnės informacijos patikrink konsolėje.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Pašalinti slaptaraktį" }, "passkeyRemoved": { "message": "Pašalintas slaptaraktis" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index efac417556..d26b10a30c 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Mainīt galveno paroli" }, + "continueToWebApp": { + "message": "Pāriet uz tīmekļa lietotni?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Savu galveno paroli var mainīt Bitwarden tīmekļa lietotnē." + }, "fingerprintPhrase": { "message": "Atpazīšanas vārdkopa", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Pievienoja mapi" }, - "changeMasterPass": { - "message": "Mainīt galveno paroli" - }, - "changeMasterPasswordConfirmation": { - "message": "Galveno paroli ir iespējams mainīt bitwarden.com tīmekļa glabātavā. Vai tagad apmeklēt tīmekļvietni?" - }, "twoStepLoginConfirmation": { "message": "Divpakāpju pieteikšanās padara kontu krietni drošāku, pieprasot apstiprināt pieteikšanos ar tādu citu ierīču vai pakalpojumu starpniecību kā drošības atslēga, autentificētāja lietotne, īsziņa, tālruņa zvans vai e-pasts. Divpakāpju pieteikšanos var iespējot bitwarden.com tīmekļa glabātavā. Vai tagad apmeklēt tīmekļvietni?" }, @@ -3000,16 +3000,36 @@ "message": "Kļūda piekļuves informācijas saglabāšanā. Jāpārbauda, vai konsolē ir izvērstāka informācija.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Noņemt piekļuves atslēgu" }, "passkeyRemoved": { "message": "Piekļuves atslēga noņemta" }, - "unassignedItemsBanner": { - "message": "Jāņem vērā: nepiešķirti apvienības vienumi vairs nav redzami skatā \"Visas glabātavas\" un ir sasniedzami tikai no pārvaldības konsoles, kur šie vienumi jāpiešķir krājumam, lai padarītu tos redzamus." + "unassignedItemsBannerNotice": { + "message": "Jāņem vērā: nepiešķirti apvienības vienumi vairs nav redzami skatā \"Visas glabātavas\" un ir pieejami tikai pārvaldības konsolē." }, - "unassignedItemsBannerSelfHost": { - "message": "Jāņem vērā: 2024. gada 2. maijā nepiešķirti apvienības vienumi vairs nebūs redzami skatā \"Visas glabātavas\" un būs sasniedzami tikai no pārvaldības konsoles, kur šie vienumi jāpiešķir krājumam, lai padarītu tos redzamus." + "unassignedItemsBannerSelfHostNotice": { + "message": "Jāņem vērā: no 2024. gada 16. maija nepiešķirti apvienības vienumi vairs nebūs redzami skatā \"Visas glabātavas\" un būs pieejami tikai pārvaldības konsolē." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Piešķirt šos vienumus krājumam", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "lai padarītu tos redzamus.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "pārvaldības konsolē," + }, + "errorAssigningTargetCollection": { + "message": "Kļūda mērķa krājuma piešķiršanā." + }, + "errorAssigningTargetFolder": { + "message": "Kļūda mērķa mapes piešķiršanā." } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 9b66e6f0d6..1db5f6458b 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "പ്രാഥമിക പാസ്‌വേഡ് മാറ്റുക" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "ഫിംഗർപ്രിന്റ് ഫ്രേസ്‌", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "ചേർക്കപ്പെട്ട ഫോൾഡർ" }, - "changeMasterPass": { - "message": "പ്രാഥമിക പാസ്‌വേഡ് മാറ്റുക" - }, - "changeMasterPasswordConfirmation": { - "message": "തങ്ങൾക്കു ബിറ്റ് വാർഡൻ വെബ് വാൾട്ടിൽ പ്രാഥമിക പാസ്‌വേഡ് മാറ്റാൻ സാധിക്കും.വെബ്സൈറ്റ് ഇപ്പോൾ സന്ദർശിക്കാൻ ആഗ്രഹിക്കുന്നുവോ?" - }, "twoStepLoginConfirmation": { "message": "സുരക്ഷാ കീ, ഓതന്റിക്കേറ്റർ അപ്ലിക്കേഷൻ, SMS, ഫോൺ കോൾ അല്ലെങ്കിൽ ഇമെയിൽ പോലുള്ള മറ്റൊരു ഉപകരണം ഉപയോഗിച്ച് തങ്ങളുടെ ലോഗിൻ സ്ഥിരീകരിക്കാൻ ആവശ്യപ്പെടുന്നതിലൂടെ രണ്ട്-ഘട്ട ലോഗിൻ തങ്ങളുടെ അക്കൗണ്ടിനെ കൂടുതൽ സുരക്ഷിതമാക്കുന്നു. bitwarden.com വെബ് വാൾട്ടിൽ രണ്ട്-ഘട്ട ലോഗിൻ പ്രവർത്തനക്ഷമമാക്കാനാകും.തങ്ങള്ക്കു ഇപ്പോൾ വെബ്സൈറ്റ് സന്ദർശിക്കാൻ ആഗ്രഹമുണ്ടോ?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index f9f37b2511..06cf84efff 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "मुख्य पासवर्ड बदला" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "अंगुलिमुद्रा वाक्यांश", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 023e03b834..67e1f24787 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 82d847ff0f..220fe95e23 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Endre hovedpassordet" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingeravtrykksfrase", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "La til en mappe" }, - "changeMasterPass": { - "message": "Endre hovedpassordet" - }, - "changeMasterPasswordConfirmation": { - "message": "Du kan endre superpassordet ditt på bitwarden.net-netthvelvet. Vil du besøke det nettstedet nå?" - }, "twoStepLoginConfirmation": { "message": "2-trinnsinnlogging gjør kontoen din mer sikker, ved å kreve at du verifiserer din innlogging med en annen enhet, f.eks. en autentiseringsapp, SMS, e-post, telefonsamtale, eller sikkerhetsnøkkel. 2-trinnsinnlogging kan aktiveres i netthvelvet ditt på bitwarden.com. Vil du besøke bitwarden.com nå?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 023e03b834..67e1f24787 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 13d59c4546..808e599e70 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Hoofdwachtwoord wijzigen" }, + "continueToWebApp": { + "message": "Doorgaan naar web-app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Je kunt je hoofdwachtwoord wijzigen in de Bitwarden-webapp." + }, "fingerprintPhrase": { "message": "Vingerafdrukzin", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Map is toegevoegd" }, - "changeMasterPass": { - "message": "Hoofdwachtwoord wijzigen" - }, - "changeMasterPasswordConfirmation": { - "message": "Je kunt je hoofdwachtwoord wijzigen in de kluis op bitwarden.com. Wil je de website nu bezoeken?" - }, "twoStepLoginConfirmation": { "message": "Tweestapsaanmelding beschermt je account door je inlogpoging te bevestigen met een ander apparaat zoals een beveiligingscode, authenticatie-app, SMS, spraakoproep of e-mail. Je kunt Tweestapsaanmelding inschakelen in de webkluis op bitwarden.com. Wil je de website nu bezoeken?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Succes" + }, "removePasskey": { "message": "Passkey verwijderen" }, "passkeyRemoved": { "message": "Passkey verwijderd" }, - "unassignedItemsBanner": { - "message": "Let op: Niet-toegewezen organisatie-items zijn niet langer zichtbaar in de weergave van alle kluisjes en zijn alleen toegankelijk via de Admin Console. Om deze items zichtbaar te maken, moet je ze toewijzen aan een collectie via de Admin Console." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Kennisgeving: Vanaf 2 mei 2024 zijn niet-toegewezen organisatie-items op geen enkel apparaat meer zichtbaar in de weergave van alle kluisjes en alleen toegankelijk via de Admin Console. Je kunt deze items in het Admin Console aan een collectie toewijzen om ze zichtbaar te maken." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Fout bij toewijzen doelverzameling." + }, + "errorAssigningTargetFolder": { + "message": "Fout bij toewijzen doelmap." } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 023e03b834..67e1f24787 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 023e03b834..67e1f24787 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index e4b97ec956..1ef79bac42 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Zmień hasło główne" }, + "continueToWebApp": { + "message": "Kontynuować do aplikacji internetowej?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Możesz zmienić swoje hasło główne w aplikacji internetowej Bitwarden." + }, "fingerprintPhrase": { "message": "Unikalny identyfikator konta", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder został dodany" }, - "changeMasterPass": { - "message": "Zmień hasło główne" - }, - "changeMasterPasswordConfirmation": { - "message": "Hasło główne możesz zmienić na stronie sejfu bitwarden.com. Czy chcesz przejść do tej strony?" - }, "twoStepLoginConfirmation": { "message": "Logowanie dwustopniowe sprawia, że konto jest bardziej bezpieczne poprzez wymuszenie potwierdzenia logowania z innego urządzenia, takiego jak z klucza bezpieczeństwa, aplikacji uwierzytelniającej, wiadomości SMS, telefonu lub adresu e-mail. Logowanie dwustopniowe możesz włączyć w sejfie internetowym bitwarden.com. Czy chcesz przejść do tej strony?" }, @@ -2709,7 +2709,7 @@ "message": "Otwórz rozszerzenie w nowym oknie, aby dokończyć logowanie." }, "popoutExtension": { - "message": "Popout extension" + "message": "Otwórz rozszerzenie w nowym oknie" }, "launchDuo": { "message": "Uruchom DUO" @@ -2822,7 +2822,7 @@ "message": "Wybierz dane logowania do których przypisać passkey" }, "passkeyItem": { - "message": "Passkey Item" + "message": "Element Passkey" }, "overwritePasskey": { "message": "Zastąpić passkey?" @@ -3000,16 +3000,36 @@ "message": "Błąd podczas zapisywania danych logowania. Sprawdź konsolę, aby uzyskać szczegóły.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Sukces" + }, "removePasskey": { "message": "Usuń passkey" }, "passkeyRemoved": { "message": "Passkey został usunięty" }, - "unassignedItemsBanner": { - "message": "Uwaga: Nieprzypisane elementy w organizacji nie są już widoczne w widoku Wszystkie sejfy i są dostępne tylko przez Konsolę Administracyjną. Przypisz te elementy do kolekcji z konsoli administracyjnej, aby były one widoczne." + "unassignedItemsBannerNotice": { + "message": "Uwaga: Nieprzypisane elementy organizacji nie są już widoczne w widoku Wszystkie sejfy i są teraz dostępne tylko przez Konsolę Administracyjną." }, - "unassignedItemsBannerSelfHost": { - "message": "Uwaga: 2 maja 2024 r. nieprzypisane elementy w organizacji nie będą już widoczne w widoku Wszystkie sejfy i będą dostępne tylko przez Konsolę Administracyjną. Przypisz te elementy do kolekcji z konsoli administracyjnej, aby były one widoczne." + "unassignedItemsBannerSelfHostNotice": { + "message": "Uwaga: 16 maja 2024 r. nieprzypisana elementy w organizacji nie będą już widoczne w widoku Wszystkie sejfy i będą dostępne tylko przez Konsolę Administracyjną." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Przypisz te elementy do kolekcji z", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": ", aby były widoczne.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Konsola Administracyjna" + }, + "errorAssigningTargetCollection": { + "message": "Wystąpił błąd podczas przypisywania kolekcji." + }, + "errorAssigningTargetFolder": { + "message": "Wystąpił błąd podczas przypisywania folderu." } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index a4e0688e3d..9322e680d2 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Alterar Senha Mestra" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Frase Biométrica", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Pasta adicionada" }, - "changeMasterPass": { - "message": "Alterar Senha Mestra" - }, - "changeMasterPasswordConfirmation": { - "message": "Você pode alterar a sua senha mestra no cofre web em bitwarden.com. Você deseja visitar o site agora?" - }, "twoStepLoginConfirmation": { "message": "O login de duas etapas torna a sua conta mais segura ao exigir que digite um código de segurança de um aplicativo de autenticação quando for iniciar a sessão. O login de duas etapas pode ser ativado no cofre web bitwarden.com. Deseja visitar o site agora?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index c35531e445..d4fdc1be81 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Alterar palavra-passe mestra" }, + "continueToWebApp": { + "message": "Continuar para a aplicação Web?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Pode alterar a sua palavra-passe mestra na aplicação Web Bitwarden." + }, "fingerprintPhrase": { "message": "Frase de impressão digital", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Pasta adicionada" }, - "changeMasterPass": { - "message": "Alterar palavra-passe mestra" - }, - "changeMasterPasswordConfirmation": { - "message": "Pode alterar o seu endereço de e-mail no cofre do site bitwarden.com. Deseja visitar o site agora?" - }, "twoStepLoginConfirmation": { "message": "A verificação de dois passos torna a sua conta mais segura, exigindo que verifique o seu início de sessão com outro dispositivo, como uma chave de segurança, aplicação de autenticação, SMS, chamada telefónica ou e-mail. A verificação de dois passos pode ser configurada em bitwarden.com. Pretende visitar o site agora?" }, @@ -3000,16 +3000,36 @@ "message": "Erro ao guardar as credenciais. Verifique a consola para obter detalhes.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Com sucesso" + }, "removePasskey": { "message": "Remover chave de acesso" }, "passkeyRemoved": { "message": "Chave de acesso removida" }, - "unassignedItemsBanner": { - "message": "Aviso: Os itens da organização não atribuídos já não são visíveis na vista Todos os cofres e só são acessíveis através da consola de administração. Atribua estes itens a uma coleção a partir da Consola de administração para os tornar visíveis." + "unassignedItemsBannerNotice": { + "message": "Aviso: Os itens da organização não atribuídos já não são visíveis na vista Todos os cofres e só são acessíveis através da Consola de administração." }, - "unassignedItemsBannerSelfHost": { - "message": "Aviso: A 2 de maio de 2024, os itens da organização não atribuídos deixarão de ser visíveis na vista Todos os cofres e só estarão acessíveis através da Consola de administração. Atribua estes itens a uma coleção a partir da Consola de administração para os tornar visíveis." + "unassignedItemsBannerSelfHostNotice": { + "message": "Aviso: A 16 de maio de 2024, os itens da organização não atribuídos deixarão de estar visíveis na vista Todos os cofres e só estarão acessíveis através da consola de administração." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Atribua estes itens a uma coleção a partir da", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "para os tornar visíveis.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Consola de administração" + }, + "errorAssigningTargetCollection": { + "message": "Erro ao atribuir a coleção de destino." + }, + "errorAssigningTargetFolder": { + "message": "Erro ao atribuir a pasta de destino." } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 885d70ca93..5e8c82e70b 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Schimbare parolă principală" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fraza amprentă", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Dosar adăugat" }, - "changeMasterPass": { - "message": "Schimbare parolă principală" - }, - "changeMasterPasswordConfirmation": { - "message": "Puteți modifica parola principală în seiful web bitwarden.com. Doriți să vizitați saitul acum?" - }, "twoStepLoginConfirmation": { "message": "Autentificarea în două etape vă face contul mai sigur, prin solicitarea unei verificări de autentificare cu un alt dispozitiv, cum ar fi o cheie de securitate, o aplicație de autentificare, un SMS, un apel telefonic sau un e-mail. Autentificarea în două etape poate fi configurată în seiful web bitwarden.com. Doriți să vizitați site-ul web acum?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 69d9ca200f..046fe2b931 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Изменить мастер-пароль" }, + "continueToWebApp": { + "message": "Перейти к веб-приложению?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Изменить мастер-пароль можно в веб-приложении Bitwarden." + }, "fingerprintPhrase": { "message": "Фраза отпечатка", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Папка добавлена" }, - "changeMasterPass": { - "message": "Изменить мастер-пароль" - }, - "changeMasterPasswordConfirmation": { - "message": "Вы можете изменить свой мастер-пароль на bitwarden.com. Перейти на сайт сейчас?" - }, "twoStepLoginConfirmation": { "message": "Двухэтапная аутентификация делает аккаунт более защищенным, поскольку требуется подтверждение входа при помощи другого устройства, например, ключа безопасности, приложения-аутентификатора, SMS, телефонного звонка или электронной почты. Двухэтапная аутентификация включается на bitwarden.com. Перейти на сайт сейчас?" }, @@ -3000,16 +3000,36 @@ "message": "Ошибка сохранения учетных данных. Проверьте консоль для получения подробной информации.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Успешно" + }, "removePasskey": { "message": "Удалить passkey" }, "passkeyRemoved": { "message": "Passkey удален" }, - "unassignedItemsBanner": { - "message": "Обратите внимание: неприсвоенные элементы организации больше не видны в представлении \"Все хранилища\" и доступны только через консоль администратора. Назначьте эти элементы коллекции в консоли администратора, чтобы сделать их видимыми." + "unassignedItemsBannerNotice": { + "message": "Уведомление: Неприсвоенные элементы организации больше не видны в представлении \"Все хранилища\" и доступны только через консоль администратора." }, - "unassignedItemsBannerSelfHost": { - "message": "Уведомление: 2 мая 2024 года неприсвоенные элементы организации больше не будут видны в представлении \"Все хранилища\" и будут доступны только через консоль администратора. Назначьте эти элементы коллекции в консоли администратора, чтобы сделать их видимыми." + "unassignedItemsBannerSelfHostNotice": { + "message": "Уведомление: с 16 мая 2024 года не назначенные элементы организации больше не будут видны в представлении \"Все хранилища\" и будут доступны только через консоль администратора." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Назначьте эти элементы в коллекцию из", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "чтобы сделать их видимыми.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "консоли администратора" + }, + "errorAssigningTargetCollection": { + "message": "Ошибка при назначении целевой коллекции." + }, + "errorAssigningTargetFolder": { + "message": "Ошибка при назначении целевой папки." } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index fb026226bb..e576a50dd7 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "ප්රධාන මුරපදය වෙනස්" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "ඇඟිලි සලකුණු වාක්ය ඛණ්ඩය", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "එකතු කරන ලද ෆෝල්ඩරය" }, - "changeMasterPass": { - "message": "ප්රධාන මුරපදය වෙනස්" - }, - "changeMasterPasswordConfirmation": { - "message": "bitwarden.com වෙබ් සුරක්ෂිතාගාරයේ ඔබේ ප්රධාන මුරපදය වෙනස් කළ හැකිය. ඔබට දැන් වෙබ් අඩවියට පිවිසීමට අවශ්යද?" - }, "twoStepLoginConfirmation": { "message": "ආරක්ෂක යතුරක්, සත්යාපන යෙදුම, කෙටි පණිවුඩ, දුරකථන ඇමතුමක් හෝ විද්යුත් තැපෑල වැනි වෙනත් උපාංගයක් සමඟ ඔබේ පිවිසුම සත්යාපනය කිරීමට ඔබට අවශ්ය වීමෙන් ද්වි-පියවර පිවිසුම ඔබගේ ගිණුම වඩාත් සුරක්ෂිත කරයි. බිට්වොන්.com වෙබ් සුරක්ෂිතාගාරයේ ද්වි-පියවර පිවිසුම සක්රීය කළ හැකිය. ඔබට දැන් වෙබ් අඩවියට පිවිසීමට අවශ්යද?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index a7948d78f3..e34fa525be 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Zmeniť hlavné heslo" }, + "continueToWebApp": { + "message": "Pokračovať vo webovej aplikácii?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Hlavné heslo si môžete zmeniť vo webovej aplikácii Bitwarden." + }, "fingerprintPhrase": { "message": "Fráza odtlačku", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Pridaný priečinok" }, - "changeMasterPass": { - "message": "Zmeniť hlavné heslo" - }, - "changeMasterPasswordConfirmation": { - "message": "Teraz si môžete zmeniť svoje hlavné heslo vo webovom trezore bitwarden.com. Chcete navštíviť túto stránku teraz?" - }, "twoStepLoginConfirmation": { "message": "Dvojstupňové prihlasovanie robí váš účet bezpečnejším vďaka vyžadovaniu bezpečnostného kódu z overovacej aplikácie vždy, keď sa prihlásite. Dvojstupňové prihlasovanie môžete povoliť vo webovom trezore bitwarden.com. Chcete navštíviť túto stránku teraz?" }, @@ -3000,16 +3000,36 @@ "message": "Chyba pri ukladaní prihlasovacích údajov. Viac informácii nájdete v konzole.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Úspech" + }, "removePasskey": { "message": "Odstrániť prístupový kľúč" }, "passkeyRemoved": { "message": "Prístupový kľúč bol odstránený" }, - "unassignedItemsBanner": { - "message": "Upozornenie: Nepriradené položky organizácie už nie sú viditeľné v zobrazení Všetky Trezory a sú prístupné len cez administrátorskú konzolu. Aby boli viditeľné, priraďte tieto položky do kolekcie z konzoly administrátora." + "unassignedItemsBannerNotice": { + "message": "Upozornenie: Nepriradené položky organizácie už nie sú viditeľné v zobrazení Všetky trezory a sú prístupné iba cez Správcovskú konzolu." }, - "unassignedItemsBannerSelfHost": { - "message": "Upozornenie: 2. mája nepriradené položky organizácie už nebudú viditeľné v zobrazení Všetky Trezory a budú prístupné len cez administrátorskú konzolu. Aby boli viditeľné, priraďte tieto položky do kolekcie z konzoly administrátora." + "unassignedItemsBannerSelfHostNotice": { + "message": "Upozornenie: 16. mája 2024 nepriradené položky organizácie už nebudú viditeľné v zobrazení Všetky trezory a budú prístupné iba cez Správcovskú konzolu." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Priradiť tieto položky do zbierky zo", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": ", aby boli viditeľné.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Správcovská konzola" + }, + "errorAssigningTargetCollection": { + "message": "Chyba pri priraďovaní cieľovej kolekcie." + }, + "errorAssigningTargetFolder": { + "message": "Chyba pri priraďovaní cieľového priečinka." } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 2fac491c9c..c7f8ed0481 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Spremeni glavno geslo" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Identifikacijsko geslo", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Mapa dodana" }, - "changeMasterPass": { - "message": "Spremeni glavno geslo" - }, - "changeMasterPasswordConfirmation": { - "message": "Svoje glavno geslo lahko spremenite v Bitwardnovem spletnem trezorju. Želite zdaj obiskati Bitwardnovo spletno stran?" - }, "twoStepLoginConfirmation": { "message": "Avtentikacija v dveh korakih dodatno varuje vaš račun, saj zahteva, da vsakokratno prijavo potrdite z drugo napravo, kot je varnostni ključ, aplikacija za preverjanje pristnosti, SMS, telefonski klic ali e-pošta. Avtentikacijo v dveh korakih lahko omogočite v spletnem trezorju bitwarden.com. Ali želite spletno stran obiskati sedaj?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 6ec1b6181b..8d1ee8264f 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Промени главну лозинку" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Сигурносна Фраза Сефа", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Фасцикла додата" }, - "changeMasterPass": { - "message": "Промени главну лозинку" - }, - "changeMasterPasswordConfirmation": { - "message": "Можете променити главну лозинку у Вашем сефу на bitwarden.com. Да ли желите да посетите веб страницу сада?" - }, "twoStepLoginConfirmation": { "message": "Пријава у два корака чини ваш налог сигурнијим захтевом да верификујете своје податке помоћу другог уређаја, као што су безбедносни кључ, апликација, СМС-а, телефонски позив или имејл. Пријављивање у два корака може се омогућити на веб сефу. Да ли желите да посетите веб страницу сада?" }, @@ -3000,16 +3000,36 @@ "message": "Грешка при чувању акредитива. Проверите конзолу за детаље.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Уклонити приступачни кључ" }, "passkeyRemoved": { "message": "Приступачни кључ је уклоњен" }, - "unassignedItemsBanner": { - "message": "Напомена: Недодељене ставке организације више нису видљиве у приказу Сви сефови и доступне су само преко Админ конзоле. Доделите ове ставке колекцији са Админ конзолом да бисте их учинили видљивим." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Обавештење: 2. маја 2024. недодељене ставке организације више неће бити видљиве у приказу Сви сефови и биће доступне само преко Админ конзоле. Доделите ове ставке колекцији са Админ конзолом да бисте их учинили видљивим." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index d798b98ea0..ebe5e6d281 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Ändra huvudlösenord" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingeravtrycksfras", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Lade till mapp" }, - "changeMasterPass": { - "message": "Ändra huvudlösenord" - }, - "changeMasterPasswordConfirmation": { - "message": "Du kan ändra ditt huvudlösenord på bitwardens webbvalv. Vill du besöka webbplatsen nu?" - }, "twoStepLoginConfirmation": { "message": "Tvåstegsverifiering gör ditt konto säkrare genom att kräva att du verifierar din inloggning med en annan enhet, t.ex. en säkerhetsnyckel, autentiseringsapp, SMS, telefonsamtal eller e-post. Tvåstegsverifiering kan aktiveras i Bitwardens webbvalv. Vill du besöka webbplatsen nu?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 023e03b834..67e1f24787 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change master password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Folder added" }, - "changeMasterPass": { - "message": "Change master password" - }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 827ca72854..718440438e 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Change Master Password" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Fingerprint Phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "เพิ่มโฟลเดอร์แล้ว" }, - "changeMasterPass": { - "message": "Change Master Password" - }, - "changeMasterPasswordConfirmation": { - "message": "คุณสามารถเปลี่ยนรหัสผ่านหลักได้ที่เว็บตู้เซฟ bitwarden.com คุณต้องการเปิดเว็บไซต์เลยหรือไม่?" - }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to enter a security code from an authenticator app whenever you log in. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index cd0e12e6b0..7633b47258 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Ana parolayı değiştir" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "Parmak izi ifadesi", "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." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Klasör eklendi" }, - "changeMasterPass": { - "message": "Ana parolayı değiştir" - }, - "changeMasterPasswordConfirmation": { - "message": "Ana parolanızı bitwarden.com web kasası üzerinden değiştirebilirsiniz. Siteye gitmek ister misiniz?" - }, "twoStepLoginConfirmation": { "message": "İki aşamalı giriş, hesabınıza girererken işlemi bir güvenlik anahtarı, şifrematik uygulaması, SMS, telefon araması veya e-posta gibi ek bir yöntemle doğrulamanızı isteyerek hesabınızın güvenliğini artırır. İki aşamalı giriş özelliğini bitwarden.com web kasası üzerinden etkinleştirebilirsiniz. Şimdi siteye gitmek ister misiniz?" }, @@ -3000,16 +3000,36 @@ "message": "Kimlik bilgileri kaydedilirken hata oluştu. Ayrıntılar için konsolu kontrol edin.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 4820860de2..b09d966142 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Змінити головний пароль" }, + "continueToWebApp": { + "message": "Продовжити у вебпрограмі?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Ви можете змінити головний пароль у вебпрограмі Bitwarden." + }, "fingerprintPhrase": { "message": "Фраза відбитка", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "Теку додано" }, - "changeMasterPass": { - "message": "Змінити головний пароль" - }, - "changeMasterPasswordConfirmation": { - "message": "Ви можете змінити головний пароль в сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" - }, "twoStepLoginConfirmation": { "message": "Двоетапна перевірка дає змогу надійніше захистити ваш обліковий запис, вимагаючи підтвердження входу з використанням іншого пристрою, наприклад, за допомогою ключа безпеки, програми автентифікації, SMS, телефонного виклику, або е-пошти. Ви можете налаштувати двоетапну перевірку в сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" }, @@ -3000,16 +3000,36 @@ "message": "Помилка збереження облікових даних. Перегляньте подробиці в консолі.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Успішно" + }, "removePasskey": { "message": "Вилучити ключ доступу" }, "passkeyRemoved": { "message": "Ключ доступу вилучено" }, - "unassignedItemsBanner": { - "message": "Увага: непризначені елементи організації більше не видимі у поданні \"Усі сховища\" і доступні лише в консолі адміністратора. Щоб зробити їх видимими, призначте ці елементи збірці в консолі адміністратора." + "unassignedItemsBannerNotice": { + "message": "Примітка: непризначені елементи організації більше не видимі у поданні \"Усі сховища\" і доступні лише в консолі адміністратора." }, - "unassignedItemsBannerSelfHost": { - "message": "Сповіщення: 2 травня 2024 року, непризначені елементи організації більше не будуть видимі в поданні \"Усі сховища\", і будуть доступні лише через консоль адміністратора. Щоб зробити їх видимими, призначте ці елементи збірці в консолі адміністратора." + "unassignedItemsBannerSelfHostNotice": { + "message": "Примітка: 16 травня 2024 року непризначені елементи організації більше не будуть видимі у поданні \"Усі сховища\" і будуть доступні лише через консоль адміністратора." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Призначте ці елементи збірці в", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "щоб зробити їх видимими.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "консолі адміністратора," + }, + "errorAssigningTargetCollection": { + "message": "Помилка призначення цільової збірки." + }, + "errorAssigningTargetFolder": { + "message": "Помилка призначення цільової теки." } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 234e60e756..af518878b9 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "Thay đổi mật khẩu chính" }, + "continueToWebApp": { + "message": "Tiếp tục tới ứng dụng web?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Bạn có thể thay đổi mật khẩu chính của mình trên Bitwarden bản web." + }, "fingerprintPhrase": { "message": "Fingerprint Phrase", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -415,7 +421,7 @@ "message": "Khóa ngay" }, "lockAll": { - "message": "Lock all" + "message": "Khóa tất cả" }, "immediately": { "message": "Ngay lập tức" @@ -494,10 +500,10 @@ "message": "Tài khoản mới của bạn đã được tạo! Bạn có thể đăng nhập từ bây giờ." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Bạn đã đăng nhập thành công" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Bạn có thể đóng cửa sổ này" }, "masterPassSent": { "message": "Chúng tôi đã gửi cho bạn email có chứa gợi ý mật khẩu chính của bạn." @@ -522,16 +528,16 @@ "message": "Không thể tự động điền mục đã chọn trên trang này. Hãy thực hiện sao chép và dán thông tin một cách thủ công." }, "totpCaptureError": { - "message": "Unable to scan QR code from the current webpage" + "message": "Không thể quét mã QR từ trang web hiện tại" }, "totpCaptureSuccess": { - "message": "Authenticator key added" + "message": "Đã thêm khóa xác thực" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "Quét mã QR xác thực từ trang web hiện tại" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Sao chép khóa Authenticator (TOTP)" }, "loggedOut": { "message": "Đã đăng xuất" @@ -557,12 +563,6 @@ "addedFolder": { "message": "Đã thêm thư mục" }, - "changeMasterPass": { - "message": "Thay đổi mật khẩu chính" - }, - "changeMasterPasswordConfirmation": { - "message": "Bạn có thể thay đổi mật khẩu chính trong trang web kho lưu trữ của Bitwarden. Bạn có muốn truy cập trang web ngay bây giờ không?" - }, "twoStepLoginConfirmation": { "message": "Xác thực hai lớp giúp cho tài khoản của bạn an toàn hơn bằng cách yêu cầu bạn xác minh thông tin đăng nhập của bạn bằng một thiết bị khác như khóa bảo mật, ứng dụng xác thực, SMS, cuộc gọi điện thoại hoặc email. Bạn có thể bật xác thực hai lớp trong kho bitwarden nền web. Bạn có muốn ghé thăm trang web bây giờ?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Lưu ý: Các mục tổ chức chưa được chỉ định sẽ không còn hiển thị trong chế độ xem Tất cả Vault và chỉ có thể truy cập được qua Bảng điều khiển dành cho quản trị viên." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Lưu ý: Vào ngày 16 tháng 5 năm 2024, các mục tổ chức chưa được chỉ định sẽ không còn hiển thị trong chế độ xem Tất cả Vault và sẽ chỉ có thể truy cập được qua Bảng điều khiển dành cho quản trị viên." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Gán các mục này vào một bộ sưu tập từ", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "để làm cho chúng hiển thị.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Bảng điều khiển dành cho quản trị viên" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 519313df81..5842d4f4c1 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "更改主密码" }, + "continueToWebApp": { + "message": "前往网页 App 吗?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "指纹短语", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "文件夹已添加" }, - "changeMasterPass": { - "message": "修改主密码" - }, - "changeMasterPasswordConfirmation": { - "message": "您可以在 bitwarden.com 网页版密码库修改主密码。您现在要访问这个网站吗?" - }, "twoStepLoginConfirmation": { "message": "两步登录要求您从其他设备(例如安全钥匙、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在访问此网站吗?" }, @@ -3000,16 +3000,36 @@ "message": "保存凭据时出错。检查控制台以获取详细信息。", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "移除通行密钥" }, "passkeyRemoved": { "message": "通行密钥已移除" }, - "unassignedItemsBanner": { - "message": "注意:未分配的组织项目在「所有密码库」视图中不再可见,只能通过管理控制台访问。通过管理控制台将这些项目分配给集合以使其可见。" + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "注意:从 2024 年 5 月 2 日起,未分配的组织项目在「所有密码库」视图中将不再可见,只能通过管理控制台访问。通过管理控制台将这些项目分配给集合以使其可见。" + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index b6f1ff574a..a8163dab98 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -172,6 +172,12 @@ "changeMasterPassword": { "message": "變更主密碼" }, + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." + }, "fingerprintPhrase": { "message": "指紋短語", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." @@ -557,12 +563,6 @@ "addedFolder": { "message": "資料夾已新增" }, - "changeMasterPass": { - "message": "變更主密碼" - }, - "changeMasterPasswordConfirmation": { - "message": "您可以在 bitwarden.com 網頁版密碼庫變更主密碼。現在要前往嗎?" - }, "twoStepLoginConfirmation": { "message": "兩步驟登入需要您從其他裝置(例如安全鑰匙、驗證器程式、SMS、手機或電子郵件)來驗證您的登入,這使您的帳戶更加安全。兩步驟登入可以在 bitwarden.com 網頁版密碼庫啟用。現在要前往嗎?" }, @@ -3000,16 +3000,36 @@ "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." }, + "success": { + "message": "Success" + }, "removePasskey": { "message": "Remove passkey" }, "passkeyRemoved": { "message": "Passkey removed" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, - "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "adminConsole": { + "message": "Admin Console" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/browser/store/locales/gl/copy.resx b/apps/browser/store/locales/gl/copy.resx index d812256fb7..04e84f6677 100644 --- a/apps/browser/store/locales/gl/copy.resx +++ b/apps/browser/store/locales/gl/copy.resx @@ -158,7 +158,7 @@ Protexa e comparta datos confidenciais dentro da súa Caixa Forte de Bitwarden d Sincroniza e accede á túa caixa forte desde múltiples dispositivos - Xestiona todos os teus usuarios e contrasinais desde unha caixa forte segura + Xestiona todos os teus inicios de sesión e contrasinais desde unha caixa forte segura Autocompleta rapidamente os teus datos de acceso en calquera páxina web que visites From f6dee29a5fc0e48b7464181bd7f3c03280838fe6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:41:39 +0000 Subject: [PATCH 36/86] Autosync the updated translations (#8824) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 139 +++++++++- apps/web/src/locales/ar/messages.json | 139 +++++++++- apps/web/src/locales/az/messages.json | 141 +++++++++- apps/web/src/locales/be/messages.json | 139 +++++++++- apps/web/src/locales/bg/messages.json | 141 +++++++++- apps/web/src/locales/bn/messages.json | 139 +++++++++- apps/web/src/locales/bs/messages.json | 139 +++++++++- apps/web/src/locales/ca/messages.json | 151 ++++++++++- apps/web/src/locales/cs/messages.json | 141 +++++++++- apps/web/src/locales/cy/messages.json | 139 +++++++++- apps/web/src/locales/da/messages.json | 139 +++++++++- apps/web/src/locales/de/messages.json | 141 +++++++++- apps/web/src/locales/el/messages.json | 139 +++++++++- apps/web/src/locales/en_GB/messages.json | 139 +++++++++- apps/web/src/locales/en_IN/messages.json | 139 +++++++++- apps/web/src/locales/eo/messages.json | 139 +++++++++- apps/web/src/locales/es/messages.json | 139 +++++++++- apps/web/src/locales/et/messages.json | 139 +++++++++- apps/web/src/locales/eu/messages.json | 139 +++++++++- apps/web/src/locales/fa/messages.json | 139 +++++++++- apps/web/src/locales/fi/messages.json | 141 +++++++++- apps/web/src/locales/fil/messages.json | 139 +++++++++- apps/web/src/locales/fr/messages.json | 141 +++++++++- apps/web/src/locales/gl/messages.json | 139 +++++++++- apps/web/src/locales/he/messages.json | 139 +++++++++- apps/web/src/locales/hi/messages.json | 139 +++++++++- apps/web/src/locales/hr/messages.json | 139 +++++++++- apps/web/src/locales/hu/messages.json | 141 +++++++++- apps/web/src/locales/id/messages.json | 139 +++++++++- apps/web/src/locales/it/messages.json | 141 +++++++++- apps/web/src/locales/ja/messages.json | 143 +++++++++- apps/web/src/locales/ka/messages.json | 139 +++++++++- apps/web/src/locales/km/messages.json | 139 +++++++++- apps/web/src/locales/kn/messages.json | 139 +++++++++- apps/web/src/locales/ko/messages.json | 139 +++++++++- apps/web/src/locales/lv/messages.json | 153 ++++++++++- apps/web/src/locales/ml/messages.json | 139 +++++++++- apps/web/src/locales/mr/messages.json | 139 +++++++++- apps/web/src/locales/my/messages.json | 139 +++++++++- apps/web/src/locales/nb/messages.json | 139 +++++++++- apps/web/src/locales/ne/messages.json | 139 +++++++++- apps/web/src/locales/nl/messages.json | 139 +++++++++- apps/web/src/locales/nn/messages.json | 139 +++++++++- apps/web/src/locales/or/messages.json | 139 +++++++++- apps/web/src/locales/pl/messages.json | 293 ++++++++++++++------ apps/web/src/locales/pt_BR/messages.json | 323 ++++++++++++++++------- apps/web/src/locales/pt_PT/messages.json | 139 +++++++++- apps/web/src/locales/ro/messages.json | 139 +++++++++- apps/web/src/locales/ru/messages.json | 139 +++++++++- apps/web/src/locales/si/messages.json | 139 +++++++++- apps/web/src/locales/sk/messages.json | 139 +++++++++- apps/web/src/locales/sl/messages.json | 139 +++++++++- apps/web/src/locales/sr/messages.json | 141 +++++++++- apps/web/src/locales/sr_CS/messages.json | 139 +++++++++- apps/web/src/locales/sv/messages.json | 139 +++++++++- apps/web/src/locales/te/messages.json | 139 +++++++++- apps/web/src/locales/th/messages.json | 139 +++++++++- apps/web/src/locales/tr/messages.json | 139 +++++++++- apps/web/src/locales/uk/messages.json | 141 +++++++++- apps/web/src/locales/vi/messages.json | 139 +++++++++- apps/web/src/locales/zh_CN/messages.json | 145 +++++++++- apps/web/src/locales/zh_TW/messages.json | 139 +++++++++- 62 files changed, 8691 insertions(+), 321 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 6dfb5ec30d..51ba16e5e0 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 1923502b1a..4377e5655b 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 911031f0f1..2e94d219d8 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -1474,7 +1474,7 @@ "message": "Veb anbarda istifadə olunan dili dəyişdirin." }, "enableFavicon": { - "message": "Veb sayt nişanlarını göstər" + "message": "Veb sayt ikonlarını göstər" }, "faviconDesc": { "message": "Hər girişin yanında tanına bilən təsvir göstər." @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provayder Portal" }, + "success": { + "message": "Uğurlu" + }, "viewCollection": { "message": "Kolleksiyaya bax" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Maşın hesabına müraciət güncəlləndi" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "Özünüzü bir qrupa əlavə edə bilməzsiniz." }, "unassignedItemsBannerSelfHost": { "message": "Bildiriş: 2 May 2024-cü ildən etibarən təyin edilməmiş təşkilat elementləri artıq cihazlar arasında Bütün Anbarlar görünüşündə görünməyən və yalnız Admin Konsolu vasitəsilə əlçatan olacaq. Bu elementləri görünən etmək üçün Admin Konsolundan bir kolleksiyaya təyin edin." + }, + "unassignedItemsBannerNotice": { + "message": "Bildiriş: Təyin edilməyən təşkilat elementləri artıq Bütün Anbarlar görünüşündə görünən olmayacaq və yalnız Admin Konsolu vasitəsilə əlçatan olacaq." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Bildiriş: 16 May 2024-cü il tarixindən etibarən, təyin edilməyən təşkilat elementləri cihazlar arasında və Bütün Anbarlar görünüşündə görünən olmayacaq və yalnız Admin Konsolu vasitəsilə əlçatan olacaq." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Bu elementləri görünən etmək üçün", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "bir kolleksiyaya təyin edin.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Provayderi sil" + }, + "deleteProviderConfirmation": { + "message": "Bir provayderin silinməsi daimi və geri qaytarıla bilməyən prosesdir. Provayderin və əlaqəli bütün datanın silinməsini təsdiqləmək üçün ana parolunuzu daxil edin." + }, + "deleteProviderName": { + "message": "$ID$ silinə bilmir", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "$ID$ silinməzdən əvvəl bütün müştəriləri (client) ayırmalısınız", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provayder silindi" + }, + "providerDeletedDesc": { + "message": "Provayder və bütün əlaqəli datalar silindi." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Bu Provayderi silmək üçün tələb göndərdiniz. Təsdiqləmək üçün aşağıdakı düyməni istifadə edin." + }, + "deleteProviderWarning": { + "message": "Provayderin silinməsi daimi prosesdir. Geri dönüşü olmayacaq." + }, + "errorAssigningTargetCollection": { + "message": "Hədəf kolleksiyaya təyin etmə xətası." + }, + "errorAssigningTargetFolder": { + "message": "Hədəf qovluğa təyin etmə xətası." + }, + "integrationsAndSdks": { + "message": "İnteqrasiyalar və SDK-lar", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "İnteqrasiyalar" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDK-lar" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Github Actions qur" + }, + "setUpGitlabCICD": { + "message": "GitLab CI/CD qur" + }, + "setUpAnsible": { + "message": "Ansible qur" + }, + "cSharpSDKRepo": { + "message": "C# repozitoriyasına bax" + }, + "cPlusPlusSDKRepo": { + "message": "C++ repozitoriyasına bax" + }, + "jsWebAssemblySDKRepo": { + "message": "JS WebAssembly repozitoriyasına bax" + }, + "javaSDKRepo": { + "message": "Java repozitoriyasına bax" + }, + "pythonSDKRepo": { + "message": "Python repozitoriyasına bax" + }, + "phpSDKRepo": { + "message": "php repozitoriyasına bax" + }, + "rubySDKRepo": { + "message": "Ruby repozitoriyasına bax" + }, + "goSDKRepo": { + "message": "Go repozitoriyasına bax" + }, + "createNewClientToManageAsProvider": { + "message": "Provayder kimi idarə etmək üçün yeni bir client təşkilatı yaradın. Əlavə yerlər növbəti faktura dövründə əks olunacaq." + }, + "selectAPlan": { + "message": "Bir plan seçin" + }, + "thirtyFivePercentDiscount": { + "message": "35% endirim" + }, + "monthPerMember": { + "message": "üzv başına ay" + }, + "seats": { + "message": "Yer" + }, + "addOrganization": { + "message": "Təşkilat əlavə et" + }, + "createdNewClient": { + "message": "Yeni client uğurla yaradıldı" + }, + "noAccess": { + "message": "Müraciət yoxdur" + }, + "collectionAdminConsoleManaged": { + "message": "Bu kolleksiya yalnız admin konsolundan əlçatandır" } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index c93f773382..2cfb8af125 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index b3c20f50e2..05a819f97a 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Портал за доставчици" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "Преглед на колекцията" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Достъпът на машинния акаунт е променен" }, - "unassignedItemsBanner": { - "message": "Известие: неразпределените елементи на организацията вече не се виждат в изгледа с „Всички трезори“ на различните устройства, а са достъпни само през Административната конзола. Добавете тези елементи към някоя колекция в Административната конзола, за да станат видими." + "restrictedGroupAccessDesc": { + "message": "Не може да добавяте себе си към групи." }, "unassignedItemsBannerSelfHost": { - "message": "Известие: от 2 май 2024г. неразпределените елементи на организациите вече няма се виждат в изгледа с „Всички трезори“ на различните устройства, а ще бъдат достъпни само през Административната конзола. Добавете тези елементи към някоя колекция в Административната конзола, за да станат видими." + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Известие: неразпределените елементи в организацията вече няма да се виждат в изгледа с всички трезори на различните устройства, а са достъпни само през Административната конзола." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Известие: след 16 май 2024, неразпределените елементи в организацията вече няма да се виждат в изгледа с всички трезори на различните устройства, а ще бъдат достъпни само през Административната конзола." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Добавете тези елементи към колекция в", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "за да ги направите видими.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Изтриване на доставчик" + }, + "deleteProviderConfirmation": { + "message": "Изтриването на доставчик е окончателно и необратимо. Въведете главната си парола, за да потвърдите изтриването на доставчика и всички свързани данни." + }, + "deleteProviderName": { + "message": "Изтриването на $ID$ е невъзможно", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Трябва да разкачите всички клиенти, преди да можете да изтриете $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Доставчикът е изтрит" + }, + "providerDeletedDesc": { + "message": "Доставчикът и всички свързани данни са изтрити." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Заявили сте, че искате да изтриете този доставчик. Използвайте бутона по-долу, за да потвърдите това решение." + }, + "deleteProviderWarning": { + "message": "Изтриването на доставчика Ви е окончателно и необратимо." + }, + "errorAssigningTargetCollection": { + "message": "Грешка при задаването на целева колекция." + }, + "errorAssigningTargetFolder": { + "message": "Грешка при задаването на целева папка." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index afde1e40d4..46d0e574ce 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index f5cddc79db..85880f9cbf 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 2bec515116..cc072c3b96 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Portal del proveïdor" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "Mostra col·lecció" }, @@ -7625,13 +7628,13 @@ "message": "Assigna a aquestes col·leccions" }, "bulkCollectionAssignmentDialogDescription": { - "message": "Select the collections that the items will be shared with. Once an item is updated in one collection, it will be reflected in all collections. Only organization members with access to these collections will be able to see the items." + "message": "Seleccioneu les col·leccions amb les quals es compartiran els elements. Una vegada que un element s'actualitza en una col·lecció, es reflectirà a totes les col·leccions. Només els membres de l'organització amb accés a aquestes col·leccions podran veure els elements." }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "Seleccioneu les col·leccions per assignar" }, "noCollectionsAssigned": { - "message": "No collections have been assigned" + "message": "No s'ha assignat cap col·lecció" }, "successfullyAssignedCollections": { "message": "Successfully assigned collections" @@ -7650,7 +7653,7 @@ } }, "items": { - "message": "Items" + "message": "Elements" }, "assignedSeats": { "message": "Assigned seats" @@ -7659,10 +7662,10 @@ "message": "Assigned" }, "used": { - "message": "Used" + "message": "Utilitzat" }, "remaining": { - "message": "Remaining" + "message": "Queden" }, "unlinkOrganization": { "message": "Unlink organization" @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 82dfe2ecae..6b82766cd3 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Portál poskytovatele" }, + "success": { + "message": "Úspěch" + }, "viewCollection": { "message": "Zobrazit kolekci" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Přístup strojového účtu byl aktualizován" }, - "unassignedItemsBanner": { - "message": "Upozornění: Nepřiřazené položky organizace již nejsou viditelné ve Vašem zobrazení všech trezorů napříč zařízeními a jsou nyní přístupné jen v konzoli správce. Přiřaďte tyto položky do kolekce z konzole pro správce, aby byly viditelné." + "restrictedGroupAccessDesc": { + "message": "Do skupiny nemůžete přidat sami sebe." }, "unassignedItemsBannerSelfHost": { - "message": "Upozornění: Dne 2. května 2024 již nebudou nepřiřazené položky organizace viditelné v zobrazení Všechny trezory ve všech zařízeních a budou přístupné jen prostřednictvím konzoly správce. Přiřaďte tyto položky do kolekce z konzoly pro správce, aby byly viditelné." + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Upozornění: Nepřiřazené položky organizace již nejsou viditelné ve vašem zobrazení všech trezorů napříč zařízeními a jsou nyní přístupné pouze v konzoli správce." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Upozornění: 16. květba 2024 již nebudou nepřiřazené položky organizace viditelné ve vašem zobrazení všech trezorů napříč zařízeními a budou přístupné pouze v konzoli správce." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Přiřadit tyto položky ke kolekci z", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "aby byly viditelné.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Smazat poskytovatele" + }, + "deleteProviderConfirmation": { + "message": "Smazání poskytovatele je trvalé a nevratné. Zadejte hlavní heslo pro potvrzení smazání poskytovatele a všech souvisejících dat." + }, + "deleteProviderName": { + "message": "Nelze smazat $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Před smazáním $ID$ musíte odpojit všechny klienty", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Poskytovatel byl smazán" + }, + "providerDeletedDesc": { + "message": "Poskytovatel a veškerá související data byla smazána." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Požádali jste o smazání tohoto poskytovatele. Pro potvrzení použijte tlačítko níže." + }, + "deleteProviderWarning": { + "message": "Smazání poskytovatele je trvalé. Tuto akci nelze vrátit zpět." + }, + "errorAssigningTargetCollection": { + "message": "Chyba při přiřazování cílové kolekce." + }, + "errorAssigningTargetFolder": { + "message": "Chyba při přiřazování cílové složky." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Vytvořte novou klientskou organizaci pro správu jako poskytovatele. Další uživatelé budou reflektováni v dalším platebním cyklu." + }, + "selectAPlan": { + "message": "Vyberte plán" + }, + "thirtyFivePercentDiscount": { + "message": "35% sleva" + }, + "monthPerMember": { + "message": "měsíčně za člena" + }, + "seats": { + "message": "Počet" + }, + "addOrganization": { + "message": "Přidat organizaci" + }, + "createdNewClient": { + "message": "Nový klient byl úspěšně vytvořen" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 8ee7ab3569..7394c3fe2a 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index e52510ed1c..081fef7865 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Udbyderportal" }, + "success": { + "message": "Gennemført" + }, "viewCollection": { "message": "Vis samling" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Maskinekontoadgang opdateret" }, - "unassignedItemsBanner": { - "message": "Bemærk: Utildelte organisationsemner er ikke længere synlige i Alle Bokse-visningen og er kun tilgængelige via Adminkonsollen. Føj disse emner til en samling fra Adminkonsollen for at gøre dem synlige." + "restrictedGroupAccessDesc": { + "message": "Man kan ikke føje sig selv til en gruppe." }, "unassignedItemsBannerSelfHost": { "message": "Bemærk: Pr. 2. maj 2024 vil utildelte organisationsemner ikke længere være synlige i Alle Bokse-visningen på tværs af enheder og vil kun være tilgængelige via Admin-konsollen. Tildel disse emner til en samling via Admin-konsollen for at gøre dem synlige." + }, + "unassignedItemsBannerNotice": { + "message": "Bemærk: Utildelte organisationsemner er ikke længere synlige i Alle Bokse-visningen, men er kun tilgængelige via Admin-konsol." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Bemærk: Pr. 16. maj 2024 er utildelte organisationsemner er ikke længere synlige i Alle Bokse-visningen, men er kun tilgængelige via Admin-konsol." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Tildel disse emner til en samling via", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "for at gøre dem synlige.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Slet udbyder" + }, + "deleteProviderConfirmation": { + "message": "Sletning af en udbyder er permanent og irreversibel. Angiv hovedadgangskoden for at bekræfte sletningen af udbyderen og alle tilknyttede data." + }, + "deleteProviderName": { + "message": "Kan ikke slette $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Alle klienttilknytninger skal fjernes, før $ID$ kan slettes", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Udbyder er hermed slettet" + }, + "providerDeletedDesc": { + "message": "Udbyderen og alle tilknyttede data er hermed slettet." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Der er anmodet sletning af din Bitwarden-konto. Klik på knappen nedenfor for at bekræfte." + }, + "deleteProviderWarning": { + "message": "Sletning af kontoen er permanent og irreversibel." + }, + "errorAssigningTargetCollection": { + "message": "Fejl ved tildeling af målsamling." + }, + "errorAssigningTargetFolder": { + "message": "Fejl ved tildeling af målmappe." + }, + "integrationsAndSdks": { + "message": "Integrationer og SDK'er", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrationer" + }, + "integrationsDesc": { + "message": "Synk automatisk hemmeligheder fra Bitwarden Secrets Manager til en tredjepartstjeneste." + }, + "sdks": { + "message": "SDK'er" + }, + "sdksDesc": { + "message": "Brug Bitwarden Secrets Manager SDK i flg. programmeringssprog til bygning af egne applikationer." + }, + "setUpGithubActions": { + "message": "Opsæt Github-handlinger" + }, + "setUpGitlabCICD": { + "message": "Opsæt GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Opsæt Ansible" + }, + "cSharpSDKRepo": { + "message": "Vis C#-repo" + }, + "cPlusPlusSDKRepo": { + "message": "Vis C++-repo" + }, + "jsWebAssemblySDKRepo": { + "message": "VIs JS WebAssembly-repo" + }, + "javaSDKRepo": { + "message": "Vis Java-repo" + }, + "pythonSDKRepo": { + "message": "Vis Python-repo" + }, + "phpSDKRepo": { + "message": "Vis php-repo" + }, + "rubySDKRepo": { + "message": "Vis Ruby-repo" + }, + "goSDKRepo": { + "message": "Vis Go-repo" + }, + "createNewClientToManageAsProvider": { + "message": "Opret en ny kundeorganisation til at håndtere som udbyder. Yderligere pladser afspejles i næste faktureringscyklus." + }, + "selectAPlan": { + "message": "Vælg en abonnementstype" + }, + "thirtyFivePercentDiscount": { + "message": "35% rabat" + }, + "monthPerMember": { + "message": "måned pr. medlem" + }, + "seats": { + "message": "Pladser" + }, + "addOrganization": { + "message": "Tilføj organisation" + }, + "createdNewClient": { + "message": "Ny kunde er hermed oprettet" + }, + "noAccess": { + "message": "Ingen adgang" + }, + "collectionAdminConsoleManaged": { + "message": "Denne samling er kun tilgængelig via Admin-konsol" } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 0e9b0a983f..3fb7598416 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Anbieterportal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "Sammlung anzeigen" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Zugriff auf Gerätekonto aktualisiert" }, - "unassignedItemsBanner": { - "message": "Hinweis: Nicht zugewiesene Organisationseinträge sind nicht mehr geräteübergreifend in der Ansicht aller Tresore sichtbar und sind nun nur über die Administrator-Konsole zugänglich. Weise diese Einträge einer Sammlung aus der Administrator-Konsole zu, um sie sichtbar zu machen." + "restrictedGroupAccessDesc": { + "message": "Du kannst dich nicht selbst zu einer Gruppe hinzufügen." }, "unassignedItemsBannerSelfHost": { - "message": "Hinweis: Ab dem 2. Mai 2024 werden nicht zugewiesene Organisationseinträge nicht mehr geräteübergreifend in der Ansicht aller Tresore sichtbar sein und sind nur über die Administrator-Konsole zugänglich. Weise diese Elemente einer Sammlung aus der Administrator-Konsole zu, um sie sichtbar zu machen." + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Fehler beim Zuweisen der Ziel-Sammlung." + }, + "errorAssigningTargetFolder": { + "message": "Fehler beim Zuweisen des Ziel-Ordners." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 256a5644d6..742a76402d 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 152c6a89e1..66f728cb4d 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index d4d22f6aba..c0c77d3d78 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 899558160f..2a6db2ea4b 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 78b051d588..9f0f085e9d 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index fdb656f31e..0c3f7cb300 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 0125ceec25..c315d62520 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index e91ea40d05..32feda3edd 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 18fa9c15fb..377f7b084f 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Toimittajaportaali" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "Tarkastele kokoelmaa" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Konetilin oikeuksia muutettiin" }, - "unassignedItemsBanner": { - "message": "Huomautus: Organisaatioiden kokoelmiin määrittämättömät kohteet eivät enää näy laitteiden \"Kaikki holvit\" -näkymissä, vaan ne ovat nähtävissä vain Hallintapaneelista. Määritä kohteet kokoelmiin Hallintapaneelista, jotta ne ovat jatkossakin käytettävissä kaikilta laitteilta." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { - "message": "Huomautus: 2.5.2024 alkaen kokoelmiin määrittämättömät organisaatioiden kohteet eivät enää näy laitteiden \"Kaikki holvit\" -näkymissä, vaan ne ovat nähtävissä vain Hallintapaneelista. Määritä kohteet kokoelmiin Hallintapaneelista, jotta ne ovat jatkossakin käytettävissä kaikilta laitteilta." + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 2cf9eb3b7e..50c9f8d4e8 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 27b529442c..2934176956 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Portail fournisseur" }, + "success": { + "message": "Succès" + }, "viewCollection": { "message": "Afficher la Collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Accès au compte machine mis à jour" }, - "unassignedItemsBanner": { - "message": "Remarque : les éléments d'organisation non assignés ne sont plus visibles dans votre vue Tous les coffres sur tous les appareils et sont uniquement accessibles via la Console d'administration. Assignez ces éléments à une collection à partir de la Console d'administration pour les rendre visibles." + "restrictedGroupAccessDesc": { + "message": "Vous ne pouvez pas vous ajouter vous-même à un groupe." }, "unassignedItemsBannerSelfHost": { - "message": "Remarque : au 2 mai 2024, les éléments d'organisation non assignés ne sont plus visibles dans votre vue Tous les coffres sur tous les appareils et sont uniquement accessibles via la Console d'administration. Assignez ces éléments à une collection à partir de la Console d'administration pour les rendre visibles." + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Remarque : Les éléments d'organisation non assignés ne sont plus visibles dans la vue Tous les coffres sur les appareils et ne sont maintenant accessibles que via la Console Admin." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Remarque : À partir du 16 mai 2024, les éléments d'organisation non assignés ne seront plus visibles dans la vue Tous les coffres sur les appareils et ne seront maintenant accessibles que via la Console Admin." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assigner ces éléments à une collection depuis", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "pour les rendre visibles.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Supprimer le fournisseur" + }, + "deleteProviderConfirmation": { + "message": "La suppression d'un fournisseur est permanente et irréversible. Entrez votre mot de passe principal pour confirmer la suppression du fournisseur et de toutes les données associées." + }, + "deleteProviderName": { + "message": "Impossible de supprimer $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Vous devez dissocier tous les clients avant de pouvoir supprimer $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Fournisseur supprimé" + }, + "providerDeletedDesc": { + "message": "Le fournisseur et toutes les données associées ont été supprimés." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Vous avez demandé à supprimer ce fournisseur. Utilisez le bouton ci-dessous pour confirmer." + }, + "deleteProviderWarning": { + "message": "La suppression de votre fournisseur est permanente. Elle ne peut pas être annulée." + }, + "errorAssigningTargetCollection": { + "message": "Erreur lors de l'assignation de la collection cible." + }, + "errorAssigningTargetFolder": { + "message": "Erreur lors de l'assignation du dossier cible." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Créez une nouvelle organisation de clients à gérer en tant que Fournisseur. Des sièges supplémentaires seront reflétés lors du prochain cycle de facturation." + }, + "selectAPlan": { + "message": "Sélectionnez un plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% de réduction" + }, + "monthPerMember": { + "message": "mois par membre" + }, + "seats": { + "message": "Licences" + }, + "addOrganization": { + "message": "Ajouter une organisation" + }, + "createdNewClient": { + "message": "Nouveau client créé avec succès" + }, + "noAccess": { + "message": "Aucun accès" + }, + "collectionAdminConsoleManaged": { + "message": "Cette collection n'est accessible qu'à partir de la Console Admin" } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 781c9000cb..ed04c3a3ef 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index fd42338a28..b7b4d59227 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index f0487066d6..23321b27ef 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 20a62053f0..edc6adaf3b 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 35781b814a..4ec6f15029 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Szolgáltató portál" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "Gyűjtemény megtekintése" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "A gépi fiók elérése frissítésre került." }, - "unassignedItemsBanner": { - "message": "Megjegyzés: A nem hozzá rendelt szervezeti elemek már nem láthatók az Összes széf nézetben a különböző eszközökön és mostantól csak a Felügyeleti konzolon keresztül érhetők el. Rendeljük ezeket az elemeket egy gyűjteményhez az Adminisztrátori konzolból, hogy láthatóvá tegyeük azokat." + "restrictedGroupAccessDesc": { + "message": "Nem adhadjuk magunkat a csoporthoz." }, "unassignedItemsBannerSelfHost": { - "message": "Megjegyzés: A nem hozzá rendelt szervezeti elemek már nem láthatók az Összes széf nézetben a különböző eszközökön és mostantól csak a Felügyeleti konzolon keresztül érhetők el. Rendeljük ezeket az elemeket egy gyűjteményhez az Adminisztrátori konzolból, hogy láthatóvá tegyeük azokat." + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Megjegyzés: A nem hozzárendelt szervezeti elemek már nem láthatók az Összes széf nézetben a különböző eszközökön és csak az Adminisztrátori konzolon keresztül érhetők el." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Megjegyzés: 2024. május 16-tól a nem hozzárendelt szervezeti elemek többé nem lesznek láthatók az Összes széf nézetben a különböző eszközökön és csak az Adminisztrátoir konzolon keresztül lesznek elérhetők." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Rendeljük hozzá ezeket az elemeket a gyűjteményhez", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "a láthatósághoz.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Hiba történt a célgyűjtemény hozzárendelése során." + }, + "errorAssigningTargetFolder": { + "message": "Hiba történt a célmappa hozzárendelése során." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "Nincs hozzáférés." + }, + "collectionAdminConsoleManaged": { + "message": "Ez a gyűjtemény csak az adminisztrátori konzolról érhető el." } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 4cf235eb16..3bc9ef4e62 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 1cdee4420d..11dec69959 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Portale Fornitori" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "Visualizza raccolta" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Accesso all'account macchina aggiornato" }, - "unassignedItemsBanner": { - "message": "Avviso: gli elementi dell'organizzazione non assegnati non sono più visibili nella visualizzazione Tutte le Cassaforti su tutti i dispositivi e sono ora accessibili solo tramite la Console di amministrazione. Assegna questi elementi ad una raccolta dalla Console di amministrazione per renderli visibili." + "restrictedGroupAccessDesc": { + "message": "Non puoi aggiungerti a un gruppo." }, "unassignedItemsBannerSelfHost": { - "message": "Avviso: dal 2 maggio 2024, gli elementi dell'organizzazione non assegnati non saranno più visibili nella tua visualizzazione Tutte le Cassaforti su tutti i dispositivi e saranno accessibili solo tramite la Console di amministrazione. Assegna questi elementi ad una raccolta dalla Console di amministrazione per renderli visibili." + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Avviso: gli elementi dell'organizzazione non assegnati non sono più visibili nella visualizzazione Tutte le Cassaforti su tutti i dispositivi e sono ora accessibili solo tramite la Console di amministrazione." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Avviso: dal 2 maggio 2024, gli elementi dell'organizzazione non assegnati non saranno più visibili nella tua visualizzazione Tutte le Cassaforti su tutti i dispositivi e saranno accessibili solo tramite la Console di amministrazione." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assegna questi elementi ad una raccolta dalla", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "per renderli visibili.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Elimina fornitore" + }, + "deleteProviderConfirmation": { + "message": "La cancellazione di un fornitore è permanente e irreversibile. Inserisci la tua password principale per confermare l'eliminazione del fornitore e di tutti i dati associati." + }, + "deleteProviderName": { + "message": "Impossibile eliminare $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Devi scollegare tutti i client prima di poter eliminare $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Fornitore eliminato" + }, + "providerDeletedDesc": { + "message": "Il fornitore e tutti i dati associati sono stati eliminati." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Hai richiesto di eliminare il questo fornitore. Clicca qui sotto per confermare." + }, + "deleteProviderWarning": { + "message": "L'eliminazione del fornitore è permanente. Questa azione non è reversibile." + }, + "errorAssigningTargetCollection": { + "message": "Errore nell'assegnazione della raccolta di destinazione." + }, + "errorAssigningTargetFolder": { + "message": "Errore nell'assegnazione della cartella di destinazione." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Crea una nuova organizzazione cliente da gestire come fornitore. Gli slot aggiuntivi saranno riflessi nel prossimo ciclo di fatturazione." + }, + "selectAPlan": { + "message": "Seleziona un piano" + }, + "thirtyFivePercentDiscount": { + "message": "Sconto del 35%" + }, + "monthPerMember": { + "message": "mese per membro" + }, + "seats": { + "message": "Slot" + }, + "addOrganization": { + "message": "Aggiungi organizzazione" + }, + "createdNewClient": { + "message": "Nuovo cliente creato con successo" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 3561c81bd1..17b58a4299 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -7601,11 +7601,14 @@ "message": "リリースブログを読む" }, "adminConsole": { - "message": "管理者コンソール" + "message": "管理コンソール" }, "providerPortal": { "message": "プロバイダーポータル" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "コレクションを表示" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "マシンアカウントへのアクセス権限を更新しました" }, - "unassignedItemsBanner": { - "message": "注意: 割り当てられていない組織項目は、デバイス間のすべての保管庫のビューでは表示されなくなり、管理コンソールからのみアクセスできます。 管理コンソールからコレクションにこれらのアイテムを割り当てると、表示するようにできます。" + "restrictedGroupAccessDesc": { + "message": "あなた自身をグループに追加することはできません。" }, "unassignedItemsBannerSelfHost": { - "message": "お知らせ:2024年5月2日に、 割り当てられていない組織アイテムはデバイス間のすべての保管庫ビューに表示されなくなり、管理コンソールからのみアクセス可能になります。 管理コンソールからコレクションにこれらのアイテムを割り当てると、表示できるようになります。" + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "注意: 割り当てられていない組織アイテムは、デバイス間のすべての保管庫ビューでは表示されなくなり、管理コンソールからのみアクセスできるようになりました。" + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "お知らせ:2024年5月16日に、 割り当てられていない組織アイテムは、デバイス間のすべての保管庫ビューに表示されなくなり、管理コンソールからのみアクセス可能になります。" + }, + "unassignedItemsBannerCTAPartOne": { + "message": "これらのアイテムのコレクションへの割り当てを", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "で実行すると表示できるようになります。", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "プロバイダを削除" + }, + "deleteProviderConfirmation": { + "message": "プロバイダの削除は恒久的で元に戻せません。マスターパスワードを入力して、プロバイダと関連するすべてのデータの削除を確認してください。" + }, + "deleteProviderName": { + "message": "$ID$ を削除できません", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "$ID$ を削除する前に、すべてのクライアントのリンクを解除してください", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "プロバイダを削除しました" + }, + "providerDeletedDesc": { + "message": "プロバイダと関連するすべてのデータを削除しました。" + }, + "deleteProviderRecoverConfirmDesc": { + "message": "このプロバイダの削除をリクエストしました。下のボタンを使うと確認できます。" + }, + "deleteProviderWarning": { + "message": "プロバイダを削除すると元に戻すことはできません。" + }, + "errorAssigningTargetCollection": { + "message": "ターゲットコレクションの割り当てに失敗しました。" + }, + "errorAssigningTargetFolder": { + "message": "ターゲットフォルダーの割り当てに失敗しました。" + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index d94a701410..6d34252b88 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 781c9000cb..ed04c3a3ef 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index f2a5bdd829..ae6f2c2a89 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index b54268adbd..f9240f21aa 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index d68a62bdcf..0dc0dff097 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -3360,7 +3360,7 @@ "message": "Ja konts pastāv, tika nosūtīts e-pasta ziņojums ar turpmākām norādēm." }, "deleteRecoverConfirmDesc": { - "message": "Tika pieprasīts izdzēst Bitwarden kontu. Jānospiež zemāk esošā poga, lai apstiprinātu." + "message": "Tika pieprasīts izdzēst Bitwarden kontu. Jāizmanto zemāk esošā poga, lai apstiprinātu." }, "myOrganization": { "message": "Mana apvienība" @@ -3396,7 +3396,7 @@ "message": "Apvienība izdzēsta" }, "organizationDeletedDesc": { - "message": "Apvienība un visi ar to saistītie dati ir izdzēsti." + "message": "Apvienība un visi ar to saistītie dati tika izdzēsti." }, "organizationUpdated": { "message": "Apvienība atjaunināta" @@ -4927,7 +4927,7 @@ "message": "Uziacināt jaunu nodrošinātāja lietotāju, zemāk esošajā laukā ievadot tā Bitwarden konta e-pasta adresi. Ja tam vēl nav Bitwarden konta, tiks vaicāts izveidot jaunu." }, "joinProvider": { - "message": "Pievienot nodrošinātāju" + "message": "Pievienoties nodrošinātāju" }, "joinProviderDesc": { "message": "Tu esi uzaicināts pievienoties augstāk norādītajam nodrošinātājam. Lai to pieņemtu, jāpiesakās vai jāizveido jauns Bitwarden konts." @@ -4945,7 +4945,7 @@ "message": "Nodrošinātājs" }, "newClientOrganization": { - "message": "Jauna sniedzēja apvienība" + "message": "Jauna pasūtītāja apvienība" }, "newClientOrganizationDesc": { "message": "Izveidot jaunu pasūtītāja apvienību, kas būs piesaistīta šim kontam kā nodrošinātājam. Tas sniegs iespēju piekļūt šai apvienībai un to pārvaldīt." @@ -5661,7 +5661,7 @@ "message": "Rēķinu vēsture" }, "backToReports": { - "message": "Atgriezties pie atskaitēm" + "message": "Atgriezties pie pārskatiem" }, "organizationPicker": { "message": "Apvienību atlasītājs" @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Nodrošinātāju portāls" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "Skatīt krājumu" }, @@ -7757,7 +7760,7 @@ "description": "Message to encourage the user to start creating machine accounts." }, "machineAccountsNoItemsTitle": { - "message": "Nothing to show yet", + "message": "Vēl nav nekā, ko parādīt", "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Jāņem vērā: nepiešķirti apvienības vienumi vairs nav redzami skatā \"Visas glabātavas\" dažādās ierīcēs un ir sasniedzami tikai no pārvaldības konsoles, kur šie vienumi jāpiešķir krājumam, lai padarītu tos redzamus." + "restrictedGroupAccessDesc": { + "message": "Sevi nevar pievienot kopai." }, "unassignedItemsBannerSelfHost": { - "message": "Jāņem vērā: 2024. gada 2. maijā nepiešķirti apvienības vienumi vairs nebūs redzami skatā \"Visas glabātavas\" dažādās ierīcēs un būs sasniedzami tikai no pārvaldības konsoles, kur šie vienumi jāpiešķir krājumam, lai padarītu tos redzamus." + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Jāņem vērā: nepiešķirti apvienības vienumi vairs nav redzami skatā \"Visas glabātavas\" dažādās ierīcēs un tagad ir pieejami tikai pārvaldības konsolē." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Jāņem vērā: no 2024. gada 16. maija nepiešķirti apvienības vienumi vairs nebūs redzami skatā \"Visas glabātavas\" dažādās ierīcēs un būs pieejami tikai pārvaldības konsolē." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Piešķirt šos vienumus krājumam", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": ", lai padarītu tos redzamus.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Izdzēst nodrošinātāju" + }, + "deleteProviderConfirmation": { + "message": "Nodrošinātāja izdzēšana ir paliekoša un neatgriezeniska. Jāievada sava galvenā parole, lai apliecinātu nodrošinātāja un visu saistīto datu izdzēšanu." + }, + "deleteProviderName": { + "message": "Nevar izdzēst $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Ir jāatsaista visi klienti, pirms var izdzēst $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Nodrošinātājs izdzēsts" + }, + "providerDeletedDesc": { + "message": "Nodrošinātājs un visi ar to saistītie dati tika izdzēsti." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Tika pieprasīts izdzēst šo nodrošinātāju. Jāizmanto zemāk esošā poga, lai apstiprinātu." + }, + "deleteProviderWarning": { + "message": "Nodrošinātāja izdzēšana ir paliekoša. To nevar atsaukt." + }, + "errorAssigningTargetCollection": { + "message": "Kļūda mērķa krājuma piešķiršanā." + }, + "errorAssigningTargetFolder": { + "message": "Kļūda mērķa mapes piešķiršanā." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 4f81532796..9b6d1ab565 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 781c9000cb..ed04c3a3ef 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 781c9000cb..ed04c3a3ef 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 0719b129b3..4e5fb13d15 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index e0ca2b4f0a..f411847571 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 3ab66e763b..7bf9b4018f 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Providerportaal" }, + "success": { + "message": "Succes" + }, "viewCollection": { "message": "Collectie weergeven" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Toegang tot machine-account bijgewerkt" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "Je kunt jezelf niet aan een groep toevoegen." }, "unassignedItemsBannerSelfHost": { "message": "Kennisgeving: Vanaf 2 mei 2024 zijn niet-toegewezen organisatie-items op geen enkel apparaat meer zichtbaar in de weergave van alle kluisjes en alleen toegankelijk via de Admin Console. Je kunt deze items in het Admin Console aan een collectie toewijzen om ze zichtbaar te maken." + }, + "unassignedItemsBannerNotice": { + "message": "Let op: Niet-toegewezen organisatie-items zijn niet langer zichtbaar in de weergave van alle kluissen op verschillende apparaten en zijn nu alleen toegankelijk via de Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Let op: Vanaf 16 mei 2024 zijn niet-toegewezen organisatie-items niet langer zichtbaar in de weergave van alle kluissen op verschillende apparaten en alleen toegankelijk via de Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Deze items toewijzen aan een collectie van de", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "om ze zichtbaar te maken.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Provider verwijderen" + }, + "deleteProviderConfirmation": { + "message": "Het verwijderen van een provider is definitief en onomkeerbaar. Voer je hoofdwachtwoord in om het verwijderen van de provider en alle bijbehorende gegevens te bevestigen." + }, + "deleteProviderName": { + "message": "Kan $ID$ niet verwijderen", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Je moet alle clients ontkoppelen voordat je $ID$ kunt verwijderen", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider verwijderd" + }, + "providerDeletedDesc": { + "message": "De organisatie en alle bijhorende gegevens zijn verwijderd." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Je hebt het verwijderen van deze provider aangevraagd. Gebruik deonderstaande knop om te bevestigen." + }, + "deleteProviderWarning": { + "message": "Het verwijderen van een provider is definitief. Je kunt het niet ongedaan maken." + }, + "errorAssigningTargetCollection": { + "message": "Fout bij toewijzen doelverzameling." + }, + "errorAssigningTargetFolder": { + "message": "Fout bij toewijzen doelmap." + }, + "integrationsAndSdks": { + "message": "Integraties & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integraties" + }, + "integrationsDesc": { + "message": "Automatisch secrets synchroniseren van Bitwarden Secrets Manager met een dienst van derden." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Gebruik Bitwarden Secrets Manager SDK in de volgende programmeertalen om je eigen applicaties te bouwen." + }, + "setUpGithubActions": { + "message": "GitHub Actions instellen" + }, + "setUpGitlabCICD": { + "message": "GitLab CI/CD instellen" + }, + "setUpAnsible": { + "message": "Ansibel instellen" + }, + "cSharpSDKRepo": { + "message": "C#-repository bekijken" + }, + "cPlusPlusSDKRepo": { + "message": "C++-repository bekijken" + }, + "jsWebAssemblySDKRepo": { + "message": "JS WebAssembly-repository bekijken" + }, + "javaSDKRepo": { + "message": "Java-repository bekijken" + }, + "pythonSDKRepo": { + "message": "Python-repository bekijken" + }, + "phpSDKRepo": { + "message": "Php-repository bekijken" + }, + "rubySDKRepo": { + "message": "Ruby-repository bekijken" + }, + "goSDKRepo": { + "message": "Go-repository bekijken" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "Geen toegang" + }, + "collectionAdminConsoleManaged": { + "message": "Deze collectie is alleen toegankelijk vanaf de admin console" } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index a80816b511..3d54ea70fd 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 781c9000cb..ed04c3a3ef 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 9532e8d25c..57e0eb1929 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -296,7 +296,7 @@ "message": "Szukaj w organizacji" }, "searchMembers": { - "message": "Szukaj w użytkownikach" + "message": "Szukaj członków" }, "searchGroups": { "message": "Szukaj w grupach" @@ -854,7 +854,7 @@ "message": "Brak użytkowników do wyświetlenia." }, "noMembersInList": { - "message": "Brak użytkowników do wyświetlenia." + "message": "Brak członków do wyświetlenia." }, "noEventsInList": { "message": "Brak wydarzeń do wyświetlenia." @@ -1534,7 +1534,7 @@ "message": "Włącz dwustopniowe logowanie dla swojej organizacji." }, "twoStepLoginEnterpriseDescStart": { - "message": "Wymuszaj opcje logowania dwustopniowego Bitwarden dla użytkowników za pomocą ", + "message": "Wymuszaj opcje logowania dwustopniowego Bitwarden dla członków za pomocą ", "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": { @@ -2085,7 +2085,7 @@ "message": "Konto Premium" }, "premiumAccessDesc": { - "message": "Możesz przyznać dostęp premium wszystkim użytkownikom w Twojej organizacji za $PRICE$ /$INTERVAL$.", + "message": "Możesz przyznać dostęp premium wszystkim członkom Twojej organizacji za $PRICE$ /$INTERVAL$.", "placeholders": { "price": { "content": "$1", @@ -2541,7 +2541,7 @@ } }, "trialSecretsManagerThankYou": { - "message": "Thanks for signing up for Bitwarden Secrets Manager for $PLAN$!", + "message": "Dziękujemy za zarejestrowanie się do Menedżera Sekretów Bitwarden w planie $PLAN$!", "placeholders": { "plan": { "content": "$1", @@ -2691,7 +2691,7 @@ "message": "Czy na pewno chcesz usunąć tę kolekcję?" }, "editMember": { - "message": "Edytuj użytkownika" + "message": "Edytuj członka" }, "fieldOnTabRequiresAttention": { "message": "Pole na karcie '$TAB$' wymaga uwagi.", @@ -2751,7 +2751,7 @@ "message": "Administrator" }, "adminDesc": { - "message": "Administratorzy mają dostęp do wszystkich elementów, kolekcji i użytkowników w Twojej organizacji." + "message": "Administratorzy mają dostęp do wszystkich elementów, kolekcji i członków Twojej organizacji" }, "user": { "message": "Użytkownik" @@ -3234,7 +3234,7 @@ "message": "Dostęp grupowy" }, "groupAccessUserDesc": { - "message": "Zmień grupy, do których należy użytkownik." + "message": "Przyznaj członkowi dostęp do kolekcji, dodając go do 1 lub więcej grup." }, "invitedUsers": { "message": "Użytkownicy zostali zaproszeni" @@ -3270,10 +3270,10 @@ } }, "confirmUsers": { - "message": "Zatwierdź użytkowników" + "message": "Zatwierdź członków" }, "usersNeedConfirmed": { - "message": "Posiadasz użytkowników, którzy zaakceptowali zaproszenie, ale muszą jeszcze zostać potwierdzeni. Użytkownicy nie będą posiadali dostępu do organizacji, dopóki nie zostaną potwierdzeni." + "message": "Posiadasz członków, którzy zaakceptowali zaproszenie, ale muszą jeszcze zostać potwierdzeni. Członkowie nie będą posiadali dostępu do organizacji, dopóki nie zostaną potwierdzeni." }, "startDate": { "message": "Data rozpoczęcia" @@ -3486,10 +3486,10 @@ "message": "Wpisz identyfikator instalacji" }, "limitSubscriptionDesc": { - "message": "Ustaw limit liczby stanowisk subskrypcji. Po osiągnięciu tego limitu nie będziesz mógł zapraszać nowych użytkowników." + "message": "Ustaw limit liczby subskrypcji. Po osiągnięciu tego limitu nie będziesz mógł zapraszać nowych członków." }, "limitSmSubscriptionDesc": { - "message": "Ustaw limit liczby subskrypcji menedżera sekretów. Po osiągnięciu tego limitu nie będziesz mógł zapraszać nowych użytkowników." + "message": "Ustaw limit liczby subskrypcji menedżera sekretów. Po osiągnięciu tego limitu nie będziesz mógł zapraszać nowych członków." }, "maxSeatLimit": { "message": "Maksymalna liczba stanowisk (opcjonalnie)", @@ -3510,7 +3510,7 @@ "message": "Zmiany w subskrypcji spowodują proporcjonalne zmiany w rozliczeniach. Jeśli nowo zaproszeni użytkownicy przekroczą liczbę stanowisk w subskrypcji, otrzymasz proporcjonalną opłatę za dodatkowych użytkowników." }, "subscriptionUserSeats": { - "message": "Twoja subskrypcja pozwala na łączną liczbę $COUNT$ użytkowników.", + "message": "Twoja subskrypcja pozwala na łączną liczbę $COUNT$ członków.", "placeholders": { "count": { "content": "$1", @@ -3537,10 +3537,10 @@ "message": "Aby uzyskać dodatkową pomoc w zarządzaniu subskrypcją, skontaktuj się z działem obsługi klienta." }, "subscriptionUserSeatsUnlimitedAutoscale": { - "message": "Zmiany w subskrypcji spowodują proporcjonalne zmiany w rozliczeniach. Jeśli nowo zaproszeni użytkownicy przekroczą liczbę stanowisk w subskrypcji, otrzymasz proporcjonalną opłatę za dodatkowych użytkowników." + "message": "Zmiany w subskrypcji spowodują proporcjonalne zmiany w rozliczeniach. Jeśli nowo zaproszeni członkowie przekroczą liczbę miejsc w subskrypcji, otrzymasz proporcjonalną opłatę za dodatkowych członków." }, "subscriptionUserSeatsLimitedAutoscale": { - "message": "Korekty subskrypcji spowodują proporcjonalne zmiany w sumach rozliczeniowych. Jeśli nowo zaproszeni użytkownicy przekroczą liczbę miejsc objętych subskrypcją, natychmiast otrzymasz proporcjonalną opłatę za dodatkowych użytkowników, aż do osiągnięcia limitu miejsc $MAX$.", + "message": "Korekty subskrypcji spowodują proporcjonalne zmiany w sumach rozliczeniowych. Jeśli nowo zaproszeni członkowie przekroczą liczbę miejsc objętych subskrypcją, natychmiast otrzymasz proporcjonalną opłatę za dodatkowych członków, aż do osiągnięcia limitu miejsc $MAX$.", "placeholders": { "max": { "content": "$1", @@ -4083,7 +4083,7 @@ "message": "Identyfikator SSO" }, "ssoIdentifierHintPartOne": { - "message": "Podaj ten identyfikator swoim użytkownikom, aby zalogować się za pomocą SSO. Aby pominąć ten krok, ustaw ", + "message": "Podaj ten identyfikator swoim członkowm, aby zalogować się za pomocą SSO. Aby pominąć ten krok, ustaw ", "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": { @@ -5210,7 +5210,7 @@ "message": "Zweryfikuj certyfikaty" }, "spUniqueEntityId": { - "message": "Set a unique SP entity ID" + "message": "Ustaw unikalny identyfikator jednostki SP" }, "spUniqueEntityIdDesc": { "message": "Wygeneruj identyfikator unikalny dla Twojej organizacji" @@ -5426,13 +5426,13 @@ "message": "Opcje odszyfrowania użytkownika" }, "memberDecryptionPassDesc": { - "message": "Po uwierzytelnieniu użytkownicy odszyfrują dane sejfu za pomocą hasła głównego." + "message": "Po uwierzytelnieniu członkowie odszyfrują dane sejfu za pomocą hasła głównego." }, "keyConnector": { "message": "Key Connector" }, "memberDecryptionKeyConnectorDescStart": { - "message": "Połącz logowanie za pomocą SSO z Twoim serwerem kluczy odszyfrowania. Używając tej opcji, użytkownicy nie będą musieli używać swoich haseł głównych, aby odszyfrować dane sejfu.", + "message": "Połącz logowanie za pomocą SSO z Twoim serwerem kluczy odszyfrowania. Używając tej opcji, członkowie nie będą musieli używać swoich haseł głównych, aby odszyfrować dane sejfu.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescLink": { @@ -6455,7 +6455,7 @@ "message": "Informacje o grupie" }, "editGroupMembersDesc": { - "message": "Przyznaj użytkownikom dostęp do kolekcji przypisanych do grupy." + "message": "Przyznaj członkom dostęp do kolekcji przypisanych do grupy." }, "editGroupCollectionsDesc": { "message": "Przyznaj dostęp do kolekcji, dodając je do tej grupy." @@ -6467,7 +6467,7 @@ "message": "Jeśli zaznaczone, zastąpi to wszystkie inne uprawnienia kolekcji." }, "selectMembers": { - "message": "Wybierz użytkowników" + "message": "Wybierz członków" }, "selectCollections": { "message": "Wybierz kolekcje" @@ -6476,7 +6476,7 @@ "message": "Rola" }, "removeMember": { - "message": "Usuń użytkownika" + "message": "Usuń członka" }, "collection": { "message": "Kolekcja" @@ -6500,7 +6500,7 @@ "message": "Nie dodano kolekcji" }, "noMembersAdded": { - "message": "Nie dodano użytkowników" + "message": "Nie dodano członków" }, "noGroupsAdded": { "message": "Nie dodano grup" @@ -6680,13 +6680,13 @@ "message": "Wysłać kod ponownie" }, "memberColumnHeader": { - "message": "Member" + "message": "Członek" }, "groupSlashMemberColumnHeader": { - "message": "Group/Member" + "message": "Grupa/Członek" }, "selectGroupsAndMembers": { - "message": "Wybierz grupy i użytkowników" + "message": "Wybierz grupy i członków" }, "selectGroups": { "message": "Wybierz grupy" @@ -6695,22 +6695,22 @@ "message": "Uprawnienia ustawione dla użytkownika zastąpią uprawnienia ustawione przez grupę tego użytkownika" }, "noMembersOrGroupsAdded": { - "message": "Nie dodano użytkowników lub grup" + "message": "Nie dodano członków lub grup" }, "deleted": { "message": "Usunięto" }, "memberStatusFilter": { - "message": "Filtr statusu użytkownika" + "message": "Filtr statusu członka" }, "inviteMember": { - "message": "Zaproś użytkownika" + "message": "Zaproś członka" }, "needsConfirmation": { "message": "Wymaga potwierdzenia" }, "memberRole": { - "message": "Rola użytkownika" + "message": "Rola członka" }, "moreFromBitwarden": { "message": "Więcej od Bitwarden" @@ -7105,7 +7105,7 @@ "message": "Zaufane urządzenia" }, "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Po uwierzytelnieniu użytkownicy odszyfrowają dane sejfu przy użyciu klucza zapisanego na ich urządzeniu.", + "message": "Po uwierzytelnieniu członkowie odszyfrowają dane sejfu przy użyciu klucza zapisanego na ich urządzeniu.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkOne": { @@ -7229,7 +7229,7 @@ "message": "Brak hasła głównego" }, "removeMembersWithoutMasterPasswordWarning": { - "message": "Usuwanie użytkowników, którzy nie mają hasła głównego bez ustawienia go, może ograniczyć dostęp do ich pełnych kont." + "message": "Usuwanie członków, którzy nie mają hasła głównego bez ustawienia go, może ograniczyć dostęp do ich pełnych kont." }, "approvedAuthRequest": { "message": "Zatwierdzone urządzenie dla $ID$.", @@ -7262,7 +7262,7 @@ } }, "startYour7DayFreeTrialOfBitwardenSecretsManagerFor": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for $ORG$", + "message": "Rozpocznij 7-dniowy darmowy okres próbny Menedżera Sekretów Bitwarden dla $ORG$", "placeholders": { "org": { "content": "$1", @@ -7508,7 +7508,7 @@ "message": "Przyznaj grupom lub członkom dostęp do tej kolekcji." }, "grantCollectionAccessMembersOnly": { - "message": "Grant members access to this collection." + "message": "Przyznaj członkom dostęp do tej kolekcji." }, "adminCollectionAccess": { "message": "Administratorzy mogą mieć dostęp do kolekcji i zarządzać nimi." @@ -7548,7 +7548,7 @@ "message": "Szczegóły potwierdzenia" }, "smFreeTrialThankYou": { - "message": "Thank you for signing up for Bitwarden Secrets Manager!" + "message": "Dziękujemy za zarejestrowanie się do Menedżera Sekretów Bitwarden!" }, "smFreeTrialConfirmationEmail": { "message": "Wysłaliśmy e-mail weryfikacyjny na adres " @@ -7557,7 +7557,7 @@ "message": "Ta akcja jest nieodwracalna" }, "confirmCollectionEnhancementsDialogContent": { - "message": "Turning on this feature will deprecate the manager role and replace it with a Can manage permission. This will take a few moments. Do not make any organization changes until it is complete. Are you sure you want to proceed?" + "message": "Włączenie tej funkcji spowoduje deprekację roli menedżera i zastąpi ją uprawnieniami do zarządzania uprawnieniami. To zajmie kilka chwil. Nie wprowadzaj żadnych zmian w organizacji dopóki nie zostanie zakończone. Czy na pewno chcesz kontynuować?" }, "sorryToSeeYouGo": { "message": "Przykro nam, że nas opuszczasz! Pomóż ulepszyć Bitwarden udostępniając informacje dlaczego anulujesz.", @@ -7598,7 +7598,7 @@ "message": "Witaj w nowej i ulepszonej aplikacji internetowej. Dowiedz się więcej o tym, co się zmieniło." }, "releaseBlog": { - "message": "Read release blog" + "message": "Przeczytaj blog z informacjami o wydaniu" }, "adminConsole": { "message": "Konsola administratora" @@ -7606,14 +7606,17 @@ "providerPortal": { "message": "Portal dostawcy" }, + "success": { + "message": "Sukces" + }, "viewCollection": { "message": "Zobacz kolekcję" }, "restrictedGroupAccess": { - "message": "You cannot add yourself to groups." + "message": "Nie możesz dodać siebie do grup." }, "restrictedCollectionAccess": { - "message": "You cannot add yourself to collections." + "message": "Nie możesz dodać siebie do kolekcji." }, "assign": { "message": "Przypisz" @@ -7738,42 +7741,42 @@ "description": "The date header used when a subscription is cancelled." }, "machineAccountsCannotCreate": { - "message": "Machine accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "Konta dla maszyn nie mogą być tworzone w zawieszonych organizacjach. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." }, "machineAccount": { - "message": "Machine account", + "message": "Konto dla maszyny", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "machineAccounts": { - "message": "Machine accounts", + "message": "Konta dla maszyn", "description": "The title for the section that deals with machine accounts." }, "newMachineAccount": { - "message": "New machine account", + "message": "Nowe konto dla maszyny", "description": "Title for creating a new machine account." }, "machineAccountsNoItemsMessage": { - "message": "Create a new machine account to get started automating secret access.", + "message": "Utwórz nowe konto dla maszyny, aby rozpocząć automatyzację dostępu do sekretu.", "description": "Message to encourage the user to start creating machine accounts." }, "machineAccountsNoItemsTitle": { - "message": "Nothing to show yet", + "message": "Nie ma nic do pokazania", "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { - "message": "Delete machine accounts", + "message": "Usuń konta maszyn", "description": "Title for the action to delete one or multiple machine accounts." }, "deleteMachineAccount": { - "message": "Delete machine account", + "message": "Usuń konto maszyny", "description": "Title for the action to delete a single machine account." }, "viewMachineAccount": { - "message": "View machine account", + "message": "Zobacz konto maszyny", "description": "Action to view the details of a machine account." }, "deleteMachineAccountDialogMessage": { - "message": "Deleting machine account $MACHINE_ACCOUNT$ is permanent and irreversible.", + "message": "Usuwanie konta maszyny $MACHINE_ACCOUNT$ jest trwałe i nieodwracalne.", "placeholders": { "machine_account": { "content": "$1", @@ -7782,10 +7785,10 @@ } }, "deleteMachineAccountsDialogMessage": { - "message": "Deleting machine accounts is permanent and irreversible." + "message": "Usuwanie kont maszyn jest trwałe i nieodwracalne." }, "deleteMachineAccountsConfirmMessage": { - "message": "Delete $COUNT$ machine accounts", + "message": "Usuń $COUNT$ kont maszyn", "placeholders": { "count": { "content": "$1", @@ -7794,60 +7797,60 @@ } }, "deleteMachineAccountToast": { - "message": "Machine account deleted" + "message": "Konto maszyny usunięte" }, "deleteMachineAccountsToast": { - "message": "Machine accounts deleted" + "message": "Konta maszyn usunięte" }, "searchMachineAccounts": { - "message": "Search machine accounts", + "message": "Szukaj kont maszyn", "description": "Placeholder text for searching machine accounts." }, "editMachineAccount": { - "message": "Edit machine account", + "message": "Edytuj konto maszyny", "description": "Title for editing a machine account." }, "machineAccountName": { - "message": "Machine account name", + "message": "Nazwa konta maszyny", "description": "Label for the name of a machine account" }, "machineAccountCreated": { - "message": "Machine account created", + "message": "Konto maszyny utworzone", "description": "Notifies that a new machine account has been created" }, "machineAccountUpdated": { - "message": "Machine account updated", + "message": "Konto maszyny zaktualizowane", "description": "Notifies that a machine account has been updated" }, "projectMachineAccountsDescription": { - "message": "Grant machine accounts access to this project." + "message": "Przyznaj dostęp kontom maszynowym do tego projektu." }, "projectMachineAccountsSelectHint": { - "message": "Type or select machine accounts" + "message": "Wpisz lub wybierz konta maszyn" }, "projectEmptyMachineAccountAccessPolicies": { - "message": "Add machine accounts to grant access" + "message": "Dodaj konta maszyn, aby udzielić dostępu" }, "machineAccountPeopleDescription": { - "message": "Grant groups or people access to this machine account." + "message": "Przyznaj grupom lub ludziom dostęp do tego konta maszyny." }, "machineAccountProjectsDescription": { - "message": "Assign projects to this machine account. " + "message": "Przypisz projekty do tego konta maszyny. " }, "createMachineAccount": { - "message": "Create a machine account" + "message": "Utwórz konto dla maszyny" }, "maPeopleWarningMessage": { - "message": "Removing people from a machine account does not remove the access tokens they created. For security best practice, it is recommended to revoke access tokens created by people removed from a machine account." + "message": "Usuwanie osób z konta maszyny nie usuwa tokenów dostępu. Dla zapewnienia najlepszej praktyki bezpieczeństwa zaleca się cofnięcie tokenów dostępu stworzonych przez osoby usunięte z konta maszyny." }, "smAccessRemovalWarningMaTitle": { - "message": "Remove access to this machine account" + "message": "Usuń dostęp do tego konta maszyny" }, "smAccessRemovalWarningMaMessage": { - "message": "This action will remove your access to the machine account." + "message": "Ta akcja usunie Twój dostęp do konta maszyny." }, "machineAccountsIncluded": { - "message": "$COUNT$ machine accounts included", + "message": "Uwzględnione konta maszyn: $COUNT$", "placeholders": { "count": { "content": "$1", @@ -7856,7 +7859,7 @@ } }, "additionalMachineAccountCost": { - "message": "$COST$ per month for additional machine accounts", + "message": "$COST$ miesięcznie dla dodatkowych kont dla maszyn", "placeholders": { "cost": { "content": "$1", @@ -7865,10 +7868,10 @@ } }, "additionalMachineAccounts": { - "message": "Additional machine accounts" + "message": "Dodatkowe konta dla maszyn" }, "includedMachineAccounts": { - "message": "Your plan comes with $COUNT$ machine accounts.", + "message": "Twój plan obejmuje $COUNT$ kont dla maszyn.", "placeholders": { "count": { "content": "$1", @@ -7877,7 +7880,7 @@ } }, "addAdditionalMachineAccounts": { - "message": "You can add additional machine accounts for $COST$ per month.", + "message": "Możesz dodać dodatkowe konta dla maszyn za $COST$ miesięcznie.", "placeholders": { "cost": { "content": "$1", @@ -7886,24 +7889,156 @@ } }, "limitMachineAccounts": { - "message": "Limit machine accounts (optional)" + "message": "Limit kont dla maszyn (opcjonalnie)" }, "limitMachineAccountsDesc": { - "message": "Set a limit for your machine accounts. Once this limit is reached, you will not be able to create new machine accounts." + "message": "Ustaw limit liczby kont dla maszyn. Po osiągnięciu tego limitu nie będziesz mógł tworzyć nowych kont." }, "machineAccountLimit": { - "message": "Machine account limit (optional)" + "message": "Limit konta dla maszyn (opcjonalnie)" }, "maxMachineAccountCost": { - "message": "Max potential machine account cost" + "message": "Maksymalny potencjalny koszt kont dla maszyn" }, "machineAccountAccessUpdated": { - "message": "Machine account access updated" + "message": "Zaktualizowano dostęp do konta dla maszyny" }, - "unassignedItemsBanner": { - "message": "Uwaga: Nieprzypisane elementy w organizacji nie są już widoczne w widoku Wszystkie sejfy na urządzeniach i są teraz dostępne tylko przez Konsolę Administracyjną. Przypisz te elementy do kolekcji z konsoli administracyjnej, aby były one widoczne." + "restrictedGroupAccessDesc": { + "message": "Nie możesz dodać siebie do grupy." }, "unassignedItemsBannerSelfHost": { "message": "Uwaga: 2 maja 2024 r. nieprzypisane elementy w organizacji nie będą już widoczne w widoku Wszystkie sejfy na urządzeniach i będą dostępne tylko przez Konsolę Administracyjną. Przypisz te elementy do kolekcji z konsoli administracyjnej, aby były one widoczne." + }, + "unassignedItemsBannerNotice": { + "message": "Uwaga: Nieprzypisane elementy organizacji nie są już widoczne w widoku Wszystkie sejfy na urządzeniach i są teraz dostępne tylko przez Konsolę Administracyjną." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Uwaga: 16 maja 2024 r. nieprzypisana elementy w organizacji nie będą już widoczne w widoku Wszystkie sejfy na urządzeniach i będą dostępne tylko przez Konsolę Administracyjną." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Przypisz te elementy do kolekcji z", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": ", aby były widoczne.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Usuń dostawcę" + }, + "deleteProviderConfirmation": { + "message": "Usunięcie dostawcy jest trwałe i nieodwracalne. Wprowadź swoje hasło główne, aby potwierdzić usunięcie dostawcy i wszystkich powiązanych danych." + }, + "deleteProviderName": { + "message": "Nie można usunąć $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Musisz odłączyć wszystkich klientów zanim będziesz mógł usunąć $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Dostawca został usunięty" + }, + "providerDeletedDesc": { + "message": "Dostawca i wszystkie powiązane dane zostały usunięte." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Poprosiłeś o usunięcie tego dostawcy. Kliknij poniższy przycisk, aby to potwierdzić." + }, + "deleteProviderWarning": { + "message": "Usunięcie dostawcy jest nieodwracalne. Ta czynność nie może zostać cofnięta." + }, + "errorAssigningTargetCollection": { + "message": "Wystąpił błąd podczas przypisywania kolekcji." + }, + "errorAssigningTargetFolder": { + "message": "Wystąpił błąd podczas przypisywania folderu." + }, + "integrationsAndSdks": { + "message": "Integracje i SDK", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integracje" + }, + "integrationsDesc": { + "message": "Automatycznie synchronizuj sekrety z Menedżera Sekretnych Bitwarden do usługi innej firmy." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Użyj SDK Menedżera Sekretów Bitwarden w następujących językach programowania, aby zbudować własne aplikacje." + }, + "setUpGithubActions": { + "message": "Skonfiguruj Github Actions" + }, + "setUpGitlabCICD": { + "message": "Skonfiguruj GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Skonfiguruj Ansible" + }, + "cSharpSDKRepo": { + "message": "Zobacz repozytorium C#" + }, + "cPlusPlusSDKRepo": { + "message": "Zobacz repozytorium C++" + }, + "jsWebAssemblySDKRepo": { + "message": "Zobacz repozytorium JS WebAssembly" + }, + "javaSDKRepo": { + "message": "Zobacz repozytorium Java" + }, + "pythonSDKRepo": { + "message": "Zobacz repozytorium Pythona" + }, + "phpSDKRepo": { + "message": "Zobacz repozytorium php" + }, + "rubySDKRepo": { + "message": "Zobacz repozytorium Ruby" + }, + "goSDKRepo": { + "message": "Zobacz repozytorium Go" + }, + "createNewClientToManageAsProvider": { + "message": "Utwórz nową organizację klienta do zarządzania jako dostawca. Dodatkowe miejsca zostaną odzwierciedlone w następnym cyklu rozliczeniowym." + }, + "selectAPlan": { + "message": "Wybierz plan" + }, + "thirtyFivePercentDiscount": { + "message": "Zniżka 35%" + }, + "monthPerMember": { + "message": "miesięcznie za członka" + }, + "seats": { + "message": "Miejsca" + }, + "addOrganization": { + "message": "Dodaj organizację" + }, + "createdNewClient": { + "message": "Pomyślnie utworzono nowego klienta" + }, + "noAccess": { + "message": "Brak dostępu" + }, + "collectionAdminConsoleManaged": { + "message": "Ta kolekcja jest dostępna tylko z konsoli administracyjnej" } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 084d4c95d9..4f43c2aad7 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -579,7 +579,7 @@ "message": "Acesso" }, "accessLevel": { - "message": "Access level" + "message": "Nível de acesso" }, "loggedOut": { "message": "Sessão encerrada" @@ -672,7 +672,7 @@ "message": "Criptografia não suportada" }, "enablePasskeyEncryption": { - "message": "Set up encryption" + "message": "Configurar criptografia" }, "usedForEncryption": { "message": "Usado para criptografia" @@ -4951,13 +4951,13 @@ "message": "Crie uma nova organização de cliente que será associada a você como o provedor. Você poderá acessar e gerenciar esta organização." }, "newClient": { - "message": "New client" + "message": "Novo cliente" }, "addExistingOrganization": { "message": "Adicionar Organização Existente" }, "addNewOrganization": { - "message": "Add new organization" + "message": "Adicionar nova organização" }, "myProvider": { "message": "Meu Provedor" @@ -5940,13 +5940,13 @@ } }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "Inicie o Duo e siga os passos para finalizar o login." }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "A autenticação em duas etapas do Duo é necessária para sua conta." }, "launchDuo": { - "message": "Launch Duo" + "message": "Abrir o Duo" }, "turnOn": { "message": "Ligar" @@ -6680,10 +6680,10 @@ "message": "Reenviar código" }, "memberColumnHeader": { - "message": "Member" + "message": "Membro" }, "groupSlashMemberColumnHeader": { - "message": "Group/Member" + "message": "Grupo/Membro" }, "selectGroupsAndMembers": { "message": "Selecione grupos e membros" @@ -7450,7 +7450,7 @@ "message": "Já tem uma conta?" }, "skipToContent": { - "message": "Skip to content" + "message": "Pular para o conteúdo" }, "managePermissionRequired": { "message": "Pelo menos um membro ou grupo deve ter poder gerenciar a permissão." @@ -7502,13 +7502,13 @@ "message": "O acesso à coleção está restrito" }, "readOnlyCollectionAccess": { - "message": "You do not have access to manage this collection." + "message": "Você não tem acesso para gerenciar esta coleção." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, "grantCollectionAccessMembersOnly": { - "message": "Grant members access to this collection." + "message": "Conceder acesso a essa coleção." }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." @@ -7557,57 +7557,60 @@ "message": "Esta ação é irreversível" }, "confirmCollectionEnhancementsDialogContent": { - "message": "Turning on this feature will deprecate the manager role and replace it with a Can manage permission. This will take a few moments. Do not make any organization changes until it is complete. Are you sure you want to proceed?" + "message": "Ativar este recurso irá depreciar a função de gerente e substituí-lo por uma Permissão de para Gerenciar. Isso levará algum tempo. Não faça nenhuma alteração de organização até que esteja concluída. Tem certeza que deseja continuar?" }, "sorryToSeeYouGo": { - "message": "Sorry to see you go! Help improve Bitwarden by sharing why you're canceling.", + "message": "Lamentamos vê-lo ir! Ajude a melhorar o Bitwarden compartilhando o motivo de você estar cancelando.", "description": "A message shown to users as part of an offboarding survey asking them to provide more information on their subscription cancelation." }, "selectCancellationReason": { - "message": "Select a reason for canceling", + "message": "Selecione o motivo do cancelamento", "description": "Used as a form field label for a select input on the offboarding survey." }, "anyOtherFeedback": { - "message": "Is there any other feedback you'd like to share?", + "message": "Existe algum outro comentário que você gostaria de compartilhar?", "description": "Used as a form field label for a textarea input on the offboarding survey." }, "missingFeatures": { - "message": "Missing features", + "message": "Recursos ausentes", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "movingToAnotherTool": { - "message": "Moving to another tool", + "message": "Trocando de ferramenta", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooDifficultToUse": { - "message": "Too difficult to use", + "message": "Muito difícil de usar", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "notUsingEnough": { - "message": "Not using enough", + "message": "Não está usando o suficiente", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooExpensive": { - "message": "Too expensive", + "message": "Muito caro", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { "message": "Grátis por 1 ano" }, "newWebApp": { - "message": "Welcome to the new and improved web app. Learn more about what’s changed." + "message": "Bem-vindo ao novo e melhorado aplicativo da web. Saiba mais sobre o que mudou." }, "releaseBlog": { - "message": "Read release blog" + "message": "Ler o blog do lançamento" }, "adminConsole": { - "message": "Admin Console" + "message": "Painel de administração" }, "providerPortal": { - "message": "Provider Portal" + "message": "Portal do provedor" + }, + "success": { + "message": "Success" }, "viewCollection": { - "message": "View collection" + "message": "Ver Coleção" }, "restrictedGroupAccess": { "message": "Você não pode se adicionar aos grupos." @@ -7616,28 +7619,28 @@ "message": "Você não pode se adicionar às coleções." }, "assign": { - "message": "Assign" + "message": "Atribuir" }, "assignToCollections": { - "message": "Assign to collections" + "message": "Atribuir à coleções" }, "assignToTheseCollections": { - "message": "Assign to these collections" + "message": "Atribuir a estas coleções" }, "bulkCollectionAssignmentDialogDescription": { - "message": "Select the collections that the items will be shared with. Once an item is updated in one collection, it will be reflected in all collections. Only organization members with access to these collections will be able to see the items." + "message": "Selecione as coleções com as quais os itens serão compartilhados. Assim que um item for atualizado em uma coleção, isso será refletido em todas as coleções. Apenas membros da organização com acesso a essas coleções poderão ver os itens." }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "Selecione as coleções para atribuir" }, "noCollectionsAssigned": { - "message": "No collections have been assigned" + "message": "Nenhuma coleção foi atribuída" }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "Coleções atribuídas com sucesso" }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "Você selecionou $TOTAL_COUNT$ itens. Você não pode atualizar $READONLY_COUNT$ destes itens porque você não tem permissão para edição.", "placeholders": { "total_count": { "content": "$1", @@ -7650,55 +7653,55 @@ } }, "items": { - "message": "Items" + "message": "Itens" }, "assignedSeats": { - "message": "Assigned seats" + "message": "Lugares Atribuídos" }, "assigned": { - "message": "Assigned" + "message": "Atribuído" }, "used": { - "message": "Used" + "message": "Utilizado" }, "remaining": { - "message": "Remaining" + "message": "Restante" }, "unlinkOrganization": { - "message": "Unlink organization" + "message": "Desvincular organização" }, "manageSeats": { - "message": "MANAGE SEATS" + "message": "GERENCIAR LUGARES" }, "manageSeatsDescription": { - "message": "Adjustments to seats will be reflected in the next billing cycle." + "message": "Os ajustes nos lugares serão refletidos no próximo ciclo de faturamento." }, "unassignedSeatsDescription": { - "message": "Unassigned subscription seats" + "message": "Assinatura de assentos desvinculada" }, "purchaseSeatDescription": { - "message": "Additional seats purchased" + "message": "Assentos adicionais comprados" }, "assignedSeatCannotUpdate": { - "message": "Assigned Seats can not be updated. Please contact your organization owner for assistance." + "message": "Os assentos atribuídos não podem ser atualizados. Por favor, entre em contato com o proprietário da sua organização para obter assistência." }, "subscriptionUpdateFailed": { - "message": "Subscription update failed" + "message": "Atualização da assinatura falhou" }, "trial": { - "message": "Trial", + "message": "Avaliação", "description": "A subscription status label." }, "pastDue": { - "message": "Past due", + "message": "Atrasado", "description": "A subscription status label" }, "subscriptionExpired": { - "message": "Subscription expired", + "message": "Assinatura expirada", "description": "The date header used when a subscription is past due." }, "pastDueWarningForChargeAutomatically": { - "message": "You have a grace period of $DAYS$ days from your subscription expiration date to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.", + "message": "Você tem um período de carência de $DAYS$ dias a contar da data de expiração para manter sua assinatura. Por favor, resolva as faturas vencidas até $SUSPENSION_DATE$.", "placeholders": { "days": { "content": "$1", @@ -7712,7 +7715,7 @@ "description": "A warning shown to the user when their subscription is past due and they are charged automatically." }, "pastDueWarningForSendInvoice": { - "message": "You have a grace period of $DAYS$ days from the date your first unpaid invoice is due to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.", + "message": "Você tem um período de licença de $DAYS$, contados a partir da data de vencimento da sua primeira fatura em aberto para manter a sua assinatura. Por favor, resolva as faturas vencidas até $SUSPENSION_DATE$.", "placeholders": { "days": { "content": "$1", @@ -7726,54 +7729,54 @@ "description": "A warning shown to the user when their subscription is past due and they pay via invoice." }, "unpaidInvoice": { - "message": "Unpaid invoice", + "message": "Fatura não paga", "description": "The header of a warning box shown to a user whose subscription is unpaid." }, "toReactivateYourSubscription": { - "message": "To reactivate your subscription, please resolve the past due invoices.", + "message": "Para reativar sua assinatura, por favor resolva as faturas vencidas.", "description": "The body of a warning box shown to a user whose subscription is unpaid." }, "cancellationDate": { - "message": "Cancellation date", + "message": "Data de cancelamento", "description": "The date header used when a subscription is cancelled." }, "machineAccountsCannotCreate": { - "message": "Machine accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "Contas de máquina não podem ser criadas em organizações suspensas. Entre em contato com o proprietário da organização para obter assistência." }, "machineAccount": { - "message": "Machine account", + "message": "Conta de máquina", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "machineAccounts": { - "message": "Machine accounts", + "message": "Contas de máquina", "description": "The title for the section that deals with machine accounts." }, "newMachineAccount": { - "message": "New machine account", + "message": "Nova conta de máquina", "description": "Title for creating a new machine account." }, "machineAccountsNoItemsMessage": { - "message": "Create a new machine account to get started automating secret access.", + "message": "Crie uma nova conta de máquina para começar a automatizar o acesso secreto.", "description": "Message to encourage the user to start creating machine accounts." }, "machineAccountsNoItemsTitle": { - "message": "Nothing to show yet", + "message": "Ainda não há nada a ser exibido", "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { - "message": "Delete machine accounts", + "message": "Excluir contas de máquina", "description": "Title for the action to delete one or multiple machine accounts." }, "deleteMachineAccount": { - "message": "Delete machine account", + "message": "Excluir conta de máquina", "description": "Title for the action to delete a single machine account." }, "viewMachineAccount": { - "message": "View machine account", + "message": "Ver conta de máquina", "description": "Action to view the details of a machine account." }, "deleteMachineAccountDialogMessage": { - "message": "Deleting machine account $MACHINE_ACCOUNT$ is permanent and irreversible.", + "message": "A exclusão da conta de máquina $MACHINE_ACCOUNT$ é permanente e irreversível.", "placeholders": { "machine_account": { "content": "$1", @@ -7782,10 +7785,10 @@ } }, "deleteMachineAccountsDialogMessage": { - "message": "Deleting machine accounts is permanent and irreversible." + "message": "Excluir contas de máquina é permanente e irreversível." }, "deleteMachineAccountsConfirmMessage": { - "message": "Delete $COUNT$ machine accounts", + "message": "Excluir $COUNT$ contas de máquina", "placeholders": { "count": { "content": "$1", @@ -7794,60 +7797,60 @@ } }, "deleteMachineAccountToast": { - "message": "Machine account deleted" + "message": "Conta de máquina excluída" }, "deleteMachineAccountsToast": { - "message": "Machine accounts deleted" + "message": "Contas de máquina excluídas" }, "searchMachineAccounts": { - "message": "Search machine accounts", + "message": "Pesquisar contas de máquina", "description": "Placeholder text for searching machine accounts." }, "editMachineAccount": { - "message": "Edit machine account", + "message": "Editar conta da máquina", "description": "Title for editing a machine account." }, "machineAccountName": { - "message": "Machine account name", + "message": "Nome da conta de máquina", "description": "Label for the name of a machine account" }, "machineAccountCreated": { - "message": "Machine account created", + "message": "Conta de máquina criada", "description": "Notifies that a new machine account has been created" }, "machineAccountUpdated": { - "message": "Machine account updated", + "message": "Conta de máquina atualizada", "description": "Notifies that a machine account has been updated" }, "projectMachineAccountsDescription": { - "message": "Grant machine accounts access to this project." + "message": "Conceder acesso a contas de máquina a este projeto." }, "projectMachineAccountsSelectHint": { - "message": "Type or select machine accounts" + "message": "Digite ou selecione contas de máquina" }, "projectEmptyMachineAccountAccessPolicies": { - "message": "Add machine accounts to grant access" + "message": "Adicione contas de máquina para conceder acesso" }, "machineAccountPeopleDescription": { - "message": "Grant groups or people access to this machine account." + "message": "Conceder acesso de grupos ou pessoas a esta conta de máquina." }, "machineAccountProjectsDescription": { - "message": "Assign projects to this machine account. " + "message": "Atribuir projetos a esta conta de máquina. " }, "createMachineAccount": { - "message": "Create a machine account" + "message": "Criar uma conta de máquina" }, "maPeopleWarningMessage": { - "message": "Removing people from a machine account does not remove the access tokens they created. For security best practice, it is recommended to revoke access tokens created by people removed from a machine account." + "message": "Remover pessoas de uma conta de máquina não remove os tokens de acesso que elas criaram. Por melhores práticas de segurança, é recomendado revogar os tokens de acesso criados por pessoas que foram removidas de uma conta de máquina." }, "smAccessRemovalWarningMaTitle": { - "message": "Remove access to this machine account" + "message": "Remover acesso a esta conta de máquina" }, "smAccessRemovalWarningMaMessage": { - "message": "This action will remove your access to the machine account." + "message": "Esta ação irá remover seu acesso à conta de máquina." }, "machineAccountsIncluded": { - "message": "$COUNT$ machine accounts included", + "message": "$COUNT$ contas de serviço incluídas", "placeholders": { "count": { "content": "$1", @@ -7856,7 +7859,7 @@ } }, "additionalMachineAccountCost": { - "message": "$COST$ per month for additional machine accounts", + "message": "$COST$ mensal para contas de máquina adicionais", "placeholders": { "cost": { "content": "$1", @@ -7865,10 +7868,10 @@ } }, "additionalMachineAccounts": { - "message": "Additional machine accounts" + "message": "Contas de máquina adicionais" }, "includedMachineAccounts": { - "message": "Your plan comes with $COUNT$ machine accounts.", + "message": "Seu plano vem com $COUNT$ contas de máquina.", "placeholders": { "count": { "content": "$1", @@ -7877,7 +7880,7 @@ } }, "addAdditionalMachineAccounts": { - "message": "You can add additional machine accounts for $COST$ per month.", + "message": "Você pode adicionar contas de máquina extras por $COST$ mensais.", "placeholders": { "cost": { "content": "$1", @@ -7886,24 +7889,156 @@ } }, "limitMachineAccounts": { - "message": "Limit machine accounts (optional)" + "message": "Limitar contas de máquina (opcional)" }, "limitMachineAccountsDesc": { - "message": "Set a limit for your machine accounts. Once this limit is reached, you will not be able to create new machine accounts." + "message": "Defina um limite para suas contas de máquina. Quando este limite for atingido, você não poderá criar novas contas de máquina." }, "machineAccountLimit": { - "message": "Machine account limit (optional)" + "message": "Limite de conta de máquina (opcional)" }, "maxMachineAccountCost": { - "message": "Max potential machine account cost" + "message": "Custo máximo de conta de máquina" }, "machineAccountAccessUpdated": { - "message": "Machine account access updated" + "message": "Acesso a conta de máquina atualizado" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "Você não pode adicionar você mesmo a um grupo." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Aviso: Itens de organização não atribuídos não estão mais visíveis na sua tela Todos os Cofres através dos dispositivos e agora só são acessíveis por meio do Console de Administração." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Aviso: Em 16 de maio, 2024, itens da organização que não foram atribuídos não estarão mais visíveis em sua visualização de Todos os Cofres dos dispositivos e só serão acessíveis por meio do painel de administração." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Atribua estes itens a uma coleção da", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "para torná-los visíveis.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Excluir Provedor" + }, + "deleteProviderConfirmation": { + "message": "A exclusão de um provedor é permanente e irreversível. Digite sua senha mestra para confirmar a exclusão do provedor e de todos os dados associados." + }, + "deleteProviderName": { + "message": "Não é possível excluir $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Você deve desvincular todos os clientes antes de excluir $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provedor excluído" + }, + "providerDeletedDesc": { + "message": "O provedor e todos os dados associados foram excluídos." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Você pediu para excluir este Provedor. Clique no botão abaixo para confirmar." + }, + "deleteProviderWarning": { + "message": "A exclusão do seu provedor é permanente. Não pode ser desfeita." + }, + "errorAssigningTargetCollection": { + "message": "Erro ao atribuir coleção de destino." + }, + "errorAssigningTargetFolder": { + "message": "Erro ao atribuir pasta de destino." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Crie uma nova organização de cliente para gerenciar como um Provedor. Posições adicionais serão refletidas no próximo ciclo de faturamento." + }, + "selectAPlan": { + "message": "Selecione um plano" + }, + "thirtyFivePercentDiscount": { + "message": "35% de Desconto" + }, + "monthPerMember": { + "message": "mês por membro" + }, + "seats": { + "message": "Lugares" + }, + "addOrganization": { + "message": "Adicionar Organização" + }, + "createdNewClient": { + "message": "Novo cliente criado com sucesso" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index cf1ad10d9b..c2f2fbeee3 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Portal do fornecedor" }, + "success": { + "message": "Com sucesso" + }, "viewCollection": { "message": "Ver coleção" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Acesso à conta automática atualizado" }, - "unassignedItemsBanner": { - "message": "Aviso: Os itens da organização não atribuídos já não são visíveis na sua vista Todos os cofres em todos os dispositivos e agora só estão acessíveis através da Consola de administração. Atribua estes itens a uma coleção a partir da Consola de administração para os tornar visíveis." + "restrictedGroupAccessDesc": { + "message": "Não se pode adicionar a si próprio a um grupo." }, "unassignedItemsBannerSelfHost": { "message": "Aviso: A 2 de maio de 2024, os itens da organização não atribuídos deixarão de estar visíveis na vista Todos os cofres em todos os dispositivos e só estarão acessíveis através da Consola de administração. Atribua estes itens a uma coleção a partir da Consola de administração para os tornar visíveis." + }, + "unassignedItemsBannerNotice": { + "message": "Aviso: Os itens da organização não atribuídos já não são visíveis na vista Todos os cofres em todos os dispositivos e agora só estão acessíveis através da Consola de administração." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Aviso: A 16 de maio de 2024, os itens da organização não atribuídos deixarão de estar visíveis na vista Todos os cofres em todos os dispositivos e só estarão acessíveis através da Consola de administração." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Atribua estes itens a uma coleção a partir da", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "para os tornar visíveis.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Eliminar fornecedor" + }, + "deleteProviderConfirmation": { + "message": "A eliminação de um fornecedor é permanente e irreversível. Introduza a sua palavra-passe mestra para confirmar a eliminação do fornecedor e de todos os dados associados." + }, + "deleteProviderName": { + "message": "Não é possível eliminar $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "É necessário desvincular todos os clientes antes de poder eliminar $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Fornecedor eliminado" + }, + "providerDeletedDesc": { + "message": "O fornecedor e todos os dados associados foram eliminados." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Pediu para eliminar este fornecedor. Clique no botão abaixo para confirmar." + }, + "deleteProviderWarning": { + "message": "A eliminação do seu fornecedor é permanente. Não pode ser anulada." + }, + "errorAssigningTargetCollection": { + "message": "Erro ao atribuir a coleção de destino." + }, + "errorAssigningTargetFolder": { + "message": "Erro ao atribuir a pasta de destino." + }, + "integrationsAndSdks": { + "message": "Integrações e SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrações" + }, + "integrationsDesc": { + "message": "Sincronize automaticamente segredos do Gestor de Segredos do Bitwarden para um serviço de terceiros." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Utilize o SDK do Gestor de Segredos do Bitwarden nas seguintes linguagens de programação para criar as suas próprias aplicações." + }, + "setUpGithubActions": { + "message": "Configurar ações do Github" + }, + "setUpGitlabCICD": { + "message": "Configurar o GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Configurar o Ansible" + }, + "cSharpSDKRepo": { + "message": "Ver repositório de C#" + }, + "cPlusPlusSDKRepo": { + "message": "Ver repositório de C++" + }, + "jsWebAssemblySDKRepo": { + "message": "Ver repositório de JS WebAssembly" + }, + "javaSDKRepo": { + "message": "Ver repositório de Java" + }, + "pythonSDKRepo": { + "message": "Ver repositório de Python" + }, + "phpSDKRepo": { + "message": "Ver repositório de php" + }, + "rubySDKRepo": { + "message": "Ver repositório de Ruby" + }, + "goSDKRepo": { + "message": "Ver repositório de Go" + }, + "createNewClientToManageAsProvider": { + "message": "Crie uma nova organização de clientes para gerir como Fornecedor. Os lugares adicionais serão refletidos na próxima faturação." + }, + "selectAPlan": { + "message": "Selecionar um plano" + }, + "thirtyFivePercentDiscount": { + "message": "Desconto de 35%" + }, + "monthPerMember": { + "message": "mês por membro" + }, + "seats": { + "message": "Lugares" + }, + "addOrganization": { + "message": "Adicionar organização" + }, + "createdNewClient": { + "message": "Novo cliente criado com sucesso" + }, + "noAccess": { + "message": "Sem acesso" + }, + "collectionAdminConsoleManaged": { + "message": "Esta coleção só é acessível a partir da Consola de administração" } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index ba5badd908..1a47866ebb 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index eeb89acf55..85ba7d5785 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Портал провайдера" }, + "success": { + "message": "Успешно" + }, "viewCollection": { "message": "Посмотреть коллекцию" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Доступ аккаунта компьютера обновлен" }, - "unassignedItemsBanner": { - "message": "Обратите внимание: неприсвоенные элементы организации больше не видны в представлении \"Все хранилища\" на всех устройствах и теперь доступны только через консоль администратора. Назначьте эти элементы коллекции в консоли администратора, чтобы сделать их видимыми." + "restrictedGroupAccessDesc": { + "message": "Нельзя добавить самого себя в группу." }, "unassignedItemsBannerSelfHost": { "message": "Уведомление: 2 мая 2024 года неприсвоенные элементы организации больше не будут отображаться в представлении \"Все хранилища\" на всех устройствах и будут доступны только через консоль администратора. Назначьте эти элементы коллекции в консоли администратора, чтобы сделать их видимыми." + }, + "unassignedItemsBannerNotice": { + "message": "Уведомление: Неприсвоенные элементы организации больше не отображаются в представлении \"Все хранилища\" на всех устройствах и теперь доступны только через консоль администратора." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Уведомление: с 16 мая 2024 года неназначенные элементы организации больше не будут отображаться в представлении \"Все хранилища\" на всех устройствах и будут доступны только через консоль администратора." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Назначьте эти элементы в коллекцию из", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "чтобы сделать их видимыми.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Удалить провайдера" + }, + "deleteProviderConfirmation": { + "message": "Удаление провайдера является постоянным и необратимым. Введите свой мастер-пароль, чтобы подтвердить удаление провайдера и всех связанных с ним данных." + }, + "deleteProviderName": { + "message": "Невозможно удалить $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Перед удалением $ID$ необходимо отвязать всех клиентов", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Провайдер удален" + }, + "providerDeletedDesc": { + "message": "Провайдер и все связанные с ним данные были удалены." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Вы запросили удаление этого провайдера. Воспользуйтесь кнопкой ниже для подтверждения." + }, + "deleteProviderWarning": { + "message": "Удаление вашего провайдера необратимо. Это нельзя отменить." + }, + "errorAssigningTargetCollection": { + "message": "Ошибка при назначении целевой коллекции." + }, + "errorAssigningTargetFolder": { + "message": "Ошибка при назначении целевой папки." + }, + "integrationsAndSdks": { + "message": "Интеграции и SDK", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Интеграции" + }, + "integrationsDesc": { + "message": "Автоматическая синхронизация секретов из Bitwarden Secrets Manager со сторонним сервисом." + }, + "sdks": { + "message": "SDK" + }, + "sdksDesc": { + "message": "Используйте SDK Bitwarden Secrets Manager на следующих языках программирования для создания собственных приложений." + }, + "setUpGithubActions": { + "message": "Настроить Github Actions" + }, + "setUpGitlabCICD": { + "message": "Настроить GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Настроить Ansible" + }, + "cSharpSDKRepo": { + "message": "Просмотр репозитория C#" + }, + "cPlusPlusSDKRepo": { + "message": "Просмотр репозитория C++" + }, + "jsWebAssemblySDKRepo": { + "message": "Просмотр репозитория JS WebAssembly" + }, + "javaSDKRepo": { + "message": "Просмотр репозитория Java" + }, + "pythonSDKRepo": { + "message": "Просмотр репозитория Python" + }, + "phpSDKRepo": { + "message": "Просмотр репозитория php" + }, + "rubySDKRepo": { + "message": "Просмотр репозитория Ruby" + }, + "goSDKRepo": { + "message": "Просмотр репозитория Go" + }, + "createNewClientToManageAsProvider": { + "message": "Создайте новую клиентскую организацию для управления ею в качестве провайдера. Дополнительные места будут отражены в следующем биллинговом цикле." + }, + "selectAPlan": { + "message": "Выберите план" + }, + "thirtyFivePercentDiscount": { + "message": "Скидка 35%" + }, + "monthPerMember": { + "message": "в месяц за пользователя" + }, + "seats": { + "message": "Места" + }, + "addOrganization": { + "message": "Добавить организацию" + }, + "createdNewClient": { + "message": "Новый клиент успешно создан" + }, + "noAccess": { + "message": "Нет доступа" + }, + "collectionAdminConsoleManaged": { + "message": "Эта коллекция доступна только из консоли администратора" } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 0faa92c08d..e2c1e42427 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 67f5abdc7d..234a4670b9 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Portál poskytovateľa" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "Pozrieť zbierku" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Upozornenie: Nepriradené položky organizácie už nie sú viditeľné v zobrazení Všetky trezory a sú prístupné iba cez Správcovskú konzolu." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Upozornenie: 16. mája 2024 nepriradené položky organizácie už nebudú viditeľné v zobrazení Všetky trezory a budú prístupné iba cez Správcovskú konzolu." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Priradiť tieto položky do zbierky zo", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": ", aby boli viditeľné.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Chyba pri priraďovaní cieľovej kolekcie." + }, + "errorAssigningTargetFolder": { + "message": "Chyba pri priraďovaní cieľového priečinka." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 6a55868df2..c209ef05bd 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 9683c587b7..a59b6a8855 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Портал провајдера" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "Преглед колекције" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Приступ налога машине ажуриран" }, - "unassignedItemsBanner": { - "message": "Напомена: Недодељене ставке организације више нису видљиве у вашем приказу Сви сефови на свим уређајима и сада су доступне само преко Админ конзоле. Доделите ове ставке колекцији са Админ конзолом да бисте их учинили видљивим." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { - "message": "Обавештење: 2. маја 2024. недодељене ставке организације више неће бити видљиве у вашем приказу Сви сефови на свим уређајима и биће им доступне само преко Админ конзоле. Доделите ове ставке колекцији са Админ конзолом да бисте их учинили видљивим." + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 15770437ce..1ff89501f3 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 57321d3376..389f1682a8 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "Visa samling" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "Du kan inte lägga till dig själv i en grupp." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Radera leverantör" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 781c9000cb..ed04c3a3ef 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 1ecd45a9e4..ec114aad90 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 65ef93eef7..9177321cb5 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Sağlayıcı Portalı" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 1baaa5c67b..ecb31b6da0 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -7601,11 +7601,14 @@ "message": "Читати блог випусків" }, "adminConsole": { - "message": "Консоль адміністратора" + "message": "консолі адміністратора," }, "providerPortal": { "message": "Портал провайдера" }, + "success": { + "message": "Успішно" + }, "viewCollection": { "message": "Переглянути збірку" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Доступ до машинного облікового запису оновлено" }, - "unassignedItemsBanner": { - "message": "Увага: непризначені елементи організації більше не видимі у поданні \"Усі сховища\" на різних пристроях і тепер доступні лише в консолі адміністратора. Щоб зробити їх видимими, призначте ці елементи збірці в консолі адміністратора." + "restrictedGroupAccessDesc": { + "message": "Ви не можете додати себе до групи." }, "unassignedItemsBannerSelfHost": { "message": "Сповіщення: 2 травня 2024 року, непризначені елементи організації більше не будуть видимі на ваших пристроях у поданні \"Усі сховища\", і будуть доступні лише через консоль адміністратора. Щоб зробити їх видимими, призначте ці елементи збірці в консолі адміністратора." + }, + "unassignedItemsBannerNotice": { + "message": "Примітка: непризначені елементи організації більше не видимі на ваших пристроях у поданні \"Усі сховища\", і тепер доступні лише через консоль адміністратора." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Примітка: 16 травня 2024 року непризначені елементи організації більше не будуть видимі на ваших пристроях у поданні \"Усі сховища\", і будуть доступні лише через консоль адміністратора." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Призначте ці елементи збірці в", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "щоб зробити їх видимими.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Видалити провайдера" + }, + "deleteProviderConfirmation": { + "message": "Видалення провайдера є остаточною і незворотною дією. Введіть свій головний пароль, щоб підтвердити видалення провайдера і всі пов'язані з ним дані." + }, + "deleteProviderName": { + "message": "Неможливо видалити $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "Перш ніж видалити $ID$, ви повинні від'єднати всіх клієнтів", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Провайдера видалено" + }, + "providerDeletedDesc": { + "message": "Провайдера і всі пов'язані дані було видалено." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "Ви відправили запит на видалення цього провайдера. Натисніть кнопку внизу для підтвердження." + }, + "deleteProviderWarning": { + "message": "Видалення провайдера є незворотною дією. Це не можна буде скасувати." + }, + "errorAssigningTargetCollection": { + "message": "Помилка призначення цільової збірки." + }, + "errorAssigningTargetFolder": { + "message": "Помилка призначення цільової теки." + }, + "integrationsAndSdks": { + "message": "Інтеграції та SDK", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Інтеграції" + }, + "integrationsDesc": { + "message": "Автоматична синхронізація секретів між менеджером секретів Bitwarden і стороннім сервісом." + }, + "sdks": { + "message": "SDK" + }, + "sdksDesc": { + "message": "Використовуйте SDK менеджера секретів Bitwarden із зазначеними мовами програмування для створення власних програм." + }, + "setUpGithubActions": { + "message": "Налаштувати дії для Github" + }, + "setUpGitlabCICD": { + "message": "Налаштувати GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Налаштувати Ansible" + }, + "cSharpSDKRepo": { + "message": "Перегляд репозиторію C#" + }, + "cPlusPlusSDKRepo": { + "message": "Перегляд репозиторію C++" + }, + "jsWebAssemblySDKRepo": { + "message": "Перегляд репозиторію JS WebAssembly" + }, + "javaSDKRepo": { + "message": "Перегляд репозиторію Java" + }, + "pythonSDKRepo": { + "message": "Перегляд репозиторію Python" + }, + "phpSDKRepo": { + "message": "Перегляд репозиторію php" + }, + "rubySDKRepo": { + "message": "Перегляд репозиторію Ruby" + }, + "goSDKRepo": { + "message": "Перегляд репозиторію Go" + }, + "createNewClientToManageAsProvider": { + "message": "Створіть нову організацію клієнта, щоб керувати нею як провайдер. Додаткові місця будуть відображені в наступному платіжному циклі." + }, + "selectAPlan": { + "message": "Оберіть тарифний план" + }, + "thirtyFivePercentDiscount": { + "message": "Знижка 35%" + }, + "monthPerMember": { + "message": "на місяць за учасника" + }, + "seats": { + "message": "Місця" + }, + "addOrganization": { + "message": "Додати організацію" + }, + "createdNewClient": { + "message": "Нового клієнта успішно створено" + }, + "noAccess": { + "message": "Немає доступу" + }, + "collectionAdminConsoleManaged": { + "message": "Ця збірка доступна тільки з консолі адміністратора" } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index ffbd72ce9d..0696e226e0 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index e584785a5a..f6093d9bc7 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "提供商门户" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "查看集合" }, @@ -7698,7 +7701,7 @@ "description": "The date header used when a subscription is past due." }, "pastDueWarningForChargeAutomatically": { - "message": "从您的订阅到期之日起,您有 $DAYS$ 天的宽限期来保留您的订购。请在 $SUSPENSION_DATE$ 之前处理逾期未支付的账单。", + "message": "从您的订阅到期之日起,您有 $DAYS$ 天的宽限期来保留您的订阅。请在 $SUSPENSION_DATE$ 之前处理逾期未支付的账单。", "placeholders": { "days": { "content": "$1", @@ -7712,7 +7715,7 @@ "description": "A warning shown to the user when their subscription is past due and they are charged automatically." }, "pastDueWarningForSendInvoice": { - "message": "从第一笔未支付的账单到期之日起,您有 $DAYS$ 天的宽限期来保留您的订购。请在 $SUSPENSION_DATE$ 之前处理逾期未支付的账单。", + "message": "从第一笔未支付的账单到期之日起,您有 $DAYS$ 天的宽限期来保留您的订阅。请在 $SUSPENSION_DATE$ 之前处理逾期未支付的账单。", "placeholders": { "days": { "content": "$1", @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "机器账户访问权限已更新" }, - "unassignedItemsBanner": { - "message": "注意:未分配的组织项目在您所有设备的「所有密码库」视图中不再可见,现在只能通过管理控制台访问。通过管理控制台将这些项目分配给集合以使其可见。" + "restrictedGroupAccessDesc": { + "message": "您不能将自己添加到群组。" }, "unassignedItemsBannerSelfHost": { - "message": "注意:从 2024 年 5 月 2 日起,未分配的组织项目在您所有设备的「所有密码库」视图中将不再可见,只能通过管理控制台访问。通过管理控制台将这些项目分配给集合以使其可见。" + "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "删除提供商" + }, + "deleteProviderConfirmation": { + "message": "删除提供商是永久性操作,无法撤销!输入您的主密码以确认删除提供商及所有关联的数据。" + }, + "deleteProviderName": { + "message": "无法删除 $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "提供商已删除" + }, + "providerDeletedDesc": { + "message": "提供商和所有关联数据已被删除。" + }, + "deleteProviderRecoverConfirmDesc": { + "message": "您已请求删除此提供商。请使用下面的按钮确认。" + }, + "deleteProviderWarning": { + "message": "删除您的提供商是永久性操作,无法撤销!" + }, + "errorAssigningTargetCollection": { + "message": "分配目标集合时出错。" + }, + "errorAssigningTargetFolder": { + "message": "分配目标文件夹时出错。" + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 89bb26cacd..1bc56d2157 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -7606,6 +7606,9 @@ "providerPortal": { "message": "Provider Portal" }, + "success": { + "message": "Success" + }, "viewCollection": { "message": "View collection" }, @@ -7900,10 +7903,142 @@ "machineAccountAccessUpdated": { "message": "Machine account access updated" }, - "unassignedItemsBanner": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "restrictedGroupAccessDesc": { + "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerNotice": { + "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + }, + "unassignedItemsBannerSelfHostNotice": { + "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + }, + "unassignedItemsBannerCTAPartOne": { + "message": "Assign these items to a collection from the", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "unassignedItemsBannerCTAPartTwo": { + "message": "to make them visible.", + "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." + }, + "deleteProvider": { + "message": "Delete provider" + }, + "deleteProviderConfirmation": { + "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + }, + "deleteProviderName": { + "message": "Cannot delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "deleteProviderWarningDesc": { + "message": "You must unlink all clients before you can delete $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "providerDeleted": { + "message": "Provider deleted" + }, + "providerDeletedDesc": { + "message": "The Provider and all associated data has been deleted." + }, + "deleteProviderRecoverConfirmDesc": { + "message": "You have requested to delete this Provider. Use the button below to confirm." + }, + "deleteProviderWarning": { + "message": "Deleting your provider is permanent. It cannot be undone." + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." + }, + "integrationsAndSdks": { + "message": "Integrations & SDKs", + "description": "The title for the section that deals with integrations and SDKs." + }, + "integrations": { + "message": "Integrations" + }, + "integrationsDesc": { + "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + }, + "sdks": { + "message": "SDKs" + }, + "sdksDesc": { + "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + }, + "setUpGithubActions": { + "message": "Set up Github Actions" + }, + "setUpGitlabCICD": { + "message": "Set up GitLab CI/CD" + }, + "setUpAnsible": { + "message": "Set up Ansible" + }, + "cSharpSDKRepo": { + "message": "View C# repository" + }, + "cPlusPlusSDKRepo": { + "message": "View C++ repository" + }, + "jsWebAssemblySDKRepo": { + "message": "View JS WebAssembly repository" + }, + "javaSDKRepo": { + "message": "View Java repository" + }, + "pythonSDKRepo": { + "message": "View Python repository" + }, + "phpSDKRepo": { + "message": "View php repository" + }, + "rubySDKRepo": { + "message": "View Ruby repository" + }, + "goSDKRepo": { + "message": "View Go repository" + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" + }, + "noAccess": { + "message": "No access" + }, + "collectionAdminConsoleManaged": { + "message": "This collection is only accessible from the admin console" } } From 2916fc7404c18a6ca3914b1546c63d0b6859c8b4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:42:33 +0000 Subject: [PATCH 37/86] Autosync the updated translations (#8825) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 22 ++++++- apps/desktop/src/locales/ar/messages.json | 22 ++++++- apps/desktop/src/locales/az/messages.json | 66 +++++++++++++------- apps/desktop/src/locales/be/messages.json | 22 ++++++- apps/desktop/src/locales/bg/messages.json | 22 ++++++- apps/desktop/src/locales/bn/messages.json | 22 ++++++- apps/desktop/src/locales/bs/messages.json | 22 ++++++- apps/desktop/src/locales/ca/messages.json | 22 ++++++- apps/desktop/src/locales/cs/messages.json | 22 ++++++- apps/desktop/src/locales/cy/messages.json | 22 ++++++- apps/desktop/src/locales/da/messages.json | 22 ++++++- apps/desktop/src/locales/de/messages.json | 22 ++++++- apps/desktop/src/locales/el/messages.json | 22 ++++++- apps/desktop/src/locales/en_GB/messages.json | 22 ++++++- apps/desktop/src/locales/en_IN/messages.json | 22 ++++++- apps/desktop/src/locales/eo/messages.json | 22 ++++++- apps/desktop/src/locales/es/messages.json | 22 ++++++- apps/desktop/src/locales/et/messages.json | 22 ++++++- apps/desktop/src/locales/eu/messages.json | 22 ++++++- apps/desktop/src/locales/fa/messages.json | 22 ++++++- apps/desktop/src/locales/fi/messages.json | 22 ++++++- apps/desktop/src/locales/fil/messages.json | 22 ++++++- apps/desktop/src/locales/fr/messages.json | 22 ++++++- apps/desktop/src/locales/gl/messages.json | 22 ++++++- apps/desktop/src/locales/he/messages.json | 22 ++++++- apps/desktop/src/locales/hi/messages.json | 22 ++++++- apps/desktop/src/locales/hr/messages.json | 22 ++++++- apps/desktop/src/locales/hu/messages.json | 22 ++++++- apps/desktop/src/locales/id/messages.json | 22 ++++++- apps/desktop/src/locales/it/messages.json | 22 ++++++- apps/desktop/src/locales/ja/messages.json | 22 ++++++- apps/desktop/src/locales/ka/messages.json | 22 ++++++- apps/desktop/src/locales/km/messages.json | 22 ++++++- apps/desktop/src/locales/kn/messages.json | 22 ++++++- apps/desktop/src/locales/ko/messages.json | 22 ++++++- apps/desktop/src/locales/lt/messages.json | 22 ++++++- apps/desktop/src/locales/lv/messages.json | 22 ++++++- apps/desktop/src/locales/me/messages.json | 22 ++++++- apps/desktop/src/locales/ml/messages.json | 22 ++++++- apps/desktop/src/locales/mr/messages.json | 22 ++++++- apps/desktop/src/locales/my/messages.json | 22 ++++++- apps/desktop/src/locales/nb/messages.json | 22 ++++++- apps/desktop/src/locales/ne/messages.json | 22 ++++++- apps/desktop/src/locales/nl/messages.json | 22 ++++++- apps/desktop/src/locales/nn/messages.json | 22 ++++++- apps/desktop/src/locales/or/messages.json | 22 ++++++- apps/desktop/src/locales/pl/messages.json | 22 ++++++- apps/desktop/src/locales/pt_BR/messages.json | 22 ++++++- apps/desktop/src/locales/pt_PT/messages.json | 22 ++++++- apps/desktop/src/locales/ro/messages.json | 22 ++++++- apps/desktop/src/locales/ru/messages.json | 22 ++++++- apps/desktop/src/locales/si/messages.json | 22 ++++++- apps/desktop/src/locales/sk/messages.json | 24 ++++++- apps/desktop/src/locales/sl/messages.json | 22 ++++++- apps/desktop/src/locales/sr/messages.json | 22 ++++++- apps/desktop/src/locales/sv/messages.json | 22 ++++++- apps/desktop/src/locales/te/messages.json | 22 ++++++- apps/desktop/src/locales/th/messages.json | 22 ++++++- apps/desktop/src/locales/tr/messages.json | 22 ++++++- apps/desktop/src/locales/uk/messages.json | 22 ++++++- apps/desktop/src/locales/vi/messages.json | 22 ++++++- apps/desktop/src/locales/zh_CN/messages.json | 22 ++++++- apps/desktop/src/locales/zh_TW/messages.json | 22 ++++++- 63 files changed, 1283 insertions(+), 149 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index ee32c045c9..97067b788a 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Verander Hoofwagwoord" }, - "changeMasterPasswordConfirmation": { - "message": "U kan u hoofwagwoord op die bitwarden.com-webkluis verander. Wil u die webwerf nou besoek?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Vingerafdrukfrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Blaaierintegrasie word nie ondersteun nie" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Ongelukkig word blaaierintegrasie tans slegs in die weergawe vir die Mac-toepwinkel ondersteun." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 104a9f7780..2d25269fff 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "تغيير كلمة المرور الرئيسية" }, - "changeMasterPasswordConfirmation": { - "message": "يمكنك تغيير كلمة المرور الرئيسية من خزنة الويب في bitwarden.com. هل تريد زيارة الموقع الآن؟" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "عبارة بصمة الإصبع", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "تكامل المتصفح غير مدعوم" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "للأسف، لا يتم دعم تكامل المتصفح إلا في إصدار متجر تطبيقات ماك في الوقت الحالي." }, @@ -2688,6 +2697,9 @@ "message": "تنسيقات مشتركة", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index f404c7f95a..1ecd18eee7 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -717,7 +717,7 @@ "message": "Bildiriş server URL-si" }, "iconsUrl": { - "message": "Nişan server URL-si" + "message": "İkon server URL-si" }, "environmentSaved": { "message": "Mühit URL-ləri saxlanıldı." @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Ana parolu dəyişdir" }, - "changeMasterPasswordConfirmation": { - "message": "Ana parolunuzu bitwarden.com veb anbarında dəyişdirə bilərsiniz. İndi saytı ziyarət etmək istəyirsiniz?" + "continueToWebApp": { + "message": "Veb tətbiqlə davam edilsin?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Ana parolunuzu Bitwarden veb tətbiqində dəyişdirə bilərsiniz." }, "fingerprintPhrase": { "message": "Barmaq izi ifadəsi", @@ -920,52 +923,52 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "enableFavicon": { - "message": "Veb sayt nişanlarını göstər" + "message": "Veb sayt ikonlarını göstər" }, "faviconDesc": { "message": "Hər girişin yanında tanına bilən bir təsvir göstər." }, "enableMinToTray": { - "message": "Bildiriş nişanına kiçildin" + "message": "Bildiriş sahəsi ikonuna kiçilt" }, "enableMinToTrayDesc": { - "message": "Pəncərə kiçildiləndə, bildiriş sahəsində bir nişan göstər." + "message": "Pəncərə kiçildiləndə, bunun əvəzinə bildiriş sahəsində bir ikon göstər." }, "enableMinToMenuBar": { - "message": "Menyu sətrinə kiçilt" + "message": "Menyu çubuğuna kiçilt" }, "enableMinToMenuBarDesc": { - "message": "Pəncərəni kiçildəndə, menyu sətrində bir nişan göstər." + "message": "Pəncərəni kiçildəndə, bunun əvəzinə menyu çubuğunda bir ikon göstər." }, "enableCloseToTray": { - "message": "Bildiriş nişanına bağla" + "message": "Bildiriş ikonu üçün bağla" }, "enableCloseToTrayDesc": { - "message": "Pəncərə bağlananda, bildiriş sahəsində bir nişan göstər." + "message": "Pəncərə bağladılanda, bunun əvəzinə bildiriş sahəsində bir ikon göstər." }, "enableCloseToMenuBar": { - "message": "Menyu sətrini bağla" + "message": "Menyu çubuğunda bağla" }, "enableCloseToMenuBarDesc": { - "message": "Pəncərəni bağlananda, menyu sətrində bir nişan göstər." + "message": "Pəncərəni bağladılanda, bunun əvəzinə menyu çubuğunda bir ikon göstər." }, "enableTray": { - "message": "Bildiriş sahəsi nişanını fəallaşdır" + "message": "Bildiriş sahəsi ikonunu göstər" }, "enableTrayDesc": { - "message": "Bildiriş sahəsində həmişə bir nişan göstər." + "message": "Bildiriş sahəsində həmişə bir ikon göstər." }, "startToTray": { - "message": "Bildiriş sahəsi nişanı kimi başlat" + "message": "Bildiriş sahəsi ikonu kimi başlat" }, "startToTrayDesc": { - "message": "Tətbiq ilk başladılanda, yalnız bildiriş sahəsi nişanı görünsün." + "message": "Tətbiq ilk başladılanda, sistem bildiriş sahəsində yalnız ikon olaraq görünsün." }, "startToMenuBar": { - "message": "Menyu sətrini başlat" + "message": "Menyu çubuğunda başlat" }, "startToMenuBarDesc": { - "message": "Tətbiq ilk başladılanda, menyu sətri sadəcə nişan kimi görünsün." + "message": "Tətbiq ilk başladılanda, menyu çubuğunda yalnız ikon olaraq görünsün." }, "openAtLogin": { "message": "Giriş ediləndə avtomatik başlat" @@ -977,7 +980,7 @@ "message": "\"Dock\"da həmişə göstər" }, "alwaysShowDockDesc": { - "message": "Menyu sətrinə kiçildiləndə belə Bitwarden nişanını \"Dock\"da göstər." + "message": "Menyu çubuğuna kiçildiləndə belə Bitwarden ikonunu Yuvada göstər." }, "confirmTrayTitle": { "message": "Bildiriş sahəsi nişanını ləğv et" @@ -1450,16 +1453,16 @@ "message": "Hesabınız bağlandı və bütün əlaqəli datalar silindi." }, "preferences": { - "message": "Tercihlər" + "message": "Tərcihlər" }, "enableMenuBar": { - "message": "Menyu sətri nişanını fəallaşdır" + "message": "Menyu çubuğu ikonunu göstər" }, "enableMenuBarDesc": { - "message": "Menyu sətrində həmişə bir nişan göstər." + "message": "Menyu çubuğunda həmişə bir ikon göstər." }, "hideToMenuBar": { - "message": "Menyu sətrini gizlət" + "message": "Menyu çubuğunda gizlət" }, "selectOneCollection": { "message": "Ən azı bir kolleksiya seçməlisiniz." @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Brauzer inteqrasiyası dəstəklənmir" }, + "browserIntegrationErrorTitle": { + "message": "Brauzer inteqrasiyasını fəallaşdırma xətası" + }, + "browserIntegrationErrorDesc": { + "message": "Brauzer inteqrasiyasını fəallaşdırarkən bir xəta baş verdi." + }, "browserIntegrationMasOnlyDesc": { "message": "Təəssüf ki, brauzer inteqrasiyası indilik yalnız Mac App Store versiyasında dəstəklənir." }, @@ -2021,7 +2030,7 @@ "message": "Eyni vaxtda 5-dən çox hesaba giriş edilə bilməz." }, "accountPreferences": { - "message": "Tercihlər" + "message": "Tərcihlər" }, "appPreferences": { "message": "Tətbiq ayarları (bütün hesablar)" @@ -2688,6 +2697,9 @@ "message": "Ortaq formatlar", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Uğurlu" + }, "troubleshooting": { "message": "Problemlərin aradan qaldırılması" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Parol silindi" + }, + "errorAssigningTargetCollection": { + "message": "Hədəf kolleksiyaya təyin etmə xətası." + }, + "errorAssigningTargetFolder": { + "message": "Hədəf qovluğa təyin etmə xətası." } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 2bc33f2b28..e0133e5a74 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Змяніць асноўны пароль" }, - "changeMasterPasswordConfirmation": { - "message": "Вы можаце змяніць свой асноўны пароль у вэб-сховішчы на bitwarden.com. Перайсці на вэб-сайт зараз?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Фраза адбітка пальца", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Інтэграцыя з браўзерам не падтрымліваецца" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "На жаль, інтэграцыя з браўзерам зараз падтрымліваецца толькі ў версіі для Mac App Store." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 9f6d5bdd36..20f47d4bcd 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Промяна на главната парола" }, - "changeMasterPasswordConfirmation": { - "message": "Главната парола на трезор може да се промени чрез сайта bitwarden.com. Искате ли да го посетите?" + "continueToWebApp": { + "message": "Продължаване към уеб приложението?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Може да промените главната си парола в уеб приложението на Битуорден." }, "fingerprintPhrase": { "message": "Уникална фраза", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Интеграцията с браузър не се поддържа" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "За жалост в момента интеграцията с браузър не се поддържа във версията за магазина на Mac." }, @@ -2688,6 +2697,9 @@ "message": "Често използвани формати", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Отстраняване на проблеми" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Секретният ключ е премахнат" + }, + "errorAssigningTargetCollection": { + "message": "Грешка при задаването на целева колекция." + }, + "errorAssigningTargetFolder": { + "message": "Грешка при задаването на целева папка." } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 22893aadab..626734ebff 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "মূল পাসওয়ার্ড পরিবর্তন" }, - "changeMasterPasswordConfirmation": { - "message": "আপনি bitwarden.com ওয়েব ভল্ট থেকে মূল পাসওয়ার্ডটি পরিবর্তন করতে পারেন। আপনি কি এখনই ওয়েবসাইটটি দেখতে চান?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "ফিঙ্গারপ্রিন্ট ফ্রেজ", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 6adb5bbce3..9d5685cca9 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Promijenite glavnu lozinku" }, - "changeMasterPasswordConfirmation": { - "message": "Možete da promjenite svoju glavnu lozinku na bitwarden.com web trezoru. Da li želite da posjetite web stranicu sada?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Jedinstvena fraza", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Nažalost, za sada je integracija sa preglednikom podržana samo u Mac App Store verziji aplikacije." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index c76111b53c..d8c0f32948 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Canvia la contrasenya mestra" }, - "changeMasterPasswordConfirmation": { - "message": "Podeu canviar la contrasenya mestra a la caixa forta web de bitwarden.com. Voleu visitar el lloc web ara?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Frase d'empremta digital", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "La integració en el navegador no és compatible" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Malauradament, la integració del navegador només és compatible amb la versió de Mac App Store." }, @@ -2688,6 +2697,9 @@ "message": "Formats comuns", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Resolució de problemes" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Clau de pas suprimida" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index e7ba56e81c..e68fe8fffc 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Změnit hlavní heslo" }, - "changeMasterPasswordConfirmation": { - "message": "Hlavní heslo si můžete změnit na webové stránce bitwarden.com. Chcete tuto stránku nyní otevřít?" + "continueToWebApp": { + "message": "Pokračovat do webové aplikace?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Hlavní heslo můžete změnit ve webové aplikaci Bitwardenu." }, "fingerprintPhrase": { "message": "Fráze otisku prstu", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Integrace prohlížeče není podporována" }, + "browserIntegrationErrorTitle": { + "message": "Chyba při povolování integrace prohlížeče" + }, + "browserIntegrationErrorDesc": { + "message": "Vyskytla se chyba při povolování integrace prohlížeče." + }, "browserIntegrationMasOnlyDesc": { "message": "Integrace prohlížeče je podporována jen ve verzi pro Mac App Store." }, @@ -2688,6 +2697,9 @@ "message": "Společné formáty", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Úspěch" + }, "troubleshooting": { "message": "Řešení problémů" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Přístupový klíč byl odebrán" + }, + "errorAssigningTargetCollection": { + "message": "Chyba při přiřazování cílové kolekce." + }, + "errorAssigningTargetFolder": { + "message": "Chyba při přiřazování cílové složky." } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index e87b805d0b..62f2e608bb 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index 1f994cf8eb..0e578a6f66 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Skift hovedadgangskode" }, - "changeMasterPasswordConfirmation": { - "message": "Man kan ændre sin hovedadgangskode via bitwarden.com web-boksen. Besøg webstedet nu?" + "continueToWebApp": { + "message": "Fortsæt til web-app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Hovedadgangskoden kan ændres via Bitwarden web-appen." }, "fingerprintPhrase": { "message": "Fingeraftrykssætning", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browserintegration understøttes ikke" }, + "browserIntegrationErrorTitle": { + "message": "Fejl ved aktivering af webbrowserintegration" + }, + "browserIntegrationErrorDesc": { + "message": "En fejl opstod under aktivering af webbrowserintegration." + }, "browserIntegrationMasOnlyDesc": { "message": "Desværre understøttes browserintegration indtil videre kun i Mac App Store-versionen." }, @@ -2688,6 +2697,9 @@ "message": "Almindelige formater", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Gennemført" + }, "troubleshooting": { "message": "Fejlfinding" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Adgangsnøgle fjernet" + }, + "errorAssigningTargetCollection": { + "message": "Fejl ved tildeling af målsamling." + }, + "errorAssigningTargetFolder": { + "message": "Fejl ved tildeling af målmappe." } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 428cfd6a27..bba1ccec15 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Master-Passwort ändern" }, - "changeMasterPasswordConfirmation": { - "message": "Du kannst dein Master-Passwort im bitwarden.com-Web-Tresor ändern. Möchtest du die Seite jetzt öffnen?" + "continueToWebApp": { + "message": "Weiter zur Web-App?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Du kannst dein Master-Passwort in der Bitwarden Web-App ändern." }, "fingerprintPhrase": { "message": "Fingerabdruck-Phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser-Integration wird nicht unterstützt" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Leider wird die Browser-Integration derzeit nur in der Mac App Store Version unterstützt." }, @@ -2688,6 +2697,9 @@ "message": "Gängigste Formate", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Problembehandlung" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey entfernt" + }, + "errorAssigningTargetCollection": { + "message": "Fehler beim Zuweisen der Ziel-Sammlung." + }, + "errorAssigningTargetFolder": { + "message": "Fehler beim Zuweisen des Ziel-Ordners." } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 63b1f21c2e..87360c33ce 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Αλλαγή Κύριου Κωδικού" }, - "changeMasterPasswordConfirmation": { - "message": "Μπορείτε να αλλάξετε τον κύριο κωδικό στο bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Φράση Δακτυλικών Αποτυπωμάτων", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Η ενσωμάτωση του περιηγητή δεν υποστηρίζεται" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Δυστυχώς η ενσωμάτωση του προγράμματος περιήγησης υποστηρίζεται μόνο στην έκδοση Mac App Store για τώρα." }, @@ -2688,6 +2697,9 @@ "message": "Κοινές μορφές", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Αντιμετώπιση Προβλημάτων" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 53958bca57..5c8c32b7c1 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index f6011c301f..abfa0b1c0d 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 772eb70985..427f08f805 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index e3dcd0dc4c..f7df93bdd7 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Cambiar contraseña maestra" }, - "changeMasterPasswordConfirmation": { - "message": "Puedes cambiar tu contraseña maestra en la caja fuerte web de bitwarden.com. ¿Quieres visitar ahora el sitio web?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Frase de huella digital", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "La integración con el navegador no está soportada" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Por desgracia la integración del navegador sólo está soportada por ahora en la versión de la Mac App Store." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 2b54df2a91..02cd737baa 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Muuda ülemparooli" }, - "changeMasterPasswordConfirmation": { - "message": "Saad oma ülemparooli muuta bitwarden.com veebihoidlas. Soovid seda kohe teha?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Sõrmejälje fraas", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Brauseri integratsioon ei ole toetatud" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Paraku on brauseri integratsioon hetkel toetatud ainult Mac App Store'i versioonis." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index d66d5265e1..2067b2dcc2 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Aldatu pasahitz nagusia" }, - "changeMasterPasswordConfirmation": { - "message": "Zure pasahitz nagusia alda dezakezu bitwarden.com webgunean. Orain joan nahi duzu webgunera?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Hatz-marka digitalaren esaldia", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Ez da nabigatzailearen integrazioa onartzen" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Zoritxarrez, Mac App Storeren bertsioan soilik onartzen da oraingoz nabigatzailearen integrazioa." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index c62bb99b2d..ef34f8222a 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "تغییر کلمه عبور اصلی" }, - "changeMasterPasswordConfirmation": { - "message": "شما می‌توانید کلمه عبور اصلی خود را در bitwarden.com تغییر دهید. آیا می‌خواهید از سایت بازدید کنید؟" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "عبارت اثر انگشت", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "ادغام مرورگر پشتیبانی نمی‌شود" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "متأسفانه در حال حاضر ادغام مرورگر فقط در نسخه Mac App Store پشتیبانی می‌شود." }, @@ -2688,6 +2697,9 @@ "message": "فرمت‌های رایج", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index f74136aedc..03059f7ef3 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Vaihda pääsalasana" }, - "changeMasterPasswordConfirmation": { - "message": "Voit vaihtaa pääsalasanasi bitwarden.com-verkkoholvissa. Haluatko käydä sivustolla nyt?" + "continueToWebApp": { + "message": "Avataanko verkkosovellus?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Voit vaihtaa pääsalasanasi Bitwardenin verkkosovelluksessa." }, "fingerprintPhrase": { "message": "Tunnistelauseke", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Selainintegraatiota ei tueta" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Valitettavasti selainintegraatiota tuetaan toistaiseksi vain Mac App Store -versiossa." }, @@ -2688,6 +2697,9 @@ "message": "Yleiset muodot", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Vianetsintä" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Suojausavain poistettiin" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 170559fc64..d28a4b568c 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Palitan ang master password" }, - "changeMasterPasswordConfirmation": { - "message": "Maaari mong palitan ang iyong master password sa bitwarden.com web vault. Gusto mo bang bisitahin ang website ngayon?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Hulmabig ng Hilik ng Dako", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Hindi suportado ang pagsasama ng browser" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Sa kasamaang palad ang pagsasama ng browser ay suportado lamang sa bersyon ng Mac App Store para sa ngayon." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 20353d2d86..86550b736f 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Changer le mot de passe principal" }, - "changeMasterPasswordConfirmation": { - "message": "Vous pouvez changer votre mot de passe principal depuis le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" + "continueToWebApp": { + "message": "Poursuivre vers l'application web ?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Vous pouvez modifier votre mot de passe principal sur l'application web de Bitwarden." }, "fingerprintPhrase": { "message": "Phrase d'empreinte", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Intégration dans le navigateur non supportée" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Malheureusement l'intégration avec le navigateur est uniquement supportée dans la version Mac App Store pour le moment." }, @@ -2688,6 +2697,9 @@ "message": "Formats communs", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Résolution de problèmes" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Clé d'identification (passkey) retirée" + }, + "errorAssigningTargetCollection": { + "message": "Erreur lors de l'assignation de la collection cible." + }, + "errorAssigningTargetFolder": { + "message": "Erreur lors de l'assignation du dossier cible." } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index f96260c005..889a2beeee 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index cc5a0f011d..3b155ffdf3 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "החלף סיסמה ראשית" }, - "changeMasterPasswordConfirmation": { - "message": "באפשרותך לשנות את הסיסמה הראשית שלך דרך הכספת באתר bitwarden.com. האם ברצונך לפתוח את האתר כעת?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "סיסמת טביעת אצבע", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "שילוב הדפדפן אינו נתמך" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "למצער, אינטגרצייה עם הדפדפן בשלב זה נתמכת רק על ידי גרסת חנות האפליקציות של מקינטוש." }, @@ -2688,6 +2697,9 @@ "message": "תסדירים נפוצים", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 57ef1d32ef..af28c66681 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 1e501cee78..01983d5891 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Promjeni glavnu lozinku" }, - "changeMasterPasswordConfirmation": { - "message": "Svoju glavnu lozinku možeš promijeniti na web trezoru. Želiš li sada posjetiti bitwarden.com?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Jedinstvena fraza", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Integracija preglednika nije podržana" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Nažalost, za sada je integracija s preglednikom podržana samo u Mac App Store verziji aplikacije." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 0b443c9a6b..ecf77e2f34 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Mesterjelszó módosítása" }, - "changeMasterPasswordConfirmation": { - "message": "A mesterjelszó megváltoztatható a bitwarden.com webes széfben. Szeretnénk felkeresni a webhelyet mos?" + "continueToWebApp": { + "message": "Tovább a webes alkalmazáshoz?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "A mesterjelszó a Bitwarden webalkalmazásban módosítható." }, "fingerprintPhrase": { "message": "Azonosítókifejezés", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "A böngésző integráció nem támogatott." }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Sajnos a böngésző integrációt egyelőre csak a Mac App Store verzió támogatja." }, @@ -2688,6 +2697,9 @@ "message": "Általános formátumok", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Hibaelhárítás" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Hiba történt a célgyűjtemény hozzárendelése során." + }, + "errorAssigningTargetFolder": { + "message": "Hiba történt a célmappa hozzárendelése során." } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 008b6b369a..cd36126b05 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Ubah Kata Sandi Utama" }, - "changeMasterPasswordConfirmation": { - "message": "Anda dapat mengubah kata sandi utama Anda di brankas web bitwarden.com. Anda ingin mengunjungi situs web sekarang?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Frase Fingerprint", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Integrasi browser tidak didukung" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Sayangnya integrasi browser hanya didukung di versi Mac App Store untuk saat ini." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index a3a6f771fe..0eeea259e2 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Cambia password principale" }, - "changeMasterPasswordConfirmation": { - "message": "Puoi cambiare la tua password principale sulla cassaforte online di bitwarden.com. Vuoi visitare ora il sito?" + "continueToWebApp": { + "message": "Passa al sito web?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Puoi modificare la tua password principale sul sito web di Bitwarden." }, "fingerprintPhrase": { "message": "Frase impronta", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "L'integrazione del browser non è supportata" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Purtroppo l'integrazione del browser è supportata solo nella versione nell'App Store per ora." }, @@ -2688,6 +2697,9 @@ "message": "Formati comuni", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Risoluzione problemi" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey rimossa" + }, + "errorAssigningTargetCollection": { + "message": "Errore nell'assegnazione della raccolta di destinazione." + }, + "errorAssigningTargetFolder": { + "message": "Errore nell'assegnazione della cartella di destinazione." } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index f0a95d4e35..ab6c0be95f 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "マスターパスワードの変更" }, - "changeMasterPasswordConfirmation": { - "message": "マスターパスワードは bitwarden.com ウェブ保管庫で変更できます。ウェブサイトを開きますか?" + "continueToWebApp": { + "message": "ウェブアプリに進みますか?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Bitwarden ウェブアプリでマスターパスワードを変更できます。" }, "fingerprintPhrase": { "message": "パスフレーズ", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "ブラウザー統合はサポートされていません" }, + "browserIntegrationErrorTitle": { + "message": "ブラウザー連携を有効にする際にエラーが発生しました" + }, + "browserIntegrationErrorDesc": { + "message": "ブラウザー統合の有効化中にエラーが発生しました。" + }, "browserIntegrationMasOnlyDesc": { "message": "残念ながら、ブラウザ統合は、Mac App Storeのバージョンでのみサポートされています。" }, @@ -2688,6 +2697,9 @@ "message": "一般的な形式", "description": "Label indicating the most common import formats" }, + "success": { + "message": "成功" + }, "troubleshooting": { "message": "トラブルシューティング" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "パスキーを削除しました" + }, + "errorAssigningTargetCollection": { + "message": "ターゲットコレクションの割り当てに失敗しました。" + }, + "errorAssigningTargetFolder": { + "message": "ターゲットフォルダーの割り当てに失敗しました。" } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index f96260c005..889a2beeee 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index f96260c005..889a2beeee 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 281d64cbc2..eb0cbcf6be 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ ಬದಲಾಯಿಸಿ" }, - "changeMasterPasswordConfirmation": { - "message": "ನಿಮ್ಮ ಮಾಸ್ಟರ್ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ನೀವು bitwarden.com ವೆಬ್ ವಾಲ್ಟ್‌ನಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು. ನೀವು ಈಗ ವೆಬ್‌ಸೈಟ್‌ಗೆ ಭೇಟಿ ನೀಡಲು ಬಯಸುವಿರಾ?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಫ್ರೇಸ್", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "ದುರದೃಷ್ಟವಶಾತ್ ಬ್ರೌಸರ್ ಏಕೀಕರಣವನ್ನು ಇದೀಗ ಮ್ಯಾಕ್ ಆಪ್ ಸ್ಟೋರ್ ಆವೃತ್ತಿಯಲ್ಲಿ ಮಾತ್ರ ಬೆಂಬಲಿಸಲಾಗುತ್ತದೆ." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index a09d53b1dd..8e50ade96c 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "마스터 비밀번호 변경" }, - "changeMasterPasswordConfirmation": { - "message": "bitwarden.com 웹 보관함에서 마스터 비밀번호를 바꿀 수 있습니다. 지금 웹 사이트를 방문하시겠습니까?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "지문 구절", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "브라우저와 연결이 지원되지 않음" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "브라우저와 연결은 현재 Mac App Store 버전에서만 지원됩니다." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index dbc2e13d1c..e9de697005 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Keisti pagrindinį slaptažodį" }, - "changeMasterPasswordConfirmation": { - "message": "Pagrindinį slaptažodį galite pakeisti bitwarden.com žiniatinklio saugykloje. Ar norite dabar apsilankyti svetainėje?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Piršto antspaudo frazė", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Naršyklės integravimas nepalaikomas" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Deja, bet naršyklės integravimas kol kas palaikomas tik Mac App Store versijoje." }, @@ -2688,6 +2697,9 @@ "message": "Dažni formatai", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 0a3501dded..0227a8c524 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Mainīt galveno paroli" }, - "changeMasterPasswordConfirmation": { - "message": "Galveno paroli ir iespējams mainīt bitwarden.com tīmekļa glabātavā. Vai tagad apmeklēt tīmekļvietni?" + "continueToWebApp": { + "message": "Pāriet uz tīmekļa lietotni?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Savu galveno paroli var mainīt Bitwarden tīmekļa lietotnē." }, "fingerprintPhrase": { "message": "Atpazīšanas vārdkopa", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Sasaistīšana ar pārlūku nav atbalstīta" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Diemžēl sasaistīšāna ar pārlūku pagaidām ir nodrošināta tikai Mac App Store laidienā." }, @@ -2688,6 +2697,9 @@ "message": "Izplatīti veidoli", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Sarežģījumu novēršana" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Piekļuves atslēga noņemta" + }, + "errorAssigningTargetCollection": { + "message": "Kļūda mērķa krājuma piešķiršanā." + }, + "errorAssigningTargetFolder": { + "message": "Kļūda mērķa mapes piešķiršanā." } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index d5e3bddf8e..1f49961b46 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Promjena glavne lozinke" }, - "changeMasterPasswordConfirmation": { - "message": "Možete promijeniti svoju glavnu lozinku u trezoru na internet strani bitwarden.com. Da li želite da posjetite internet lokaciju sada?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fraza računa", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 6b1137d232..96811b9dba 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "പ്രാഥമിക പാസ്‌വേഡ് മാറ്റുക" }, - "changeMasterPasswordConfirmation": { - "message": "തങ്ങൾക്കു Bitwarden വെബ് വാൾട്ടിൽ പ്രാഥമിക പാസ്‌വേഡ് മാറ്റാൻ സാധിക്കും.വെബ്സൈറ്റ് ഇപ്പോൾ സന്ദർശിക്കാൻ ആഗ്രഹിക്കുന്നുവോ?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "ഫിംഗർപ്രിന്റ് ഫ്രേസ്‌", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index f96260c005..889a2beeee 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 2626e93c24..0ee0db69ef 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 8e8e2e2cbb..7bf132bdac 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Endre hovedpassordet" }, - "changeMasterPasswordConfirmation": { - "message": "Du kan endre superpassordet ditt på bitwarden.net-netthvelvet. Vil du besøke det nettstedet nå?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingeravtrykksfrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Nettleserintegrasjon støttes ikke" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Nettleserintegrasjon støttes dessverre bare i Mac App Store-versjonen for øyeblikket." }, @@ -2688,6 +2697,9 @@ "message": "Vanlige formater", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index e7d586023e..13e1466805 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 9c4ba78036..b5f2a413d6 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Hoofdwachtwoord wijzigen" }, - "changeMasterPasswordConfirmation": { - "message": "Je kunt je hoofdwachtwoord wijzigen in de kluis op bitwarden.com. Wil je de website nu bezoeken?" + "continueToWebApp": { + "message": "Doorgaan naar web-app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Je kunt je hoofdwachtwoord wijzigen in de Bitwarden-webapp." }, "fingerprintPhrase": { "message": "Vingerafdrukzin", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browserintegratie niet ondersteund" }, + "browserIntegrationErrorTitle": { + "message": "Fout bij inschakelen van de browserintegratie" + }, + "browserIntegrationErrorDesc": { + "message": "Er is iets misgegaan bij het tijdens het inschakelen van de browserintegratie." + }, "browserIntegrationMasOnlyDesc": { "message": "Helaas wordt browserintegratie momenteel alleen ondersteund in de Mac App Store-versie." }, @@ -2688,6 +2697,9 @@ "message": "Veelvoorkomende formaten", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Probleemoplossing" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey verwijderd" + }, + "errorAssigningTargetCollection": { + "message": "Fout bij toewijzen doelverzameling." + }, + "errorAssigningTargetFolder": { + "message": "Fout bij toewijzen doelmap." } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index ea55378f5b..35e7173d74 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Endre hovudpassord" }, - "changeMasterPasswordConfirmation": { - "message": "Du kan endre hovudpassordet ditt i Bitwarden sin nettkvelv. Vil du gå til nettstaden no?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index c6c0c2fb0c..cd83d2ea69 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index a0626a6c90..250c557309 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Zmień hasło główne" }, - "changeMasterPasswordConfirmation": { - "message": "Hasło główne możesz zmienić na stronie sejfu bitwarden.com. Czy chcesz przejść do tej strony?" + "continueToWebApp": { + "message": "Kontynuować do aplikacji internetowej?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Możesz zmienić swoje hasło główne w aplikacji internetowej Bitwarden." }, "fingerprintPhrase": { "message": "Unikalny identyfikator konta", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Połączenie z przeglądarką nie jest obsługiwane" }, + "browserIntegrationErrorTitle": { + "message": "Błąd podczas włączania integracji z przeglądarką" + }, + "browserIntegrationErrorDesc": { + "message": "Wystąpił błąd podczas włączania integracji z przeglądarką." + }, "browserIntegrationMasOnlyDesc": { "message": "Połączenie z przeglądarką jest obsługiwane tylko z wersją aplikacji ze sklepu Mac App Store." }, @@ -2688,6 +2697,9 @@ "message": "Popularne formaty", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Sukces" + }, "troubleshooting": { "message": "Rozwiązywanie problemów" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey został usunięty" + }, + "errorAssigningTargetCollection": { + "message": "Wystąpił błąd podczas przypisywania kolekcji." + }, + "errorAssigningTargetFolder": { + "message": "Wystąpił błąd podczas przypisywania folderu." } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index c8f8316e6d..9a79ad665e 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Alterar Senha Mestra" }, - "changeMasterPasswordConfirmation": { - "message": "Você pode alterar a sua senha mestra no cofre web em bitwarden.com. Você deseja visitar o site agora?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Frase biométrica", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Integração com o navegador não suportado" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Infelizmente, por ora, a integração do navegador só é suportada na versão da Mac App Store." }, @@ -2688,6 +2697,9 @@ "message": "Formatos comuns", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 5fbd7636d1..14f0ec5d2f 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Alterar palavra-passe mestra" }, - "changeMasterPasswordConfirmation": { - "message": "Pode alterar o seu endereço de e-mail no cofre do site bitwarden.com. Deseja visitar o site agora?" + "continueToWebApp": { + "message": "Continuar para a aplicação Web?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Pode alterar a sua palavra-passe mestra na aplicação Web Bitwarden." }, "fingerprintPhrase": { "message": "Frase de impressão digital", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Integração com o navegador não suportada" }, + "browserIntegrationErrorTitle": { + "message": "Erro ao ativar a integração do navegador" + }, + "browserIntegrationErrorDesc": { + "message": "Ocorreu um erro ao ativar a integração do navegador." + }, "browserIntegrationMasOnlyDesc": { "message": "Infelizmente, a integração do navegador só é suportada na versão da Mac App Store por enquanto." }, @@ -2688,6 +2697,9 @@ "message": "Formatos comuns", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Com sucesso" + }, "troubleshooting": { "message": "Resolução de problemas" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Chave de acesso removida" + }, + "errorAssigningTargetCollection": { + "message": "Erro ao atribuir a coleção de destino." + }, + "errorAssigningTargetFolder": { + "message": "Erro ao atribuir a pasta de destino." } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 7af8f7ec16..978f57eb9b 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Schimbare parolă principală" }, - "changeMasterPasswordConfirmation": { - "message": "Puteți modifica parola principală pe saitul web bitwarden.com. Doriți să vizitați saitul acum?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Frază amprentă", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Integrarea browserului nu este acceptată" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Din păcate, integrarea browserului este acceptată numai în versiunea Mac App Store pentru moment." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 0e28c2cf90..c9b3b95b39 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Изменить мастер-пароль" }, - "changeMasterPasswordConfirmation": { - "message": "Вы можете изменить свой мастер-пароль на bitwarden.com. Перейти на сайт сейчас?" + "continueToWebApp": { + "message": "Перейти к веб-приложению?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Изменить мастер-пароль можно в веб-приложении Bitwarden." }, "fingerprintPhrase": { "message": "Фраза отпечатка", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Интеграция с браузером не поддерживается" }, + "browserIntegrationErrorTitle": { + "message": "Ошибка при включении интеграции с браузером" + }, + "browserIntegrationErrorDesc": { + "message": "Произошла ошибка при включении интеграции с браузером." + }, "browserIntegrationMasOnlyDesc": { "message": "К сожалению, интеграция браузера пока поддерживается только в версии Mac App Store." }, @@ -2688,6 +2697,9 @@ "message": "Основные форматы", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Успешно" + }, "troubleshooting": { "message": "Устранение проблем" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey удален" + }, + "errorAssigningTargetCollection": { + "message": "Ошибка при назначении целевой коллекции." + }, + "errorAssigningTargetFolder": { + "message": "Ошибка при назначении целевой папки." } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 60e8aea93c..3d43997144 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 240b883254..6499486b9d 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -445,7 +445,7 @@ "message": "Vyhnúť sa zameniteľným znakom" }, "searchCollection": { - "message": "Search collection" + "message": "Vyhľadať zbierku" }, "searchFolder": { "message": "Search folder" @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Zmeniť hlavné heslo" }, - "changeMasterPasswordConfirmation": { - "message": "Svoje hlavné heslo môžete zmeniť vo webovom trezore bitwarden.com. Chcete teraz navštíviť túto stránku?" + "continueToWebApp": { + "message": "Pokračovať vo webovej aplikácii?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Hlavné heslo si môžete zmeniť vo webovej aplikácii Bitwarden." }, "fingerprintPhrase": { "message": "Fráza odtlačku", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Integrácia v prehliadači nie je podporovaná" }, + "browserIntegrationErrorTitle": { + "message": "Chyba pri povoľovaní integrácie v prehliadači" + }, + "browserIntegrationErrorDesc": { + "message": "Pri povoľovaní integrácie v prehliadači sa vyskytla chyba." + }, "browserIntegrationMasOnlyDesc": { "message": "Bohužiaľ, integrácia v prehliadači je zatiaľ podporovaná iba vo verzii Mac App Store." }, @@ -2688,6 +2697,9 @@ "message": "Bežné formáty", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Úspech" + }, "troubleshooting": { "message": "Riešenie problémov" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Prístupový kľúč bol odstránený" + }, + "errorAssigningTargetCollection": { + "message": "Chyba pri priraďovaní cieľovej kolekcie." + }, + "errorAssigningTargetFolder": { + "message": "Chyba pri priraďovaní cieľového priečinka." } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 528274cf29..8cb06dcf0c 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Spremeni glavno geslo" }, - "changeMasterPasswordConfirmation": { - "message": "Svoje glavno geslo lahko spremenite v bitwarden.com spletnem trezorju. Želite obiskati spletno stran zdaj?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Identifikacijsko geslo", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 77b5f7221d..37c5dfa382 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Промени главну лозинку" }, - "changeMasterPasswordConfirmation": { - "message": "Можете променити главну лозинку у Вашем сефу на bitwarden.com. Да ли желите да посетите веб страницу сада?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Сигурносна Фраза Сефа", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Интеграција са претраживачем није подржана" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Нажалост, интеграција прегледача за сада је подржана само у верзији Mac App Store." }, @@ -2688,6 +2697,9 @@ "message": "Уобичајени формати", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Решавање проблема" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Приступачни кључ је уклоњен" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index c07f7efef3..bd21c0f328 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Ändra huvudlösenord" }, - "changeMasterPasswordConfirmation": { - "message": "Du kan ändra ditt huvudlösenord i Bitwardens webbvalv. Vill du besöka webbplatsen nu?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingeravtrycksfras", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Webbläsarintegration stöds inte" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Tyvärr stöds webbläsarintegration för tillfället endast i versionen från Mac App Store." }, @@ -2688,6 +2697,9 @@ "message": "Vanliga format", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Felsökning" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Nyckel borttagen" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index f96260c005..889a2beeee 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Change master password" }, - "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index efbfc86b33..f1cd5351f7 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "เปลี่ยนรหัสผ่านหลัก" }, - "changeMasterPasswordConfirmation": { - "message": "คุณสามารถเปลี่ยนรหัสผ่านหลักของคุณได้ที่เว็บ bitwarden.com คุณต้องการไปที่เว็บไซต์ตอนนี้ไหม?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint Phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Browser integration not supported" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 36d62ed2a5..3e7229c41b 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Ana parolayı değiştir" }, - "changeMasterPasswordConfirmation": { - "message": "Ana parolanızı bitwarden.com web kasası üzerinden değiştirebilirsiniz. Siteyi şimdi ziyaret etmek ister misiniz?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Parmak izi ifadesi", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Tarayıcı entegrasyonu desteklenmiyor" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Ne yazık ki tarayıcı entegrasyonu şu anda sadece Mac App Store sürümünde destekleniyor." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Sorun giderme" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 377fd23b0b..9ee7652093 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Змінити головний пароль" }, - "changeMasterPasswordConfirmation": { - "message": "Ви можете змінити головний пароль в сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" + "continueToWebApp": { + "message": "Продовжити у вебпрограмі?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "Ви можете змінити головний пароль у вебпрограмі Bitwarden." }, "fingerprintPhrase": { "message": "Фраза відбитка", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Інтеграція з браузером не підтримується" }, + "browserIntegrationErrorTitle": { + "message": "Помилка увімкнення інтеграції з браузером" + }, + "browserIntegrationErrorDesc": { + "message": "Під час увімкнення інтеграції з браузером сталася помилка." + }, "browserIntegrationMasOnlyDesc": { "message": "На жаль, зараз інтеграція з браузером підтримується лише у версії для Mac з App Store." }, @@ -2688,6 +2697,9 @@ "message": "Поширені формати", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Успішно" + }, "troubleshooting": { "message": "Усунення проблем" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Ключ доступу вилучено" + }, + "errorAssigningTargetCollection": { + "message": "Помилка призначення цільової збірки." + }, + "errorAssigningTargetFolder": { + "message": "Помилка призначення цільової теки." } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 138773d40a..0c0e6f6df7 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "Thay đổi mật khẩu chính" }, - "changeMasterPasswordConfirmation": { - "message": "Bạn có thể thay đổi mật khẩu chính trong kho bitwarden nền web. Bạn có muốn truy cập trang web bây giờ?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "Fingerprint Phrase", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "Không hỗ trợ tích hợp trình duyệt" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "Rất tiếc, tính năng tích hợp trình duyệt hiện chỉ được hỗ trợ trong phiên bản App Store trên Mac." }, @@ -2688,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "Passkey removed" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 8725fa0f21..3819cb967c 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "修改主密码" }, - "changeMasterPasswordConfirmation": { - "message": "您可以在 bitwarden.com 网页密码库修改您的主密码。现在要访问吗?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "指纹短语", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "不支持浏览器集成" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "很遗憾,目前仅 Mac App Store 版本支持浏览器集成。" }, @@ -2688,6 +2697,9 @@ "message": "常规格式", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "故障排除" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "通行密钥已移除" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index c93f236976..5f768b0a43 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -800,8 +800,11 @@ "changeMasterPass": { "message": "變更主密碼" }, - "changeMasterPasswordConfirmation": { - "message": "您可以在 bitwarden.com 網頁版密碼庫變更主密碼。現在要前往嗎?" + "continueToWebApp": { + "message": "Continue to web app?" + }, + "changeMasterPasswordOnWebConfirmation": { + "message": "You can change your master password on the Bitwarden web app." }, "fingerprintPhrase": { "message": "指紋短語", @@ -1629,6 +1632,12 @@ "browserIntegrationUnsupportedTitle": { "message": "不支援瀏覽器整合" }, + "browserIntegrationErrorTitle": { + "message": "Error enabling browser integration" + }, + "browserIntegrationErrorDesc": { + "message": "An error has occurred while enabling browser integration." + }, "browserIntegrationMasOnlyDesc": { "message": "很遺憾,目前僅 Mac App Store 版本支援瀏覽器整合功能。" }, @@ -2688,6 +2697,9 @@ "message": "常見格式", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "疑難排解" }, @@ -2702,5 +2714,11 @@ }, "passkeyRemoved": { "message": "金鑰已移除" + }, + "errorAssigningTargetCollection": { + "message": "Error assigning target collection." + }, + "errorAssigningTargetFolder": { + "message": "Error assigning target folder." } } From c1c6afb0f48e9cc8852448a0826263810f7a5cae Mon Sep 17 00:00:00 2001 From: Robyn MacCallum Date: Fri, 19 Apr 2024 09:45:09 -0400 Subject: [PATCH 38/86] [PM-7562] Add DuckDuckGo back to State Service (#8791) * Add ddg back to state service * Remove getters --- .../desktop/src/app/accounts/settings.component.ts | 5 +++++ .../src/platform/abstractions/state.service.ts | 7 +++++++ .../src/platform/models/domain/global-state.ts | 1 + libs/common/src/platform/services/state.service.ts | 14 ++++++++++++++ 4 files changed, 27 insertions(+) diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 06533e18fc..f3958d7c87 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -660,6 +660,11 @@ export class SettingsComponent implements OnInit { this.form.value.enableDuckDuckGoBrowserIntegration, ); + // Adding to cover users on a previous version of DDG + await this.stateService.setEnableDuckDuckGoBrowserIntegration( + this.form.value.enableDuckDuckGoBrowserIntegration, + ); + if (!this.form.value.enableBrowserIntegration) { await this.stateService.setDuckDuckGoSharedKey(null); } diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 2348c8844a..27026ac0ea 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -74,6 +74,13 @@ export abstract class StateService { * Used when Lock with MP on Restart is enabled */ setPinKeyEncryptedUserKeyEphemeral: (value: EncString, options?: StorageOptions) => Promise; + /** + * @deprecated For backwards compatible purposes only, use DesktopAutofillSettingsService + */ + setEnableDuckDuckGoBrowserIntegration: ( + value: boolean, + options?: StorageOptions, + ) => Promise; /** * @deprecated For migration purposes only, use getUserKeyMasterKey instead */ diff --git a/libs/common/src/platform/models/domain/global-state.ts b/libs/common/src/platform/models/domain/global-state.ts index 703a998d1c..cd7cf7d174 100644 --- a/libs/common/src/platform/models/domain/global-state.ts +++ b/libs/common/src/platform/models/domain/global-state.ts @@ -4,4 +4,5 @@ export class GlobalState { vaultTimeoutAction?: string; enableBrowserIntegration?: boolean; enableBrowserIntegrationFingerprint?: boolean; + enableDuckDuckGoBrowserIntegration?: boolean; } diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 9edc9ed1e3..1fb2d71670 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -571,6 +571,20 @@ export class StateService< ); } + async setEnableDuckDuckGoBrowserIntegration( + value: boolean, + options?: StorageOptions, + ): Promise { + const globals = await this.getGlobals( + this.reconcileOptions(options, await this.defaultOnDiskOptions()), + ); + globals.enableDuckDuckGoBrowserIntegration = value; + await this.saveGlobals( + globals, + this.reconcileOptions(options, await this.defaultOnDiskOptions()), + ); + } + /** * @deprecated Use UserKey instead */ From 6f2bed63a63ed3b6ddf49066bc8c7b000be44688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Gon=C3=A7alves?= Date: Fri, 19 Apr 2024 14:53:34 +0100 Subject: [PATCH 39/86] [PM-7569] Fix ciphers view update race on desktop (#8821) * PM-7569 Wait for the update before allow reading ciphers$ * PM-7569 Remove commented line --- .../src/vault/services/cipher.service.ts | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 4a13196c9c..0b44636ea6 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1,4 +1,4 @@ -import { Observable, firstValueFrom, map } from "rxjs"; +import { Observable, firstValueFrom, map, share, skipWhile, switchMap } from "rxjs"; import { SemVer } from "semver"; import { ApiService } from "../../abstractions/api.service"; @@ -21,7 +21,13 @@ import Domain from "../../platform/models/domain/domain-base"; import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer"; import { EncString } from "../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; -import { ActiveUserState, StateProvider } from "../../platform/state"; +import { + ActiveUserState, + CIPHERS_MEMORY, + DeriveDefinition, + DerivedState, + StateProvider, +} from "../../platform/state"; import { CipherId, CollectionId, OrganizationId } from "../../types/guid"; import { UserKey, OrgKey } from "../../types/key"; import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; @@ -71,10 +77,14 @@ export class CipherService implements CipherServiceAbstraction { private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache( this.sortCiphersByLastUsed, ); + private ciphersExpectingUpdate: DerivedState; localData$: Observable>; ciphers$: Observable>; cipherViews$: Observable>; + viewFor$(id: CipherId) { + return this.cipherViews$.pipe(map((views) => views[id])); + } addEditCipherInfo$: Observable; private localDataState: ActiveUserState>; @@ -99,10 +109,29 @@ export class CipherService implements CipherServiceAbstraction { this.encryptedCiphersState = this.stateProvider.getActive(ENCRYPTED_CIPHERS); this.decryptedCiphersState = this.stateProvider.getActive(DECRYPTED_CIPHERS); this.addEditCipherInfoState = this.stateProvider.getActive(ADD_EDIT_CIPHER_INFO_KEY); + this.ciphersExpectingUpdate = this.stateProvider.getDerived( + this.encryptedCiphersState.state$, + new DeriveDefinition(CIPHERS_MEMORY, "ciphersExpectingUpdate", { + derive: (_: Record) => false, + deserializer: (value) => value, + }), + {}, + ); this.localData$ = this.localDataState.state$.pipe(map((data) => data ?? {})); - this.ciphers$ = this.encryptedCiphersState.state$.pipe(map((ciphers) => ciphers ?? {})); - this.cipherViews$ = this.decryptedCiphersState.state$.pipe(map((views) => views ?? {})); + // First wait for ciphersExpectingUpdate to be false before emitting ciphers + this.ciphers$ = this.ciphersExpectingUpdate.state$.pipe( + skipWhile((expectingUpdate) => expectingUpdate), + switchMap(() => this.encryptedCiphersState.state$), + map((ciphers) => ciphers ?? {}), + ); + this.cipherViews$ = this.decryptedCiphersState.state$.pipe( + map((views) => views ?? {}), + + share({ + resetOnRefCountZero: true, + }), + ); this.addEditCipherInfo$ = this.addEditCipherInfoState.state$; } @@ -807,6 +836,8 @@ export class CipherService implements CipherServiceAbstraction { private async updateEncryptedCipherState( update: (current: Record) => Record, ): Promise> { + // Store that we should wait for an update to return any ciphers + await this.ciphersExpectingUpdate.forceValue(true); await this.clearDecryptedCiphersState(); const [, updatedCiphers] = await this.encryptedCiphersState.update((current) => { const result = update(current ?? {}); From 8cb16fb406b735e4fb97bcfe4dc46b00c0a913d5 Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Fri, 19 Apr 2024 07:02:48 -0700 Subject: [PATCH 40/86] Make extension copy updates for Marketing (#8822) --- apps/browser/src/_locales/en/messages.json | 4 +- apps/browser/store/locales/en/copy.resx | 103 ++++++++++++-------- apps/browser/store/windows/AppxManifest.xml | 8 +- 3 files changed, 66 insertions(+), 49 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 8c81088fc5..7e6e333689 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/store/locales/en/copy.resx b/apps/browser/store/locales/en/copy.resx index 191198691d..df8d63835c 100644 --- a/apps/browser/store/locales/en/copy.resx +++ b/apps/browser/store/locales/en/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 50 languages. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/windows/AppxManifest.xml b/apps/browser/store/windows/AppxManifest.xml index f57b3db988..df02ea085c 100644 --- a/apps/browser/store/windows/AppxManifest.xml +++ b/apps/browser/store/windows/AppxManifest.xml @@ -11,7 +11,7 @@ Version="0.0.0.0"/> - Bitwarden Extension - Free Password Manager + Bitwarden Password Manager 8bit Solutions LLC Assets/icon_50.png @@ -30,10 +30,10 @@ @@ -41,7 +41,7 @@ + DisplayName="Bitwarden Password Manager"> From 6cafb1d28fb5dd1fe4e524c6f2f203e3fa23b2a0 Mon Sep 17 00:00:00 2001 From: Merissa Weinstein Date: Fri, 19 Apr 2024 09:09:58 -0500 Subject: [PATCH 41/86] [PM-2870] [PM-2865] Accessibility updates: add labels to buttons & form checkboxes (#8358) * organization-options: add area-labels to links * vault-cipher-row: add aria-label to input checkbox * vault-collection-row: add aria-label to collection item * add internationalizatino to org options menu * add internationlization to checkbox aria-labels for vault and collection items * organization-options-component: remove added aria-lables to buttons inside of toggle --------- Co-authored-by: Merissa Weinstein --- .../vault-items/vault-cipher-row.component.html | 1 + .../vault-items/vault-collection-row.component.html | 1 + .../components/organization-options.component.html | 7 ++++++- apps/web/src/locales/en/messages.json | 9 +++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html index 5ddabf0557..ae22d89f7f 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html @@ -6,6 +6,7 @@ [disabled]="disabled" [checked]="checked" (change)="$event ? this.checkedToggled.next() : null" + [attr.aria-label]="'vaultItemSelect' | i18n" /> diff --git a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html index d333f92d5c..d03b6dcc38 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html @@ -7,6 +7,7 @@ [disabled]="disabled" [checked]="checked" (change)="$event ? this.checkedToggled.next() : null" + [attr.aria-label]="'collectionItemSelect' | i18n" /> diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html index f4fb2cc040..0b94b6e2be 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html @@ -1,5 +1,10 @@ - diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 845816562b..63069a83de 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } From fffef95c5ef0ef3f2a9112eb3e2f49d5412362a0 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:20:13 -0400 Subject: [PATCH 42/86] Auth/PM-7235 - Refactor AuthService.getAuthStatus, deprecate everBeenUnlocked, and handle initialization of auto user key on client init (#8590) * PM-7235 - AuthSvc - Refactor getAuthStatus to simply use the cryptoService.hasUserKey check to determine the user's auth status. * PM-7235 - CryptoSvc - getUserKey - remove setUserKey side effect if auto key is stored. Will move to app init * PM-7235 - For each client init service, add setUserKeyInMemoryIfAutoUserKeySet logic * PM-7235 - CryptoSvc tests - remove uncessary test. * PM-7235 - Create UserKeyInitService and inject into all init services with new listening logic to support acct switching. * PM-7235 - UserKeyInitSvc - minor refactor of setUserKeyInMemoryIfAutoUserKeySet * PM-7235 - Add test suite for UserKeyInitService * PM-7235 - Remove everBeenUnlocked as it is no longer needed * PM-7235 - Fix tests * PM-7235 - UserKeyInitSvc - per PR feedback, add error handling to protect observable stream from being cancelled in case of an error * PM-7235 - Fix tests * Update libs/common/src/platform/services/user-key-init.service.ts Co-authored-by: Matt Gibson * Update libs/common/src/platform/services/user-key-init.service.ts Co-authored-by: Matt Gibson * PM-7235 - AuthSvc - Per PR review, for getAuthStatus, only check user key existence in memory. * PM-7235 - remove not useful test per PR feedback. * PM-7235 - Per PR feedback, update cryptoService.hasUserKey to only check memory for the user key. * PM-7235 - Per PR feedback, move user key init service listener to main.background instead of init service * PM-7235 - UserKeyInitSvc tests - fix tests to plass --------- Co-authored-by: Matt Gibson --- .../browser/src/background/main.background.ts | 12 ++ .../src/popup/services/init.service.ts | 1 - apps/desktop/src/app/services/init.service.ts | 4 + apps/web/src/app/core/init.service.ts | 3 + .../src/auth/components/lock.component.ts | 1 - .../src/services/jslib-services.module.ts | 7 + libs/common/src/auth/services/auth.service.ts | 29 +--- .../platform/abstractions/state.service.ts | 2 - .../src/platform/models/domain/account.ts | 1 - .../platform/services/crypto.service.spec.ts | 27 +-- .../src/platform/services/crypto.service.ts | 20 +-- .../src/platform/services/state.service.ts | 18 -- .../services/user-key-init.service.spec.ts | 162 ++++++++++++++++++ .../services/user-key-init.service.ts | 57 ++++++ .../vault-timeout-settings.service.ts | 1 - .../vault-timeout.service.spec.ts | 1 - .../vault-timeout/vault-timeout.service.ts | 1 - 17 files changed, 253 insertions(+), 94 deletions(-) create mode 100644 libs/common/src/platform/services/user-key-init.service.spec.ts create mode 100644 libs/common/src/platform/services/user-key-init.service.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 325a7f1943..8432c398b7 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -110,6 +110,7 @@ import { MigrationBuilderService } from "@bitwarden/common/platform/services/mig import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { SystemService } from "@bitwarden/common/platform/services/system.service"; +import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service"; import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; import { ActiveUserStateProvider, @@ -325,6 +326,7 @@ export default class MainBackground { stateEventRunnerService: StateEventRunnerService; ssoLoginService: SsoLoginServiceAbstraction; billingAccountProfileStateService: BillingAccountProfileStateService; + userKeyInitService: UserKeyInitService; scriptInjectorService: BrowserScriptInjectorService; onUpdatedRan: boolean; @@ -1046,6 +1048,12 @@ export default class MainBackground { ); } } + + this.userKeyInitService = new UserKeyInitService( + this.accountService, + this.cryptoService, + this.logService, + ); } async bootstrap() { @@ -1053,6 +1061,10 @@ export default class MainBackground { await this.stateService.init({ runMigrations: !this.isPrivateMode }); + // This is here instead of in in the InitService b/c we don't plan for + // side effects to run in the Browser InitService. + this.userKeyInitService.listenForActiveUserChangesToSetUserKey(); + await (this.i18nService as I18nService).init(); await (this.eventUploadService as EventUploadService).init(true); this.twoFactorService.init(); diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index 4036ace31f..c9e6d66c2a 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -9,7 +9,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { BrowserApi } from "../../platform/browser/browser-api"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service"; - @Injectable() export class InitService { constructor( diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index d1a83d468c..ae2e1ba97c 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -12,6 +12,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; +import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -35,6 +36,7 @@ export class InitService { private nativeMessagingService: NativeMessagingService, private themingService: AbstractThemingService, private encryptService: EncryptService, + private userKeyInitService: UserKeyInitService, @Inject(DOCUMENT) private document: Document, ) {} @@ -42,6 +44,8 @@ export class InitService { return async () => { this.nativeMessagingService.init(); await this.stateService.init({ runMigrations: false }); // Desktop will run them in main process + this.userKeyInitService.listenForActiveUserChangesToSetUserKey(); + // 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.syncService.fullSync(true); diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index d5576d3bf7..dab6ed5e3d 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -11,6 +11,7 @@ import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt. import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; +import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; @@ -27,12 +28,14 @@ export class InitService { private cryptoService: CryptoServiceAbstraction, private themingService: AbstractThemingService, private encryptService: EncryptService, + private userKeyInitService: UserKeyInitService, @Inject(DOCUMENT) private document: Document, ) {} init() { return async () => { await this.stateService.init(); + this.userKeyInitService.listenForActiveUserChangesToSetUserKey(); setTimeout(() => this.notificationsService.init(), 3000); await this.vaultTimeoutService.init(true); diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts index 6602a917c9..9c2ed55357 100644 --- a/libs/angular/src/auth/components/lock.component.ts +++ b/libs/angular/src/auth/components/lock.component.ts @@ -283,7 +283,6 @@ export class LockComponent implements OnInit, OnDestroy { } private async doContinue(evaluatePasswordAfterUnlock: boolean) { - await this.stateService.setEverBeenUnlocked(true); await this.biometricStateService.resetUserPromptCancelled(); this.messagingService.send("unlocked"); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 859103474d..b0d84e7c3b 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -52,6 +52,7 @@ import { ProviderApiService } from "@bitwarden/common/admin-console/services/pro import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service"; import { + AccountService, AccountService as AccountServiceAbstraction, InternalAccountService, } from "@bitwarden/common/auth/abstractions/account.service"; @@ -154,6 +155,7 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r import { NoopNotificationsService } from "@bitwarden/common/platform/services/noop-notifications.service"; import { StateService } from "@bitwarden/common/platform/services/state.service"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; +import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service"; import { ValidationService } from "@bitwarden/common/platform/services/validation.service"; import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; import { @@ -1115,6 +1117,11 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultOrganizationManagementPreferencesService, deps: [StateProvider], }), + safeProvider({ + provide: UserKeyInitService, + useClass: UserKeyInitService, + deps: [AccountService, CryptoServiceAbstraction, LogService], + }), safeProvider({ provide: ErrorHandler, useClass: LoggingErrorHandler, diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index 7a29d313e7..c9e711b4cc 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -12,7 +12,6 @@ import { ApiService } from "../../abstractions/api.service"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; import { StateService } from "../../platform/abstractions/state.service"; -import { KeySuffixOptions } from "../../platform/enums"; import { UserId } from "../../types/guid"; import { AccountService } from "../abstractions/account.service"; import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service"; @@ -91,31 +90,11 @@ export class AuthService implements AuthServiceAbstraction { return AuthenticationStatus.LoggedOut; } - // If we don't have a user key in memory, we're locked - if (!(await this.cryptoService.hasUserKeyInMemory(userId))) { - // Check if the user has vault timeout set to never and verify that - // they've never unlocked their vault - const neverLock = - (await this.cryptoService.hasUserKeyStored(KeySuffixOptions.Auto, userId)) && - !(await this.stateService.getEverBeenUnlocked({ userId: userId })); + // Note: since we aggresively set the auto user key to memory if it exists on app init (see InitService) + // we only need to check if the user key is in memory. + const hasUserKey = await this.cryptoService.hasUserKeyInMemory(userId as UserId); - if (neverLock) { - // Attempt to get the key from storage and set it in memory - const userKey = await this.cryptoService.getUserKeyFromStorage( - KeySuffixOptions.Auto, - userId, - ); - await this.cryptoService.setUserKey(userKey, userId); - } - } - - // We do another check here in case setting the auto key failed - const hasKeyInMemory = await this.cryptoService.hasUserKeyInMemory(userId); - if (!hasKeyInMemory) { - return AuthenticationStatus.Locked; - } - - return AuthenticationStatus.Unlocked; + return hasUserKey ? AuthenticationStatus.Unlocked : AuthenticationStatus.Locked; } logOut(callback: () => void) { diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 27026ac0ea..051604f0ae 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -148,8 +148,6 @@ export abstract class StateService { * @deprecated For migration purposes only, use setEncryptedUserKeyPin instead */ setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise; - getEverBeenUnlocked: (options?: StorageOptions) => Promise; - setEverBeenUnlocked: (value: boolean, options?: StorageOptions) => Promise; getIsAuthenticated: (options?: StorageOptions) => Promise; getKdfConfig: (options?: StorageOptions) => Promise; setKdfConfig: (kdfConfig: KdfConfig, options?: StorageOptions) => Promise; diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index ae7780ada4..5a9a764696 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -126,7 +126,6 @@ export class AccountProfile { name?: string; email?: string; emailVerified?: boolean; - everBeenUnlocked?: boolean; lastSync?: string; userId?: string; kdfIterations?: number; diff --git a/libs/common/src/platform/services/crypto.service.spec.ts b/libs/common/src/platform/services/crypto.service.spec.ts index 16e6d4aa63..2f68cf2ce7 100644 --- a/libs/common/src/platform/services/crypto.service.spec.ts +++ b/libs/common/src/platform/services/crypto.service.spec.ts @@ -91,21 +91,7 @@ describe("cryptoService", () => { expect(userKey).toEqual(mockUserKey); }); - it("sets from the Auto key if the User Key if not set", async () => { - const autoKeyB64 = - "IT5cA1i5Hncd953pb00E58D2FqJX+fWTj4AvoI67qkGHSQPgulAqKv+LaKRAo9Bg0xzP9Nw00wk4TqjMmGSM+g=="; - stateService.getUserKeyAutoUnlock.mockResolvedValue(autoKeyB64); - const setKeySpy = jest.spyOn(cryptoService, "setUserKey"); - - const userKey = await cryptoService.getUserKey(mockUserId); - - expect(setKeySpy).toHaveBeenCalledWith(expect.any(SymmetricCryptoKey), mockUserId); - expect(setKeySpy).toHaveBeenCalledTimes(1); - - expect(userKey.keyB64).toEqual(autoKeyB64); - }); - - it("returns nullish if there is no auto key and the user key is not set", async () => { + it("returns nullish if the user key is not set", async () => { const userKey = await cryptoService.getUserKey(mockUserId); expect(userKey).toBeFalsy(); @@ -147,17 +133,6 @@ describe("cryptoService", () => { }, ); - describe("hasUserKey", () => { - it.each([true, false])( - "returns %s when the user key is not in memory, but the auto key is set", - async (hasKey) => { - stateProvider.singleUser.getFake(mockUserId, USER_KEY).nextState(null); - cryptoService.hasUserKeyStored = jest.fn().mockResolvedValue(hasKey); - expect(await cryptoService.hasUserKey(mockUserId)).toBe(hasKey); - }, - ); - }); - describe("getUserKeyWithLegacySupport", () => { let mockUserKey: UserKey; let mockMasterKey: MasterKey; diff --git a/libs/common/src/platform/services/crypto.service.ts b/libs/common/src/platform/services/crypto.service.ts index c091b6a5a9..3cd443c073 100644 --- a/libs/common/src/platform/services/crypto.service.ts +++ b/libs/common/src/platform/services/crypto.service.ts @@ -164,19 +164,8 @@ export class CryptoService implements CryptoServiceAbstraction { } async getUserKey(userId?: UserId): Promise { - let userKey = await firstValueFrom(this.stateProvider.getUserState$(USER_KEY, userId)); - if (userKey) { - return userKey; - } - - // If the user has set their vault timeout to 'Never', we can load the user key from storage - if (await this.hasUserKeyStored(KeySuffixOptions.Auto, userId)) { - userKey = await this.getKeyFromStorage(KeySuffixOptions.Auto, userId); - if (userKey) { - await this.setUserKey(userKey, userId); - return userKey; - } - } + const userKey = await firstValueFrom(this.stateProvider.getUserState$(USER_KEY, userId)); + return userKey; } async isLegacyUser(masterKey?: MasterKey, userId?: UserId): Promise { @@ -217,10 +206,7 @@ export class CryptoService implements CryptoServiceAbstraction { if (userId == null) { return false; } - return ( - (await this.hasUserKeyInMemory(userId)) || - (await this.hasUserKeyStored(KeySuffixOptions.Auto, userId)) - ); + return await this.hasUserKeyInMemory(userId); } async hasUserKeyInMemory(userId?: UserId): Promise { diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 1fb2d71670..f660cd7a34 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -634,24 +634,6 @@ export class StateService< ); } - async getEverBeenUnlocked(options?: StorageOptions): Promise { - return ( - (await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))) - ?.profile?.everBeenUnlocked ?? false - ); - } - - async setEverBeenUnlocked(value: boolean, options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - account.profile.everBeenUnlocked = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - } - async getIsAuthenticated(options?: StorageOptions): Promise { return ( (await this.tokenService.getAccessToken(options?.userId as UserId)) != null && diff --git a/libs/common/src/platform/services/user-key-init.service.spec.ts b/libs/common/src/platform/services/user-key-init.service.spec.ts new file mode 100644 index 0000000000..567320ded6 --- /dev/null +++ b/libs/common/src/platform/services/user-key-init.service.spec.ts @@ -0,0 +1,162 @@ +import { mock } from "jest-mock-extended"; + +import { FakeAccountService, mockAccountServiceWith } from "../../../spec"; +import { CsprngArray } from "../../types/csprng"; +import { UserId } from "../../types/guid"; +import { UserKey } from "../../types/key"; +import { LogService } from "../abstractions/log.service"; +import { KeySuffixOptions } from "../enums"; +import { Utils } from "../misc/utils"; +import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; + +import { CryptoService } from "./crypto.service"; +import { UserKeyInitService } from "./user-key-init.service"; + +describe("UserKeyInitService", () => { + let userKeyInitService: UserKeyInitService; + + const mockUserId = Utils.newGuid() as UserId; + + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + + const cryptoService = mock(); + const logService = mock(); + + beforeEach(() => { + userKeyInitService = new UserKeyInitService(accountService, cryptoService, logService); + }); + + describe("listenForActiveUserChangesToSetUserKey()", () => { + it("calls setUserKeyInMemoryIfAutoUserKeySet if there is an active user", () => { + // Arrange + accountService.activeAccountSubject.next({ + id: mockUserId, + name: "name", + email: "email", + }); + + (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet = jest.fn(); + + // Act + + const subscription = userKeyInitService.listenForActiveUserChangesToSetUserKey(); + + // Assert + + expect(subscription).not.toBeFalsy(); + + expect((userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet).toHaveBeenCalledWith( + mockUserId, + ); + }); + + it("calls setUserKeyInMemoryIfAutoUserKeySet if there is an active user and tracks subsequent emissions", () => { + // Arrange + accountService.activeAccountSubject.next({ + id: mockUserId, + name: "name", + email: "email", + }); + + const mockUser2Id = Utils.newGuid() as UserId; + + jest + .spyOn(userKeyInitService as any, "setUserKeyInMemoryIfAutoUserKeySet") + .mockImplementation(() => Promise.resolve()); + + // Act + + const subscription = userKeyInitService.listenForActiveUserChangesToSetUserKey(); + + accountService.activeAccountSubject.next({ + id: mockUser2Id, + name: "name", + email: "email", + }); + + // Assert + + expect(subscription).not.toBeFalsy(); + + expect((userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet).toHaveBeenCalledTimes( + 2, + ); + + expect( + (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet, + ).toHaveBeenNthCalledWith(1, mockUserId); + expect( + (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet, + ).toHaveBeenNthCalledWith(2, mockUser2Id); + + subscription.unsubscribe(); + }); + + it("does not call setUserKeyInMemoryIfAutoUserKeySet if there is not an active user", () => { + // Arrange + accountService.activeAccountSubject.next(null); + + (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet = jest.fn(); + + // Act + + const subscription = userKeyInitService.listenForActiveUserChangesToSetUserKey(); + + // Assert + + expect(subscription).not.toBeFalsy(); + + expect( + (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet, + ).not.toHaveBeenCalledWith(mockUserId); + }); + }); + + describe("setUserKeyInMemoryIfAutoUserKeySet", () => { + it("does nothing if the userId is null", async () => { + // Act + await (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet(null); + + // Assert + expect(cryptoService.getUserKeyFromStorage).not.toHaveBeenCalled(); + expect(cryptoService.setUserKey).not.toHaveBeenCalled(); + }); + + it("does nothing if the autoUserKey is null", async () => { + // Arrange + const userId = mockUserId; + + cryptoService.getUserKeyFromStorage.mockResolvedValue(null); + + // Act + await (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet(userId); + + // Assert + expect(cryptoService.getUserKeyFromStorage).toHaveBeenCalledWith( + KeySuffixOptions.Auto, + userId, + ); + expect(cryptoService.setUserKey).not.toHaveBeenCalled(); + }); + + it("sets the user key in memory if the autoUserKey is not null", async () => { + // Arrange + const userId = mockUserId; + + const mockRandomBytes = new Uint8Array(64) as CsprngArray; + const mockAutoUserKey: UserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey; + + cryptoService.getUserKeyFromStorage.mockResolvedValue(mockAutoUserKey); + + // Act + await (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet(userId); + + // Assert + expect(cryptoService.getUserKeyFromStorage).toHaveBeenCalledWith( + KeySuffixOptions.Auto, + userId, + ); + expect(cryptoService.setUserKey).toHaveBeenCalledWith(mockAutoUserKey, userId); + }); + }); +}); diff --git a/libs/common/src/platform/services/user-key-init.service.ts b/libs/common/src/platform/services/user-key-init.service.ts new file mode 100644 index 0000000000..1f6aacce8f --- /dev/null +++ b/libs/common/src/platform/services/user-key-init.service.ts @@ -0,0 +1,57 @@ +import { EMPTY, Subscription, catchError, filter, from, switchMap } from "rxjs"; + +import { AccountService } from "../../auth/abstractions/account.service"; +import { UserId } from "../../types/guid"; +import { CryptoService } from "../abstractions/crypto.service"; +import { LogService } from "../abstractions/log.service"; +import { KeySuffixOptions } from "../enums"; + +// TODO: this is a half measure improvement which allows us to reduce some side effects today (cryptoService.getUserKey setting user key in memory if auto key exists) +// but ideally, in the future, we would be able to put this logic into the cryptoService +// after the vault timeout settings service is transitioned to state provider so that +// the getUserKey logic can simply go to the correct location based on the vault timeout settings +// similar to the TokenService (it would either go to secure storage for the auto user key or memory for the user key) + +export class UserKeyInitService { + constructor( + private accountService: AccountService, + private cryptoService: CryptoService, + private logService: LogService, + ) {} + + // Note: must listen for changes to support account switching + listenForActiveUserChangesToSetUserKey(): Subscription { + return this.accountService.activeAccount$ + .pipe( + filter((activeAccount) => activeAccount != null), + switchMap((activeAccount) => + from(this.setUserKeyInMemoryIfAutoUserKeySet(activeAccount?.id)).pipe( + catchError((err: unknown) => { + this.logService.warning( + `setUserKeyInMemoryIfAutoUserKeySet failed with error: ${err}`, + ); + // Returning EMPTY to protect observable chain from cancellation in case of error + return EMPTY; + }), + ), + ), + ) + .subscribe(); + } + + private async setUserKeyInMemoryIfAutoUserKeySet(userId: UserId) { + if (userId == null) { + return; + } + + const autoUserKey = await this.cryptoService.getUserKeyFromStorage( + KeySuffixOptions.Auto, + userId, + ); + if (autoUserKey == null) { + return; + } + + await this.cryptoService.setUserKey(autoUserKey, userId); + } +} 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 a8afc63297..4fac3be9c9 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 @@ -172,7 +172,6 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA } async clear(userId?: string): Promise { - await this.stateService.setEverBeenUnlocked(false, { userId: userId }); await this.cryptoService.clearPinKeys(userId); } diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts index 243b644dd8..5344093a25 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts @@ -174,7 +174,6 @@ describe("VaultTimeoutService", () => { // This does NOT assert all the things that the lock process does expect(stateService.getIsAuthenticated).toHaveBeenCalledWith({ userId: userId }); expect(vaultTimeoutSettingsService.availableVaultTimeoutActions$).toHaveBeenCalledWith(userId); - expect(stateService.setEverBeenUnlocked).toHaveBeenCalledWith(true, { userId: userId }); expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, { userId: userId }); expect(masterPasswordService.mock.clearMasterKey).toHaveBeenCalledWith(userId); expect(cipherService.clearCache).toHaveBeenCalledWith(userId); diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.ts index 35faf0fcee..8baf6c04c4 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.ts @@ -98,7 +98,6 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { await this.masterPasswordService.clearMasterKey((userId ?? currentUserId) as UserId); - await this.stateService.setEverBeenUnlocked(true, { userId: userId }); await this.stateService.setUserKeyAutoUnlock(null, { userId: userId }); await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId }); From 1e67014158267477adb8cb563be49189cb7624be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Fri, 19 Apr 2024 13:12:17 -0400 Subject: [PATCH 43/86] fix update loop when overwriting state from buffer (#8834) --- .../state/buffered-key-definition.ts | 6 +- .../generator/state/buffered-state.spec.ts | 26 +++--- .../tools/generator/state/buffered-state.ts | 87 ++++++++----------- 3 files changed, 57 insertions(+), 62 deletions(-) diff --git a/libs/common/src/tools/generator/state/buffered-key-definition.ts b/libs/common/src/tools/generator/state/buffered-key-definition.ts index 5457410f80..1f11280839 100644 --- a/libs/common/src/tools/generator/state/buffered-key-definition.ts +++ b/libs/common/src/tools/generator/state/buffered-key-definition.ts @@ -87,9 +87,13 @@ export class BufferedKeyDefinition { } /** Checks whether the input type can be converted to the output type. - * @returns `true` if the definition is valid, otherwise `false`. + * @returns `true` if the definition is defined and valid, otherwise `false`. */ isValid(input: Input, dependency: Dependency) { + if (input === null) { + return Promise.resolve(false); + } + const isValid = this.options?.isValid; if (isValid) { return isValid(input, dependency); diff --git a/libs/common/src/tools/generator/state/buffered-state.spec.ts b/libs/common/src/tools/generator/state/buffered-state.spec.ts index 7f9722d384..46e132c1bd 100644 --- a/libs/common/src/tools/generator/state/buffered-state.spec.ts +++ b/libs/common/src/tools/generator/state/buffered-state.spec.ts @@ -75,14 +75,16 @@ describe("BufferedState", () => { it("rolls over pending values from the buffered state immediately by default", async () => { const provider = new FakeStateProvider(accountService); const outputState = provider.getUser(SomeUser, SOME_KEY); - await outputState.update(() => ({ foo: true, bar: false })); + const initialValue = { foo: true, bar: false }; + await outputState.update(() => initialValue); const bufferedState = new BufferedState(provider, BUFFER_KEY, outputState); const bufferedValue = { foo: true, bar: true }; await provider.setUserState(BUFFER_KEY.toKeyDefinition(), bufferedValue, SomeUser); - const result = await firstValueFrom(bufferedState.state$); + const result = await trackEmissions(bufferedState.state$); + await awaitAsync(); - expect(result).toEqual(bufferedValue); + expect(result).toEqual([initialValue, bufferedValue]); }); // also important for data migrations @@ -131,14 +133,16 @@ describe("BufferedState", () => { }); const provider = new FakeStateProvider(accountService); const outputState = provider.getUser(SomeUser, SOME_KEY); - await outputState.update(() => ({ foo: true, bar: false })); + const initialValue = { foo: true, bar: false }; + await outputState.update(() => initialValue); const bufferedState = new BufferedState(provider, bufferedKey, outputState); const bufferedValue = { foo: true, bar: true }; await provider.setUserState(bufferedKey.toKeyDefinition(), bufferedValue, SomeUser); - const result = await firstValueFrom(bufferedState.state$); + const result = await trackEmissions(bufferedState.state$); + await awaitAsync(); - expect(result).toEqual(bufferedValue); + expect(result).toEqual([initialValue, bufferedValue]); }); it("reads from the output state when shouldOverwrite returns a falsy value", async () => { @@ -274,7 +278,7 @@ describe("BufferedState", () => { await bufferedState.buffer(bufferedValue); await awaitAsync(); - expect(result).toEqual([firstValue, firstValue]); + expect(result).toEqual([firstValue]); }); it("replaces the output state when its dependency becomes true", async () => { @@ -296,7 +300,7 @@ describe("BufferedState", () => { dependency.next(true); await awaitAsync(); - expect(result).toEqual([firstValue, firstValue, bufferedValue]); + expect(result).toEqual([firstValue, bufferedValue]); }); it.each([[null], [undefined]])("ignores `%p`", async (bufferedValue) => { @@ -325,11 +329,13 @@ describe("BufferedState", () => { await outputState.update(() => firstValue); const bufferedState = new BufferedState(provider, bufferedKey, outputState); - const result = trackEmissions(bufferedState.state$); + const stateResult = trackEmissions(bufferedState.state$); await bufferedState.buffer({ foo: true, bar: true }); await awaitAsync(); + const bufferedResult = await firstValueFrom(bufferedState.bufferedState$); - expect(result).toEqual([firstValue, firstValue]); + expect(stateResult).toEqual([firstValue]); + expect(bufferedResult).toBeNull(); }); it("overwrites the output when isValid returns true", async () => { diff --git a/libs/common/src/tools/generator/state/buffered-state.ts b/libs/common/src/tools/generator/state/buffered-state.ts index 42b14b815c..bb4de645e9 100644 --- a/libs/common/src/tools/generator/state/buffered-state.ts +++ b/libs/common/src/tools/generator/state/buffered-state.ts @@ -1,4 +1,4 @@ -import { Observable, combineLatest, concatMap, filter, map, of } from "rxjs"; +import { Observable, combineLatest, concatMap, filter, map, of, concat, merge } from "rxjs"; import { StateProvider, @@ -33,68 +33,53 @@ export class BufferedState implements SingleUserState private output: SingleUserState, dependency$: Observable = null, ) { - this.bufferState = provider.getUser(output.userId, key.toKeyDefinition()); + this.bufferedState = provider.getUser(output.userId, key.toKeyDefinition()); - const watching = [ - this.bufferState.state$, - this.output.state$, - dependency$ ?? of(true as unknown as Dependency), - ] as const; - - this.state$ = combineLatest(watching).pipe( - concatMap(async ([input, output, dependency]) => { - const normalized = input ?? null; - - const canOverwrite = normalized !== null && key.shouldOverwrite(dependency); - if (canOverwrite) { - await this.updateOutput(dependency); - - // prevent duplicate updates by suppressing the update - return [false, output] as const; + // overwrite the output value + const hasValue$ = concat(of(null), this.bufferedState.state$).pipe( + map((buffer) => (buffer ?? null) !== null), + ); + const overwriteDependency$ = (dependency$ ?? of(true as unknown as Dependency)).pipe( + map((dependency) => [key.shouldOverwrite(dependency), dependency] as const), + ); + const overwrite$ = combineLatest([hasValue$, overwriteDependency$]).pipe( + concatMap(async ([hasValue, [shouldOverwrite, dependency]]) => { + if (hasValue && shouldOverwrite) { + await this.overwriteOutput(dependency); } - - return [true, output] as const; + return [false, null] as const; }), - filter(([updated]) => updated), + ); + + // drive overwrites only when there's a subscription; + // the output state determines when emissions occur + const output$ = this.output.state$.pipe(map((output) => [true, output] as const)); + this.state$ = merge(overwrite$, output$).pipe( + filter(([emit]) => emit), map(([, output]) => output), ); this.combinedState$ = this.state$.pipe(map((state) => [this.output.userId, state])); - this.bufferState$ = this.bufferState.state$; + this.bufferedState$ = this.bufferedState.state$; } - private bufferState: SingleUserState; + private bufferedState: SingleUserState; - private async updateOutput(dependency: Dependency) { - // retrieve the latest input value - let input: Input; - await this.bufferState.update((state) => state, { - shouldUpdate: (state) => { - input = state; - return false; - }, + private async overwriteOutput(dependency: Dependency) { + // take the latest value from the buffer + let buffered: Input; + await this.bufferedState.update((state) => { + buffered = state ?? null; + return null; }); - // bail if this update lost the race with the last update - if (input === null) { - return; + // update the output state + const isValid = await this.key.isValid(buffered, dependency); + if (isValid) { + const output = await this.key.map(buffered, dependency); + await this.output.update(() => output); } - - // destroy invalid data and bail - if (!(await this.key.isValid(input, dependency))) { - await this.bufferState.update(() => null); - return; - } - - // overwrite anything left to the output; the updates need to be awaited with `Promise.all` - // so that `inputState.update(() => null)` runs before `shouldUpdate` reads the value (above). - // This lets the emission from `this.outputState.update` renter the `concatMap`. If the - // awaits run in sequence, it can win the race and cause a double emission. - const output = await this.key.map(input, dependency); - await Promise.all([this.output.update(() => output), this.bufferState.update(() => null)]); - - return; } /** {@link SingleUserState.userId} */ @@ -119,14 +104,14 @@ export class BufferedState implements SingleUserState async buffer(value: Input): Promise { const normalized = value ?? null; if (normalized !== null) { - await this.bufferState.update(() => normalized); + await this.bufferedState.update(() => normalized); } } /** The data presently being buffered. This emits the pending value each time * new buffer data is provided. It emits null when the buffer is empty. */ - readonly bufferState$: Observable; + readonly bufferedState$: Observable; /** Updates the output state. * @param configureState a callback that returns an updated output From ddee74fdee4d79541bd4404a72ab25efa5cb51d2 Mon Sep 17 00:00:00 2001 From: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:15:30 -0400 Subject: [PATCH 44/86] Removed 2023 plans for view for grandfathered 2020 providers (#8804) --- .../organizations/organization-plans.component.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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 30691ce87d..645b1f29ac 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -48,11 +48,7 @@ interface OnSuccessArgs { organizationId: string; } -const AllowedLegacyPlanTypes = [ - PlanType.TeamsMonthly2023, - PlanType.TeamsAnnually2023, - PlanType.EnterpriseAnnually2023, - PlanType.EnterpriseMonthly2023, +const Allowed2020PlansForLegacyProviders = [ PlanType.TeamsMonthly2020, PlanType.TeamsAnnually2020, PlanType.EnterpriseAnnually2020, @@ -283,7 +279,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { (!this.currentPlan || this.currentPlan.upgradeSortOrder < plan.upgradeSortOrder) && (!this.hasProvider || plan.product !== ProductType.TeamsStarter) && ((!this.isProviderQualifiedFor2020Plan() && this.planIsEnabled(plan)) || - (this.isProviderQualifiedFor2020Plan() && AllowedLegacyPlanTypes.includes(plan.type))), + (this.isProviderQualifiedFor2020Plan() && + Allowed2020PlansForLegacyProviders.includes(plan.type))), ); result.sort((planA, planB) => planA.displaySortOrder - planB.displaySortOrder); @@ -298,7 +295,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { (plan) => plan.product === selectedProductType && ((!this.isProviderQualifiedFor2020Plan() && this.planIsEnabled(plan)) || - (this.isProviderQualifiedFor2020Plan() && AllowedLegacyPlanTypes.includes(plan.type))), + (this.isProviderQualifiedFor2020Plan() && + Allowed2020PlansForLegacyProviders.includes(plan.type))), ) || []; result.sort((planA, planB) => planA.displaySortOrder - planB.displaySortOrder); From d55d240b1886c7a6b26e65a09468e8c3fe7f6d8f Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Fri, 19 Apr 2024 13:23:42 -0400 Subject: [PATCH 45/86] Update host permission to all urls (#8831) Discussions on this permission here: https://github.com/bitwarden/clients/pull/5985 --- apps/browser/src/manifest.v3.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 6c58b405f4..cdd0869fc5 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -64,7 +64,7 @@ "offscreen" ], "optional_permissions": ["nativeMessaging", "privacy"], - "host_permissions": ["*://*/*"], + "host_permissions": [""], "content_security_policy": { "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'", "sandbox": "sandbox allow-scripts; script-src 'self'" From c1bbf675e2545173704be7e6cd1d6a3b57dc985e Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:29:50 -0700 Subject: [PATCH 46/86] Update number of translations and give credit to our translators (#8835) --- apps/browser/store/locales/en/copy.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/store/locales/en/copy.resx b/apps/browser/store/locales/en/copy.resx index df8d63835c..82e4eb1d88 100644 --- a/apps/browser/store/locales/en/copy.resx +++ b/apps/browser/store/locales/en/copy.resx @@ -159,7 +159,7 @@ Built-in Generator Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. Global Translations -Bitwarden translations exist for more than 50 languages. +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. From 9a4279c8bbb50a9c68503621d0b61811afdc2716 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 17:41:41 +0000 Subject: [PATCH 47/86] Autosync the updated translations (#8836) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 3 --- apps/desktop/src/locales/ar/messages.json | 3 --- apps/desktop/src/locales/az/messages.json | 3 --- apps/desktop/src/locales/be/messages.json | 3 --- apps/desktop/src/locales/bg/messages.json | 7 ++----- apps/desktop/src/locales/bn/messages.json | 3 --- apps/desktop/src/locales/bs/messages.json | 3 --- apps/desktop/src/locales/ca/messages.json | 3 --- apps/desktop/src/locales/cs/messages.json | 3 --- apps/desktop/src/locales/cy/messages.json | 3 --- apps/desktop/src/locales/da/messages.json | 3 --- apps/desktop/src/locales/de/messages.json | 3 --- apps/desktop/src/locales/el/messages.json | 3 --- apps/desktop/src/locales/en_GB/messages.json | 3 --- apps/desktop/src/locales/en_IN/messages.json | 3 --- apps/desktop/src/locales/eo/messages.json | 3 --- apps/desktop/src/locales/es/messages.json | 3 --- apps/desktop/src/locales/et/messages.json | 3 --- apps/desktop/src/locales/eu/messages.json | 3 --- apps/desktop/src/locales/fa/messages.json | 3 --- apps/desktop/src/locales/fi/messages.json | 11 ++++------- apps/desktop/src/locales/fil/messages.json | 3 --- apps/desktop/src/locales/fr/messages.json | 3 --- apps/desktop/src/locales/gl/messages.json | 3 --- apps/desktop/src/locales/he/messages.json | 3 --- apps/desktop/src/locales/hi/messages.json | 3 --- apps/desktop/src/locales/hr/messages.json | 3 --- apps/desktop/src/locales/hu/messages.json | 7 ++----- apps/desktop/src/locales/id/messages.json | 13 +++++-------- apps/desktop/src/locales/it/messages.json | 7 ++----- apps/desktop/src/locales/ja/messages.json | 3 --- apps/desktop/src/locales/ka/messages.json | 3 --- apps/desktop/src/locales/km/messages.json | 3 --- apps/desktop/src/locales/kn/messages.json | 3 --- apps/desktop/src/locales/ko/messages.json | 3 --- apps/desktop/src/locales/lt/messages.json | 3 --- apps/desktop/src/locales/lv/messages.json | 7 ++----- apps/desktop/src/locales/me/messages.json | 3 --- apps/desktop/src/locales/ml/messages.json | 3 --- apps/desktop/src/locales/mr/messages.json | 3 --- apps/desktop/src/locales/my/messages.json | 3 --- apps/desktop/src/locales/nb/messages.json | 3 --- apps/desktop/src/locales/ne/messages.json | 3 --- apps/desktop/src/locales/nl/messages.json | 3 --- apps/desktop/src/locales/nn/messages.json | 3 --- apps/desktop/src/locales/or/messages.json | 3 --- apps/desktop/src/locales/pl/messages.json | 3 --- apps/desktop/src/locales/pt_BR/messages.json | 3 --- apps/desktop/src/locales/pt_PT/messages.json | 3 --- apps/desktop/src/locales/ro/messages.json | 3 --- apps/desktop/src/locales/ru/messages.json | 3 --- apps/desktop/src/locales/si/messages.json | 3 --- apps/desktop/src/locales/sk/messages.json | 3 --- apps/desktop/src/locales/sl/messages.json | 3 --- apps/desktop/src/locales/sr/messages.json | 3 --- apps/desktop/src/locales/sv/messages.json | 3 --- apps/desktop/src/locales/te/messages.json | 3 --- apps/desktop/src/locales/th/messages.json | 3 --- apps/desktop/src/locales/tr/messages.json | 3 --- apps/desktop/src/locales/uk/messages.json | 3 --- apps/desktop/src/locales/vi/messages.json | 3 --- apps/desktop/src/locales/zh_CN/messages.json | 11 ++++------- apps/desktop/src/locales/zh_TW/messages.json | 3 --- 63 files changed, 21 insertions(+), 210 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 97067b788a..afdfc90d76 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 2d25269fff..7869b0894b 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -2697,9 +2697,6 @@ "message": "تنسيقات مشتركة", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 1ecd18eee7..d4cea4f06e 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -2697,9 +2697,6 @@ "message": "Ortaq formatlar", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Uğurlu" - }, "troubleshooting": { "message": "Problemlərin aradan qaldırılması" }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index e0133e5a74..53e3ec2d12 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 20f47d4bcd..f4886c420f 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1633,10 +1633,10 @@ "message": "Интеграцията с браузър не се поддържа" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Грешка при включването на интеграцията с браузъра" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Възникна грешка при включването на интеграцията с браузъра." }, "browserIntegrationMasOnlyDesc": { "message": "За жалост в момента интеграцията с браузър не се поддържа във версията за магазина на Mac." @@ -2697,9 +2697,6 @@ "message": "Често използвани формати", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Отстраняване на проблеми" }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 626734ebff..abd2c1cfae 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 9d5685cca9..825bd6344e 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index d8c0f32948..6c48d6cb0b 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -2697,9 +2697,6 @@ "message": "Formats comuns", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Resolució de problemes" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index e68fe8fffc..550b10a31c 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -2697,9 +2697,6 @@ "message": "Společné formáty", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Úspěch" - }, "troubleshooting": { "message": "Řešení problémů" }, diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 62f2e608bb..b1cc9e63d3 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index 0e578a6f66..f2a84a3c29 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -2697,9 +2697,6 @@ "message": "Almindelige formater", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Gennemført" - }, "troubleshooting": { "message": "Fejlfinding" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index bba1ccec15..e5e3945abc 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -2697,9 +2697,6 @@ "message": "Gängigste Formate", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Problembehandlung" }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 87360c33ce..41e6a62a2f 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -2697,9 +2697,6 @@ "message": "Κοινές μορφές", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Αντιμετώπιση Προβλημάτων" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 5c8c32b7c1..2658610df3 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index abfa0b1c0d..0542da9ddc 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 427f08f805..1c4cc4f0be 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index f7df93bdd7..ec5da44293 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 02cd737baa..3850cc1d85 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 2067b2dcc2..b21108b6ad 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index ef34f8222a..08356d410d 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -2697,9 +2697,6 @@ "message": "فرمت‌های رایج", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 03059f7ef3..fca24c197a 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1633,10 +1633,10 @@ "message": "Selainintegraatiota ei tueta" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Virhe otettaessa selainintegrointia käyttöön" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Otettaessa selainintegraatiota käyttöön tapahtui virhe." }, "browserIntegrationMasOnlyDesc": { "message": "Valitettavasti selainintegraatiota tuetaan toistaiseksi vain Mac App Store -versiossa." @@ -2697,9 +2697,6 @@ "message": "Yleiset muodot", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Vianetsintä" }, @@ -2716,9 +2713,9 @@ "message": "Suojausavain poistettiin" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Virhe määritettäessä kohdekokoelmaa." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Virhe määritettäessä kohdekansiota." } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index d28a4b568c..6d5f85fca8 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 86550b736f..1097624b14 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -2697,9 +2697,6 @@ "message": "Formats communs", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Résolution de problèmes" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 889a2beeee..90648699c0 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 3b155ffdf3..73599c012e 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -2697,9 +2697,6 @@ "message": "תסדירים נפוצים", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index af28c66681..4f8bb9b4bb 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 01983d5891..220c8bfab2 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index ecf77e2f34..149d48284e 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1633,10 +1633,10 @@ "message": "A böngésző integráció nem támogatott." }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Böngésző integráció engedélyezése" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Hiba történt a böngésző integrációjának engedélyezése közben." }, "browserIntegrationMasOnlyDesc": { "message": "Sajnos a böngésző integrációt egyelőre csak a Mac App Store verzió támogatja." @@ -2697,9 +2697,6 @@ "message": "Általános formátumok", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Hibaelhárítás" }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index cd36126b05..3194b0f7d3 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -404,7 +404,7 @@ "message": "Panjang" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "Panjang kata sandi minimum" }, "uppercase": { "message": "Huruf Kapital (A-Z)" @@ -545,7 +545,7 @@ "message": "Diperlukan pengetikan ulang kata sandi utama." }, "masterPasswordMinlength": { - "message": "Master password must be at least $VALUE$ characters long.", + "message": "Kata sandi utama minimal harus $VALUE$ karakter.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -561,7 +561,7 @@ "message": "Akun baru Anda telah dibuat! Sekarang Anda bisa masuk." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Anda berhasil masuk" }, "youMayCloseThisWindow": { "message": "You may close this window" @@ -801,10 +801,10 @@ "message": "Ubah Kata Sandi Utama" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Lanjutkan ke aplikasi web?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Anda bisa mengganti kata sandi utama Anda di aplikasi web Bitwarden." }, "fingerprintPhrase": { "message": "Frase Fingerprint", @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 0eeea259e2..08ae2d9da8 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -1633,10 +1633,10 @@ "message": "L'integrazione del browser non è supportata" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Errore durante l'attivazione dell'integrazione del browser" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Si è verificato un errore durante l'attivazione dell'integrazione del browser." }, "browserIntegrationMasOnlyDesc": { "message": "Purtroppo l'integrazione del browser è supportata solo nella versione nell'App Store per ora." @@ -2697,9 +2697,6 @@ "message": "Formati comuni", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Risoluzione problemi" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index ab6c0be95f..b07eab20cc 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -2697,9 +2697,6 @@ "message": "一般的な形式", "description": "Label indicating the most common import formats" }, - "success": { - "message": "成功" - }, "troubleshooting": { "message": "トラブルシューティング" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 889a2beeee..90648699c0 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 889a2beeee..90648699c0 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index eb0cbcf6be..162cba3a75 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 8e50ade96c..09b1767af0 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index e9de697005..de77aa8fbf 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -2697,9 +2697,6 @@ "message": "Dažni formatai", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 0227a8c524..521a0afcf2 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1633,10 +1633,10 @@ "message": "Sasaistīšana ar pārlūku nav atbalstīta" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Kļūda pārlūga saistīšanas iespējošanā" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Atgadījās kļūda pārlūka saistīšanas iespējošanas laikā." }, "browserIntegrationMasOnlyDesc": { "message": "Diemžēl sasaistīšāna ar pārlūku pagaidām ir nodrošināta tikai Mac App Store laidienā." @@ -2697,9 +2697,6 @@ "message": "Izplatīti veidoli", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Sarežģījumu novēršana" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 1f49961b46..ed458379b8 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 96811b9dba..b94b9d1b79 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 889a2beeee..90648699c0 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 0ee0db69ef..5142c8e61f 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 7bf132bdac..e190cfc236 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -2697,9 +2697,6 @@ "message": "Vanlige formater", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 13e1466805..bd58a18b0d 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index b5f2a413d6..3ca0730710 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -2697,9 +2697,6 @@ "message": "Veelvoorkomende formaten", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Probleemoplossing" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 35e7173d74..12e11b32c1 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index cd83d2ea69..7363558551 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 250c557309..df7a158a3a 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -2697,9 +2697,6 @@ "message": "Popularne formaty", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Sukces" - }, "troubleshooting": { "message": "Rozwiązywanie problemów" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 9a79ad665e..f651e0b060 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -2697,9 +2697,6 @@ "message": "Formatos comuns", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 14f0ec5d2f..4e58dad2cf 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -2697,9 +2697,6 @@ "message": "Formatos comuns", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Com sucesso" - }, "troubleshooting": { "message": "Resolução de problemas" }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 978f57eb9b..3fe73f28a7 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index c9b3b95b39..cc182812a6 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -2697,9 +2697,6 @@ "message": "Основные форматы", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Успешно" - }, "troubleshooting": { "message": "Устранение проблем" }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 3d43997144..261ae1c9b8 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 6499486b9d..6ef52a83ee 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -2697,9 +2697,6 @@ "message": "Bežné formáty", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Úspech" - }, "troubleshooting": { "message": "Riešenie problémov" }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 8cb06dcf0c..8c9c158c87 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 37c5dfa382..edafdb55a2 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -2697,9 +2697,6 @@ "message": "Уобичајени формати", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Решавање проблема" }, diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index bd21c0f328..6342424fd4 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -2697,9 +2697,6 @@ "message": "Vanliga format", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Felsökning" }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 889a2beeee..90648699c0 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index f1cd5351f7..5b6a1b9d0b 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 3e7229c41b..42a8f207c7 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Sorun giderme" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 9ee7652093..ac7b7c3243 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -2697,9 +2697,6 @@ "message": "Поширені формати", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Успішно" - }, "troubleshooting": { "message": "Усунення проблем" }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 0c0e6f6df7..aac9995db1 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -2697,9 +2697,6 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 3819cb967c..0560466cf8 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1633,10 +1633,10 @@ "message": "不支持浏览器集成" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "启用浏览器集成时出错" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "启用浏览器集成时出错。" }, "browserIntegrationMasOnlyDesc": { "message": "很遗憾,目前仅 Mac App Store 版本支持浏览器集成。" @@ -2697,9 +2697,6 @@ "message": "常规格式", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "故障排除" }, @@ -2716,9 +2713,9 @@ "message": "通行密钥已移除" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "分配目标集合时出错。" }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "分配目标文件夹时出错。" } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 5f768b0a43..9eb12e23cf 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -2697,9 +2697,6 @@ "message": "常見格式", "description": "Label indicating the most common import formats" }, - "success": { - "message": "Success" - }, "troubleshooting": { "message": "疑難排解" }, From 395ed3f5d464355d448204d944eee51094bdc28c Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Fri, 19 Apr 2024 14:02:40 -0500 Subject: [PATCH 48/86] [PM-7489] Introduce `MessageSender` & `MessageListener` (#8709) * Introduce MessageSender * Update `messageSenderFactory` * Remove Comment * Use BrowserApi * Update Comment * Rename to CommandDefinition * Add More Documentation to MessageSender * Add `EMPTY` helpers and remove NoopMessageSender * Calm Down Logging * Limit Logging On Known Errors * Use `messageStream` Parameter Co-authored-by: Matt Gibson * Add eslint rules * Update Error Handling Co-authored-by: Cesar Gonzalez * Delete Lazy Classes In Favor of Observable Factories * Remove Fido Messages --------- Co-authored-by: Matt Gibson Co-authored-by: Cesar Gonzalez --- .eslintrc.json | 16 ++ .../browser/src/background/main.background.ts | 54 +++--- .../background/nativeMessaging.background.ts | 2 +- .../src/background/runtime.background.ts | 156 ++++++++++-------- .../message-sender.factory.ts | 17 ++ .../messaging-service.factory.ts | 24 +-- .../messaging/chrome-message.sender.ts | 37 +++++ ...ssaging-private-mode-background.service.ts | 8 - ...er-messaging-private-mode-popup.service.ts | 8 - .../services/browser-messaging.service.ts | 9 - .../utils/from-chrome-runtime-messaging.ts | 26 +++ apps/browser/src/popup/app.component.ts | 145 ++++++++-------- .../src/popup/services/services.module.ts | 81 +++++++-- apps/cli/src/bw.ts | 10 +- .../src/app/services/services.module.ts | 30 +++- apps/desktop/src/main.ts | 18 +- apps/desktop/src/main/power-monitor.main.ts | 5 +- apps/desktop/src/platform/preload.ts | 21 ++- .../electron-renderer-message.sender.ts | 12 ++ .../electron-renderer-messaging.service.ts | 20 --- .../src/platform/utils/from-ipc-messaging.ts | 15 ++ .../electron-main-messaging.service.ts | 17 +- .../trial-billing-step.component.ts | 3 +- .../organization-plans.component.ts | 3 +- .../app/core/broadcaster-messaging.service.ts | 14 -- apps/web/src/app/core/core.module.ts | 7 - .../vault/components/premium-badge.stories.ts | 10 +- .../platform/services/broadcaster.service.ts | 6 - libs/angular/src/services/injection-tokens.ts | 6 +- .../src/services/jslib-services.module.ts | 30 +++- .../abstractions/messaging.service.ts | 6 +- .../src/platform/messaging/helpers.spec.ts | 46 ++++++ libs/common/src/platform/messaging/helpers.ts | 23 +++ libs/common/src/platform/messaging/index.ts | 4 + .../common/src/platform/messaging/internal.ts | 5 + .../messaging/message.listener.spec.ts | 47 ++++++ .../platform/messaging/message.listener.ts | 41 +++++ .../src/platform/messaging/message.sender.ts | 62 +++++++ .../messaging/subject-message.sender.spec.ts | 65 ++++++++ .../messaging/subject-message.sender.ts | 17 ++ libs/common/src/platform/messaging/types.ts | 13 ++ .../platform/services/broadcaster.service.ts | 34 ---- .../services/default-broadcaster.service.ts | 36 ++++ .../services/noop-messaging.service.ts | 7 - 44 files changed, 855 insertions(+), 361 deletions(-) create mode 100644 apps/browser/src/platform/background/service-factories/message-sender.factory.ts create mode 100644 apps/browser/src/platform/messaging/chrome-message.sender.ts delete mode 100644 apps/browser/src/platform/services/browser-messaging-private-mode-background.service.ts delete mode 100644 apps/browser/src/platform/services/browser-messaging-private-mode-popup.service.ts delete mode 100644 apps/browser/src/platform/services/browser-messaging.service.ts create mode 100644 apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts create mode 100644 apps/desktop/src/platform/services/electron-renderer-message.sender.ts delete mode 100644 apps/desktop/src/platform/services/electron-renderer-messaging.service.ts create mode 100644 apps/desktop/src/platform/utils/from-ipc-messaging.ts delete mode 100644 apps/web/src/app/core/broadcaster-messaging.service.ts delete mode 100644 libs/angular/src/platform/services/broadcaster.service.ts create mode 100644 libs/common/src/platform/messaging/helpers.spec.ts create mode 100644 libs/common/src/platform/messaging/helpers.ts create mode 100644 libs/common/src/platform/messaging/index.ts create mode 100644 libs/common/src/platform/messaging/internal.ts create mode 100644 libs/common/src/platform/messaging/message.listener.spec.ts create mode 100644 libs/common/src/platform/messaging/message.listener.ts create mode 100644 libs/common/src/platform/messaging/message.sender.ts create mode 100644 libs/common/src/platform/messaging/subject-message.sender.spec.ts create mode 100644 libs/common/src/platform/messaging/subject-message.sender.ts create mode 100644 libs/common/src/platform/messaging/types.ts delete mode 100644 libs/common/src/platform/services/broadcaster.service.ts create mode 100644 libs/common/src/platform/services/default-broadcaster.service.ts delete mode 100644 libs/common/src/platform/services/noop-messaging.service.ts diff --git a/.eslintrc.json b/.eslintrc.json index 671e7b2fab..61bebbf483 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -246,6 +246,22 @@ } ] } + }, + { + "files": ["**/*.ts"], + "excludedFiles": ["**/platform/**/*.ts"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + "**/platform/**/internal", // General internal pattern + // All features that have been converted to barrel files + "**/platform/messaging/**" + ] + } + ] + } } ] } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 8432c398b7..f2003f9621 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1,4 +1,4 @@ -import { firstValueFrom } from "rxjs"; +import { Subject, firstValueFrom, merge } from "rxjs"; import { PinCryptoServiceAbstraction, @@ -82,7 +82,6 @@ import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/co import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { AbstractMemoryStorageService, @@ -95,6 +94,9 @@ import { DefaultBiometricStateService, } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; +import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; +// eslint-disable-next-line no-restricted-imports -- Used for dependency creation +import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { AppIdService } from "@bitwarden/common/platform/services/app-id.service"; import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; @@ -208,13 +210,14 @@ import { Account } from "../models/account"; import { BrowserApi } from "../platform/browser/browser-api"; import { flagEnabled } from "../platform/flags"; import { UpdateBadge } from "../platform/listeners/update-badge"; +/* eslint-disable no-restricted-imports */ +import { ChromeMessageSender } from "../platform/messaging/chrome-message.sender"; +/* eslint-enable no-restricted-imports */ import { BrowserStateService as StateServiceAbstraction } from "../platform/services/abstractions/browser-state.service"; import { BrowserCryptoService } from "../platform/services/browser-crypto.service"; import { BrowserEnvironmentService } from "../platform/services/browser-environment.service"; import BrowserLocalStorageService from "../platform/services/browser-local-storage.service"; import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service"; -import BrowserMessagingPrivateModeBackgroundService from "../platform/services/browser-messaging-private-mode-background.service"; -import BrowserMessagingService from "../platform/services/browser-messaging.service"; import { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service"; import { DefaultBrowserStateService } from "../platform/services/default-browser-state.service"; import I18nService from "../platform/services/i18n.service"; @@ -223,6 +226,7 @@ import { BackgroundPlatformUtilsService } from "../platform/services/platform-ut import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider"; import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service"; +import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; import FilelessImporterBackground from "../tools/background/fileless-importer.background"; import { Fido2Background as Fido2BackgroundAbstraction } from "../vault/fido2/background/abstractions/fido2.background"; @@ -236,7 +240,7 @@ import { NativeMessagingBackground } from "./nativeMessaging.background"; import RuntimeBackground from "./runtime.background"; export default class MainBackground { - messagingService: MessagingServiceAbstraction; + messagingService: MessageSender; storageService: BrowserLocalStorageService; secureStorageService: AbstractStorageService; memoryStorageService: AbstractMemoryStorageService; @@ -326,6 +330,8 @@ export default class MainBackground { stateEventRunnerService: StateEventRunnerService; ssoLoginService: SsoLoginServiceAbstraction; billingAccountProfileStateService: BillingAccountProfileStateService; + // eslint-disable-next-line rxjs/no-exposed-subjects -- Needed to give access to services module + intraprocessMessagingSubject: Subject>; userKeyInitService: UserKeyInitService; scriptInjectorService: BrowserScriptInjectorService; @@ -369,15 +375,25 @@ export default class MainBackground { const logoutCallback = async (expired: boolean, userId?: UserId) => await this.logout(expired, userId); - this.messagingService = - this.isPrivateMode && BrowserApi.isManifestVersion(2) - ? new BrowserMessagingPrivateModeBackgroundService() - : new BrowserMessagingService(); this.logService = new ConsoleLogService(false); this.cryptoFunctionService = new WebCryptoFunctionService(self); this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService); this.storageService = new BrowserLocalStorageService(); + this.intraprocessMessagingSubject = new Subject>(); + + this.messagingService = MessageSender.combine( + new SubjectMessageSender(this.intraprocessMessagingSubject), + new ChromeMessageSender(this.logService), + ); + + const messageListener = new MessageListener( + merge( + this.intraprocessMessagingSubject.asObservable(), // For messages from the same context + fromChromeRuntimeMessaging(), // For messages from other contexts + ), + ); + const mv3MemoryStorageCreator = (partitionName: string) => { // TODO: Consider using multithreaded encrypt service in popup only context return new LocalBackedSessionStorageService( @@ -560,21 +576,6 @@ export default class MainBackground { this.twoFactorService = new TwoFactorService(this.i18nService, this.platformUtilsService); - // eslint-disable-next-line - const that = this; - const backgroundMessagingService = new (class extends MessagingServiceAbstraction { - // AuthService should send the messages to the background not popup. - send = (subscriber: string, arg: any = {}) => { - if (BrowserApi.isManifestVersion(3)) { - that.messagingService.send(subscriber, arg); - return; - } - - const message = Object.assign({}, { command: subscriber }, arg); - void that.runtimeBackground.processMessage(message, that as any); - }; - })(); - this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); @@ -605,7 +606,7 @@ export default class MainBackground { this.authService = new AuthService( this.accountService, - backgroundMessagingService, + this.messagingService, this.cryptoService, this.apiService, this.stateService, @@ -626,7 +627,7 @@ export default class MainBackground { this.tokenService, this.appIdService, this.platformUtilsService, - backgroundMessagingService, + this.messagingService, this.logService, this.keyConnectorService, this.environmentService, @@ -914,6 +915,7 @@ export default class MainBackground { this.logService, this.configService, this.fido2Background, + messageListener, ); this.nativeMessagingBackground = new NativeMessagingBackground( this.accountService, diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index e5eed06c21..5ac9961147 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -399,7 +399,7 @@ export class NativeMessagingBackground { // 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.runtimeBackground.processMessage({ command: "unlocked" }, null); + this.runtimeBackground.processMessage({ command: "unlocked" }); } break; } diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 44fe4818e0..f457889e96 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -1,4 +1,4 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, mergeMap } from "rxjs"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; @@ -10,6 +10,7 @@ import { SystemService } from "@bitwarden/common/platform/abstractions/system.se import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { MessageListener } from "../../../../libs/common/src/platform/messaging"; import { closeUnlockPopout, openSsoAuthResultPopout, @@ -44,6 +45,7 @@ export default class RuntimeBackground { private logService: LogService, private configService: ConfigService, private fido2Background: Fido2Background, + private messageListener: MessageListener, ) { // onInstalled listener must be wired up before anything else, so we do it in the ctor chrome.runtime.onInstalled.addListener((details: any) => { @@ -60,92 +62,47 @@ export default class RuntimeBackground { const backgroundMessageListener = ( msg: any, sender: chrome.runtime.MessageSender, - sendResponse: any, + sendResponse: (response: any) => void, ) => { const messagesWithResponse = ["biometricUnlock"]; if (messagesWithResponse.includes(msg.command)) { - this.processMessage(msg, sender).then( + this.processMessageWithSender(msg, sender).then( (value) => sendResponse({ result: value }), (error) => sendResponse({ error: { ...error, message: error.message } }), ); return true; } - this.processMessage(msg, sender).catch((e) => this.logService.error(e)); + void this.processMessageWithSender(msg, sender).catch((err) => + this.logService.error( + `Error while processing message in RuntimeBackground '${msg?.command}'. Error: ${err?.message ?? "Unknown Error"}`, + ), + ); + return false; }; + this.messageListener.allMessages$ + .pipe( + mergeMap(async (message: any) => { + await this.processMessage(message); + }), + ) + .subscribe(); + + // For messages that require the full on message interface BrowserApi.messageListener("runtime.background", backgroundMessageListener); - if (this.main.popupOnlyContext) { - (self as any).bitwardenBackgroundMessageListener = backgroundMessageListener; - } } - async processMessage(msg: any, sender: chrome.runtime.MessageSender) { + // Messages that need the chrome sender and send back a response need to be registered in this method. + async processMessageWithSender(msg: any, sender: chrome.runtime.MessageSender) { switch (msg.command) { - case "loggedIn": - case "unlocked": { - let item: LockedVaultPendingNotificationsData; - - if (msg.command === "loggedIn") { - await this.sendBwInstalledMessageToVault(); - } - - if (this.lockedVaultPendingNotifications?.length > 0) { - item = this.lockedVaultPendingNotifications.pop(); - await closeUnlockPopout(); - } - - await this.notificationsService.updateConnection(msg.command === "loggedIn"); - await this.main.refreshBadge(); - await this.main.refreshMenu(false); - this.systemService.cancelProcessReload(); - - if (item) { - await BrowserApi.focusWindow(item.commandToRetry.sender.tab.windowId); - await BrowserApi.focusTab(item.commandToRetry.sender.tab.id); - await BrowserApi.tabSendMessageData( - item.commandToRetry.sender.tab, - "unlockCompleted", - item, - ); - } - break; - } - case "addToLockedVaultPendingNotifications": - this.lockedVaultPendingNotifications.push(msg.data); - break; - case "logout": - await this.main.logout(msg.expired, msg.userId); - break; - case "syncCompleted": - if (msg.successfully) { - setTimeout(async () => { - await this.main.refreshBadge(); - await this.main.refreshMenu(); - }, 2000); - await this.configService.ensureConfigFetched(); - } - break; - case "openPopup": - await this.main.openPopup(); - break; case "triggerAutofillScriptInjection": await this.autofillService.injectAutofillScripts(sender.tab, sender.frameId); break; case "bgCollectPageDetails": await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId); break; - case "bgUpdateContextMenu": - case "editedCipher": - case "addedCipher": - case "deletedCipher": - await this.main.refreshBadge(); - await this.main.refreshMenu(); - break; - case "bgReseedStorage": - await this.main.reseedStorage(); - break; case "collectPageDetailsResponse": switch (msg.sender) { case "autofiller": @@ -209,6 +166,72 @@ export default class RuntimeBackground { break; } break; + case "biometricUnlock": { + const result = await this.main.biometricUnlock(); + return result; + } + } + } + + async processMessage(msg: any) { + switch (msg.command) { + case "loggedIn": + case "unlocked": { + let item: LockedVaultPendingNotificationsData; + + if (msg.command === "loggedIn") { + await this.sendBwInstalledMessageToVault(); + } + + if (this.lockedVaultPendingNotifications?.length > 0) { + item = this.lockedVaultPendingNotifications.pop(); + await closeUnlockPopout(); + } + + await this.notificationsService.updateConnection(msg.command === "loggedIn"); + await this.main.refreshBadge(); + await this.main.refreshMenu(false); + this.systemService.cancelProcessReload(); + + if (item) { + await BrowserApi.focusWindow(item.commandToRetry.sender.tab.windowId); + await BrowserApi.focusTab(item.commandToRetry.sender.tab.id); + await BrowserApi.tabSendMessageData( + item.commandToRetry.sender.tab, + "unlockCompleted", + item, + ); + } + break; + } + case "addToLockedVaultPendingNotifications": + this.lockedVaultPendingNotifications.push(msg.data); + break; + case "logout": + await this.main.logout(msg.expired, msg.userId); + break; + case "syncCompleted": + if (msg.successfully) { + setTimeout(async () => { + await this.main.refreshBadge(); + await this.main.refreshMenu(); + }, 2000); + await this.configService.ensureConfigFetched(); + } + break; + case "openPopup": + await this.main.openPopup(); + break; + case "bgUpdateContextMenu": + case "editedCipher": + case "addedCipher": + case "deletedCipher": + await this.main.refreshBadge(); + await this.main.refreshMenu(); + break; + case "bgReseedStorage": + await this.main.reseedStorage(); + break; case "authResult": { const env = await firstValueFrom(this.environmentService.environment$); const vaultUrl = env.getWebVaultUrl(); @@ -265,9 +288,6 @@ export default class RuntimeBackground { await this.main.clearClipboard(msg.clipboardValue, msg.timeoutMs); break; } - case "biometricUnlock": { - return await this.main.biometricUnlock(); - } } } diff --git a/apps/browser/src/platform/background/service-factories/message-sender.factory.ts b/apps/browser/src/platform/background/service-factories/message-sender.factory.ts new file mode 100644 index 0000000000..6f50b4b8f5 --- /dev/null +++ b/apps/browser/src/platform/background/service-factories/message-sender.factory.ts @@ -0,0 +1,17 @@ +import { MessageSender } from "@bitwarden/common/platform/messaging"; + +import { CachedServices, factory, FactoryOptions } from "./factory-options"; + +type MessagingServiceFactoryOptions = FactoryOptions; + +export type MessageSenderInitOptions = MessagingServiceFactoryOptions; + +export function messageSenderFactory( + cache: { messagingService?: MessageSender } & CachedServices, + opts: MessageSenderInitOptions, +): Promise { + // NOTE: Name needs to match that of MainBackground property until we delete these. + return factory(cache, "messagingService", opts, () => { + throw new Error("Not implemented, not expected to be used."); + }); +} diff --git a/apps/browser/src/platform/background/service-factories/messaging-service.factory.ts b/apps/browser/src/platform/background/service-factories/messaging-service.factory.ts index 46852712aa..20c6e3f424 100644 --- a/apps/browser/src/platform/background/service-factories/messaging-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/messaging-service.factory.ts @@ -1,19 +1,5 @@ -import { MessagingService as AbstractMessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; - -import { - CachedServices, - factory, - FactoryOptions, -} from "../../background/service-factories/factory-options"; -import BrowserMessagingService from "../../services/browser-messaging.service"; - -type MessagingServiceFactoryOptions = FactoryOptions; - -export type MessagingServiceInitOptions = MessagingServiceFactoryOptions; - -export function messagingServiceFactory( - cache: { messagingService?: AbstractMessagingService } & CachedServices, - opts: MessagingServiceInitOptions, -): Promise { - return factory(cache, "messagingService", opts, () => new BrowserMessagingService()); -} +// Export old messaging service stuff to minimize changes +export { + messageSenderFactory as messagingServiceFactory, + MessageSenderInitOptions as MessagingServiceInitOptions, +} from "./message-sender.factory"; diff --git a/apps/browser/src/platform/messaging/chrome-message.sender.ts b/apps/browser/src/platform/messaging/chrome-message.sender.ts new file mode 100644 index 0000000000..0e57ecfb4e --- /dev/null +++ b/apps/browser/src/platform/messaging/chrome-message.sender.ts @@ -0,0 +1,37 @@ +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { CommandDefinition, MessageSender } from "@bitwarden/common/platform/messaging"; +import { getCommand } from "@bitwarden/common/platform/messaging/internal"; + +type ErrorHandler = (logger: LogService, command: string) => void; + +const HANDLED_ERRORS: Record = { + "Could not establish connection. Receiving end does not exist.": (logger, command) => + logger.debug(`Receiving end didn't exist for command '${command}'`), + + "The message port closed before a response was received.": (logger, command) => + logger.debug(`Port was closed for command '${command}'`), +}; + +export class ChromeMessageSender implements MessageSender { + constructor(private readonly logService: LogService) {} + + send( + commandDefinition: string | CommandDefinition, + payload: object | T = {}, + ): void { + const command = getCommand(commandDefinition); + chrome.runtime.sendMessage(Object.assign(payload, { command: command }), () => { + if (chrome.runtime.lastError) { + const errorHandler = HANDLED_ERRORS[chrome.runtime.lastError.message]; + if (errorHandler != null) { + errorHandler(this.logService, command); + return; + } + + this.logService.warning( + `Unhandled error while sending message with command '${command}': ${chrome.runtime.lastError.message}`, + ); + } + }); + } +} diff --git a/apps/browser/src/platform/services/browser-messaging-private-mode-background.service.ts b/apps/browser/src/platform/services/browser-messaging-private-mode-background.service.ts deleted file mode 100644 index 0c7008473b..0000000000 --- a/apps/browser/src/platform/services/browser-messaging-private-mode-background.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; - -export default class BrowserMessagingPrivateModeBackgroundService implements MessagingService { - send(subscriber: string, arg: any = {}) { - const message = Object.assign({}, { command: subscriber }, arg); - (self as any).bitwardenPopupMainMessageListener(message); - } -} diff --git a/apps/browser/src/platform/services/browser-messaging-private-mode-popup.service.ts b/apps/browser/src/platform/services/browser-messaging-private-mode-popup.service.ts deleted file mode 100644 index 5883f61197..0000000000 --- a/apps/browser/src/platform/services/browser-messaging-private-mode-popup.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; - -export default class BrowserMessagingPrivateModePopupService implements MessagingService { - send(subscriber: string, arg: any = {}) { - const message = Object.assign({}, { command: subscriber }, arg); - (self as any).bitwardenBackgroundMessageListener(message); - } -} diff --git a/apps/browser/src/platform/services/browser-messaging.service.ts b/apps/browser/src/platform/services/browser-messaging.service.ts deleted file mode 100644 index 5eff957cb5..0000000000 --- a/apps/browser/src/platform/services/browser-messaging.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; - -import { BrowserApi } from "../browser/browser-api"; - -export default class BrowserMessagingService implements MessagingService { - send(subscriber: string, arg: any = {}) { - return BrowserApi.sendMessage(subscriber, arg); - } -} diff --git a/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts b/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts new file mode 100644 index 0000000000..e30f35b680 --- /dev/null +++ b/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts @@ -0,0 +1,26 @@ +import { map, share } from "rxjs"; + +import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal"; + +import { fromChromeEvent } from "../browser/from-chrome-event"; + +/** + * Creates an observable that listens to messages through `chrome.runtime.onMessage`. + * @returns An observable stream of messages. + */ +export const fromChromeRuntimeMessaging = () => { + return fromChromeEvent(chrome.runtime.onMessage).pipe( + map(([message, sender]) => { + message ??= {}; + + // Force the sender onto the message as long as we won't overwrite anything + if (!("webExtSender" in message)) { + message.webExtSender = sender; + } + + return message; + }), + tagAsExternal, + share(), + ); +}; diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 2aba93ac95..7acaf1ba93 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -1,17 +1,16 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { NavigationEnd, Router, RouterOutlet } from "@angular/router"; -import { filter, concatMap, Subject, takeUntil, firstValueFrom, map } from "rxjs"; +import { filter, concatMap, Subject, takeUntil, firstValueFrom, tap, map } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { MessageListener } from "@bitwarden/common/platform/messaging"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components"; import { BrowserApi } from "../platform/browser/browser-api"; -import { ZonedMessageListenerService } from "../platform/browser/zoned-message-listener.service"; import { BrowserStateService } from "../platform/services/abstractions/browser-state.service"; import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service"; import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; @@ -34,7 +33,6 @@ export class AppComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); constructor( - private broadcasterService: BroadcasterService, private authService: AuthService, private i18nService: I18nService, private router: Router, @@ -46,7 +44,7 @@ export class AppComponent implements OnInit, OnDestroy { private ngZone: NgZone, private platformUtilsService: PlatformUtilsService, private dialogService: DialogService, - private browserMessagingApi: ZonedMessageListenerService, + private messageListener: MessageListener, private toastService: ToastService, ) {} @@ -78,77 +76,76 @@ export class AppComponent implements OnInit, OnDestroy { window.onkeypress = () => this.recordActivity(); }); - const bitwardenPopupMainMessageListener = (msg: any, sender: any) => { - if (msg.command === "doneLoggingOut") { - this.authService.logOut(async () => { - if (msg.expired) { - this.toastService.showToast({ - variant: "warning", - title: this.i18nService.t("loggedOut"), - message: this.i18nService.t("loginExpired"), + this.messageListener.allMessages$ + .pipe( + tap((msg: any) => { + if (msg.command === "doneLoggingOut") { + this.authService.logOut(async () => { + if (msg.expired) { + this.toastService.showToast({ + variant: "warning", + title: this.i18nService.t("loggedOut"), + message: this.i18nService.t("loginExpired"), + }); + } + + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["home"]); }); + this.changeDetectorRef.detectChanges(); + } else if (msg.command === "authBlocked") { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["home"]); + } else if ( + msg.command === "locked" && + (msg.userId == null || msg.userId == this.activeUserId) + ) { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["lock"]); + } else if (msg.command === "showDialog") { + // 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.showDialog(msg); + } else if (msg.command === "showNativeMessagingFinterprintDialog") { + // TODO: Should be refactored to live in another service. + // 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.showNativeMessagingFingerprintDialog(msg); + } else if (msg.command === "showToast") { + this.toastService._showToast(msg); + } else if (msg.command === "reloadProcess") { + const forceWindowReload = + this.platformUtilsService.isSafari() || + this.platformUtilsService.isFirefox() || + this.platformUtilsService.isOpera(); + // Wait to make sure background has reloaded first. + window.setTimeout( + () => BrowserApi.reloadExtension(forceWindowReload ? window : null), + 2000, + ); + } else if (msg.command === "reloadPopup") { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["/"]); + } else if (msg.command === "convertAccountToKeyConnector") { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["/remove-password"]); + } else if (msg.command === "switchAccountFinish") { + // TODO: unset loading? + // this.loading = false; + } else if (msg.command == "update-temp-password") { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["/update-temp-password"]); } - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["home"]); - }); - this.changeDetectorRef.detectChanges(); - } else if (msg.command === "authBlocked") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["home"]); - } else if ( - msg.command === "locked" && - (msg.userId == null || msg.userId == this.activeUserId) - ) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["lock"]); - } else if (msg.command === "showDialog") { - // 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.showDialog(msg); - } else if (msg.command === "showNativeMessagingFinterprintDialog") { - // TODO: Should be refactored to live in another service. - // 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.showNativeMessagingFingerprintDialog(msg); - } else if (msg.command === "showToast") { - this.toastService._showToast(msg); - } else if (msg.command === "reloadProcess") { - const forceWindowReload = - this.platformUtilsService.isSafari() || - this.platformUtilsService.isFirefox() || - this.platformUtilsService.isOpera(); - // Wait to make sure background has reloaded first. - window.setTimeout( - () => BrowserApi.reloadExtension(forceWindowReload ? window : null), - 2000, - ); - } else if (msg.command === "reloadPopup") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/"]); - } else if (msg.command === "convertAccountToKeyConnector") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/remove-password"]); - } else if (msg.command === "switchAccountFinish") { - // TODO: unset loading? - // this.loading = false; - } else if (msg.command == "update-temp-password") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/update-temp-password"]); - } else { - msg.webExtSender = sender; - this.broadcasterService.send(msg); - } - }; - - (self as any).bitwardenPopupMainMessageListener = bitwardenPopupMainMessageListener; - this.browserMessagingApi.messageListener("app.component", bitwardenPopupMainMessageListener); + }), + takeUntil(this.destroy$), + ) + .subscribe(); // eslint-disable-next-line rxjs/no-async-subscribe this.router.events.pipe(takeUntil(this.destroy$)).subscribe(async (event) => { diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index f3be8490c1..4ab1fe2368 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -1,5 +1,6 @@ import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core"; import { Router } from "@angular/router"; +import { Subject, merge } from "rxjs"; import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards"; import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service"; @@ -11,6 +12,7 @@ import { OBSERVABLE_MEMORY_STORAGE, SYSTEM_THEME_OBSERVABLE, SafeInjectionToken, + INTRAPROCESS_MESSAGING_SUBJECT, } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { @@ -54,7 +56,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { @@ -63,6 +64,9 @@ import { ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; +import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; +// eslint-disable-next-line no-restricted-imports -- Used for dependency injection +import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; @@ -89,20 +93,23 @@ import AutofillService from "../../autofill/services/autofill.service"; import MainBackground from "../../background/main.background"; import { Account } from "../../models/account"; import { BrowserApi } from "../../platform/browser/browser-api"; +import { runInsideAngular } from "../../platform/browser/run-inside-angular.operator"; +/* eslint-disable no-restricted-imports */ +import { ChromeMessageSender } from "../../platform/messaging/chrome-message.sender"; +/* eslint-enable no-restricted-imports */ import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service"; import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service"; import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service"; import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service"; -import BrowserMessagingPrivateModePopupService from "../../platform/services/browser-messaging-private-mode-popup.service"; -import BrowserMessagingService from "../../platform/services/browser-messaging.service"; import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service"; import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service"; import I18nService from "../../platform/services/i18n.service"; import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service"; import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider"; import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; +import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging"; import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service"; import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service"; import { VaultBrowserStateService } from "../../vault/services/vault-browser-state.service"; @@ -155,15 +162,6 @@ const safeProviders: SafeProvider[] = [ useClass: UnauthGuardService, deps: [AuthServiceAbstraction, Router], }), - safeProvider({ - provide: MessagingService, - useFactory: () => { - return needsBackgroundInit && BrowserApi.isManifestVersion(2) - ? new BrowserMessagingPrivateModePopupService() - : new BrowserMessagingService(); - }, - deps: [], - }), safeProvider({ provide: TwoFactorService, useFactory: getBgService("twoFactorService"), @@ -484,6 +482,65 @@ const safeProviders: SafeProvider[] = [ useClass: BrowserSendStateService, deps: [StateProvider], }), + safeProvider({ + provide: MessageListener, + useFactory: (subject: Subject>, ngZone: NgZone) => + new MessageListener( + merge( + subject.asObservable(), // For messages in the same context + fromChromeRuntimeMessaging().pipe(runInsideAngular(ngZone)), // For messages in the same context + ), + ), + deps: [INTRAPROCESS_MESSAGING_SUBJECT, NgZone], + }), + safeProvider({ + provide: MessageSender, + useFactory: (subject: Subject>, logService: LogService) => + MessageSender.combine( + new SubjectMessageSender(subject), // For sending messages in the same context + new ChromeMessageSender(logService), // For sending messages to different contexts + ), + deps: [INTRAPROCESS_MESSAGING_SUBJECT, LogService], + }), + safeProvider({ + provide: INTRAPROCESS_MESSAGING_SUBJECT, + useFactory: () => { + if (BrowserPopupUtils.backgroundInitializationRequired()) { + // There is no persistent main background which means we have one in memory, + // we need the same instance that our in memory background is utilizing. + return getBgService("intraprocessMessagingSubject")(); + } else { + return new Subject>(); + } + }, + deps: [], + }), + safeProvider({ + provide: MessageSender, + useFactory: (subject: Subject>, logService: LogService) => + MessageSender.combine( + new SubjectMessageSender(subject), // For sending messages in the same context + new ChromeMessageSender(logService), // For sending messages to different contexts + ), + deps: [INTRAPROCESS_MESSAGING_SUBJECT, LogService], + }), + safeProvider({ + provide: INTRAPROCESS_MESSAGING_SUBJECT, + useFactory: () => { + if (needsBackgroundInit) { + // We will have created a popup within this context, in that case + // we want to make sure we have the same subject as that context so we + // can message with it. + return getBgService("intraprocessMessagingSubject")(); + } else { + // There isn't a locally created background so we will communicate with + // the true background through chrome apis, in that case, we can just create + // one for ourself. + return new Subject>(); + } + }, + deps: [], + }), ]; @NgModule({ diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 7fbefc10e3..e784997d82 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -60,10 +60,10 @@ import { } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; +import { MessageSender } from "@bitwarden/common/platform/messaging"; import { Account } from "@bitwarden/common/platform/models/domain/account"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { AppIdService } from "@bitwarden/common/platform/services/app-id.service"; -import { BroadcasterService } from "@bitwarden/common/platform/services/broadcaster.service"; import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; @@ -75,7 +75,6 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; -import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service"; import { StateService } from "@bitwarden/common/platform/services/state.service"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { @@ -155,7 +154,7 @@ global.DOMParser = new jsdom.JSDOM().window.DOMParser; const packageJson = require("../package.json"); export class Main { - messagingService: NoopMessagingService; + messagingService: MessageSender; storageService: LowdbStorageService; secureStorageService: NodeEnvSecureStorageService; memoryStorageService: MemoryStorageService; @@ -212,7 +211,6 @@ export class Main { organizationService: OrganizationService; providerService: ProviderService; twoFactorService: TwoFactorService; - broadcasterService: BroadcasterService; folderApiService: FolderApiService; userVerificationApiService: UserVerificationApiService; organizationApiService: OrganizationApiServiceAbstraction; @@ -298,7 +296,7 @@ export class Main { stateEventRegistrarService, ); - this.messagingService = new NoopMessagingService(); + this.messagingService = MessageSender.EMPTY; this.accountService = new AccountServiceImplementation( this.messagingService, @@ -422,8 +420,6 @@ export class Main { this.searchService = new SearchService(this.logService, this.i18nService, this.stateProvider); - this.broadcasterService = new BroadcasterService(); - this.collectionService = new CollectionService( this.cryptoService, this.i18nService, diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 264f26cbe2..d1d51c0f1c 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -1,4 +1,5 @@ import { APP_INITIALIZER, NgModule } from "@angular/core"; +import { Subject, merge } from "rxjs"; import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; import { @@ -14,6 +15,7 @@ import { SYSTEM_THEME_OBSERVABLE, SafeInjectionToken, STATE_FACTORY, + INTRAPROCESS_MESSAGING_SUBJECT, } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; @@ -23,7 +25,6 @@ import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/ab import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -42,6 +43,9 @@ import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/ import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service"; import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; +import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; +// eslint-disable-next-line no-restricted-imports -- Used for dependency injection +import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; @@ -63,11 +67,12 @@ import { ELECTRON_SUPPORTS_SECURE_STORAGE, ElectronPlatformUtilsService, } from "../../platform/services/electron-platform-utils.service"; -import { ElectronRendererMessagingService } from "../../platform/services/electron-renderer-messaging.service"; +import { ElectronRendererMessageSender } from "../../platform/services/electron-renderer-message.sender"; import { ElectronRendererSecureStorageService } from "../../platform/services/electron-renderer-secure-storage.service"; import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service"; import { ElectronStateService } from "../../platform/services/electron-state.service"; import { I18nRendererService } from "../../platform/services/i18n.renderer.service"; +import { fromIpcMessaging } from "../../platform/utils/from-ipc-messaging"; import { fromIpcSystemTheme } from "../../platform/utils/from-ipc-system-theme"; import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service"; import { NativeMessageHandlerService } from "../../services/native-message-handler.service"; @@ -138,9 +143,24 @@ const safeProviders: SafeProvider[] = [ deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY, GlobalStateProvider], }), safeProvider({ - provide: MessagingServiceAbstraction, - useClass: ElectronRendererMessagingService, - deps: [BroadcasterServiceAbstraction], + provide: MessageSender, + useFactory: (subject: Subject>) => + MessageSender.combine( + new ElectronRendererMessageSender(), // Communication with main process + new SubjectMessageSender(subject), // Communication with ourself + ), + deps: [INTRAPROCESS_MESSAGING_SUBJECT], + }), + safeProvider({ + provide: MessageListener, + useFactory: (subject: Subject>) => + new MessageListener( + merge( + subject.asObservable(), // For messages from the same context + fromIpcMessaging(), // For messages from the main process + ), + ), + deps: [INTRAPROCESS_MESSAGING_SUBJECT], }), safeProvider({ provide: AbstractStorageService, diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index a4783e0573..0655e5600d 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -1,7 +1,7 @@ import * as path from "path"; import { app } from "electron"; -import { firstValueFrom } from "rxjs"; +import { Subject, firstValueFrom } from "rxjs"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; @@ -11,6 +11,9 @@ import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwar import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { DefaultBiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; +import { Message, MessageSender } from "@bitwarden/common/platform/messaging"; +// eslint-disable-next-line no-restricted-imports -- For dependency creation +import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service"; @@ -18,7 +21,6 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; -import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service"; /* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed */ import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider"; @@ -59,7 +61,7 @@ export class Main { storageService: ElectronStorageService; memoryStorageService: MemoryStorageService; memoryStorageForStateProviders: MemoryStorageServiceForStateProviders; - messagingService: ElectronMainMessagingService; + messagingService: MessageSender; stateService: StateService; environmentService: DefaultEnvironmentService; mainCryptoFunctionService: MainCryptoFunctionService; @@ -131,7 +133,7 @@ export class Main { this.i18nService = new I18nMainService("en", "./locales/", globalStateProvider); const accountService = new AccountServiceImplementation( - new NoopMessagingService(), + MessageSender.EMPTY, this.logService, globalStateProvider, ); @@ -223,7 +225,13 @@ export class Main { this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain); this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.desktopSettingsService); - this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => { + const messageSubject = new Subject>(); + this.messagingService = MessageSender.combine( + new SubjectMessageSender(messageSubject), // For local messages + new ElectronMainMessagingService(this.windowMain), + ); + + messageSubject.asObservable().subscribe((message) => { this.messagingMain.onMessage(message); }); diff --git a/apps/desktop/src/main/power-monitor.main.ts b/apps/desktop/src/main/power-monitor.main.ts index 067a380ba0..8cad5c1d9e 100644 --- a/apps/desktop/src/main/power-monitor.main.ts +++ b/apps/desktop/src/main/power-monitor.main.ts @@ -1,6 +1,7 @@ import { powerMonitor } from "electron"; -import { ElectronMainMessagingService } from "../services/electron-main-messaging.service"; +import { MessageSender } from "@bitwarden/common/platform/messaging"; + import { isSnapStore } from "../utils"; // tslint:disable-next-line @@ -10,7 +11,7 @@ const IdleCheckInterval = 30 * 1000; // 30 seconds export class PowerMonitorMain { private idle = false; - constructor(private messagingService: ElectronMainMessagingService) {} + constructor(private messagingService: MessageSender) {} init() { // ref: https://github.com/electron/electron/issues/13767 diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index 04819998d5..771d25ef0a 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -124,12 +124,21 @@ export default { sendMessage: (message: { command: string } & any) => ipcRenderer.send("messagingService", message), - onMessage: (callback: (message: { command: string } & any) => void) => { - ipcRenderer.on("messagingService", (_event, message: any) => { - if (message.command) { - callback(message); - } - }); + onMessage: { + addListener: (callback: (message: { command: string } & any) => void) => { + ipcRenderer.addListener("messagingService", (_event, message: any) => { + if (message.command) { + callback(message); + } + }); + }, + removeListener: (callback: (message: { command: string } & any) => void) => { + ipcRenderer.removeListener("messagingService", (_event, message: any) => { + if (message.command) { + callback(message); + } + }); + }, }, launchUri: (uri: string) => ipcRenderer.invoke("launchUri", uri), diff --git a/apps/desktop/src/platform/services/electron-renderer-message.sender.ts b/apps/desktop/src/platform/services/electron-renderer-message.sender.ts new file mode 100644 index 0000000000..037c303b3b --- /dev/null +++ b/apps/desktop/src/platform/services/electron-renderer-message.sender.ts @@ -0,0 +1,12 @@ +import { MessageSender, CommandDefinition } from "@bitwarden/common/platform/messaging"; +import { getCommand } from "@bitwarden/common/platform/messaging/internal"; + +export class ElectronRendererMessageSender implements MessageSender { + send( + commandDefinition: CommandDefinition | string, + payload: object | T = {}, + ): void { + const command = getCommand(commandDefinition); + ipc.platform.sendMessage(Object.assign({}, { command: command }, payload)); + } +} diff --git a/apps/desktop/src/platform/services/electron-renderer-messaging.service.ts b/apps/desktop/src/platform/services/electron-renderer-messaging.service.ts deleted file mode 100644 index 192efc1dc6..0000000000 --- a/apps/desktop/src/platform/services/electron-renderer-messaging.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; - -export class ElectronRendererMessagingService implements MessagingService { - constructor(private broadcasterService: BroadcasterService) { - ipc.platform.onMessage((message) => this.sendMessage(message.command, message, false)); - } - - send(subscriber: string, arg: any = {}) { - this.sendMessage(subscriber, arg, true); - } - - private sendMessage(subscriber: string, arg: any = {}, toMain: boolean) { - const message = Object.assign({}, { command: subscriber }, arg); - this.broadcasterService.send(message); - if (toMain) { - ipc.platform.sendMessage(message); - } - } -} diff --git a/apps/desktop/src/platform/utils/from-ipc-messaging.ts b/apps/desktop/src/platform/utils/from-ipc-messaging.ts new file mode 100644 index 0000000000..254a215ceb --- /dev/null +++ b/apps/desktop/src/platform/utils/from-ipc-messaging.ts @@ -0,0 +1,15 @@ +import { fromEventPattern, share } from "rxjs"; + +import { Message } from "@bitwarden/common/platform/messaging"; +import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal"; + +/** + * Creates an observable that when subscribed to will listen to messaging events through IPC. + * @returns An observable stream of messages. + */ +export const fromIpcMessaging = () => { + return fromEventPattern>( + (handler) => ipc.platform.onMessage.addListener(handler), + (handler) => ipc.platform.onMessage.removeListener(handler), + ).pipe(tagAsExternal, share()); +}; diff --git a/apps/desktop/src/services/electron-main-messaging.service.ts b/apps/desktop/src/services/electron-main-messaging.service.ts index 71e1b1d7d5..ce4ffd903a 100644 --- a/apps/desktop/src/services/electron-main-messaging.service.ts +++ b/apps/desktop/src/services/electron-main-messaging.service.ts @@ -2,18 +2,17 @@ import * as path from "path"; import { app, dialog, ipcMain, Menu, MenuItem, nativeTheme, Notification, shell } from "electron"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; +import { MessageSender, CommandDefinition } from "@bitwarden/common/platform/messaging"; +// eslint-disable-next-line no-restricted-imports -- Using implementation helper in implementation +import { getCommand } from "@bitwarden/common/platform/messaging/internal"; import { SafeUrls } from "@bitwarden/common/platform/misc/safe-urls"; import { WindowMain } from "../main/window.main"; import { RendererMenuItem } from "../utils"; -export class ElectronMainMessagingService implements MessagingService { - constructor( - private windowMain: WindowMain, - private onMessage: (message: any) => void, - ) { +export class ElectronMainMessagingService implements MessageSender { + constructor(private windowMain: WindowMain) { ipcMain.handle("appVersion", () => { return app.getVersion(); }); @@ -88,9 +87,9 @@ export class ElectronMainMessagingService implements MessagingService { }); } - send(subscriber: string, arg: any = {}) { - const message = Object.assign({}, { command: subscriber }, arg); - this.onMessage(message); + send(commandDefinition: CommandDefinition | string, arg: T | object = {}) { + const command = getCommand(commandDefinition); + const message = Object.assign({}, { command: command }, arg); if (this.windowMain.win != null) { this.windowMain.win.webContents.send("messagingService", message); } diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts index 0c5f1d706b..bd138cad29 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts @@ -103,7 +103,8 @@ export class TrialBillingStepComponent implements OnInit { planDescription, }); - this.messagingService.send("organizationCreated", organizationId); + // TODO: No one actually listening to this? + this.messagingService.send("organizationCreated", { organizationId }); } protected changedCountry() { 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 645b1f29ac..23d48d93be 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -587,7 +587,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.formPromise = doSubmit(); const organizationId = await this.formPromise; this.onSuccess.emit({ organizationId: organizationId }); - this.messagingService.send("organizationCreated", organizationId); + // TODO: No one actually listening to this message? + this.messagingService.send("organizationCreated", { organizationId }); } catch (e) { this.logService.error(e); } diff --git a/apps/web/src/app/core/broadcaster-messaging.service.ts b/apps/web/src/app/core/broadcaster-messaging.service.ts deleted file mode 100644 index 7c8e4eef43..0000000000 --- a/apps/web/src/app/core/broadcaster-messaging.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Injectable } from "@angular/core"; - -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; - -@Injectable() -export class BroadcasterMessagingService implements MessagingService { - constructor(private broadcasterService: BroadcasterService) {} - - send(subscriber: string, arg: any = {}) { - const message = Object.assign({}, { command: subscriber }, arg); - this.broadcasterService.send(message); - } -} diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 9d53bc39f0..a274764756 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -22,7 +22,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; @@ -51,7 +50,6 @@ import { WebStorageServiceProvider } from "../platform/web-storage-service.provi import { WindowStorageService } from "../platform/window-storage.service"; import { CollectionAdminService } from "../vault/core/collection-admin.service"; -import { BroadcasterMessagingService } from "./broadcaster-messaging.service"; import { EventService } from "./event.service"; import { InitService } from "./init.service"; import { ModalService } from "./modal.service"; @@ -117,11 +115,6 @@ const safeProviders: SafeProvider[] = [ useClass: WebPlatformUtilsService, useAngularDecorators: true, }), - safeProvider({ - provide: MessagingServiceAbstraction, - useClass: BroadcasterMessagingService, - useAngularDecorators: true, - }), safeProvider({ provide: ModalServiceAbstraction, useClass: ModalService, diff --git a/apps/web/src/app/vault/components/premium-badge.stories.ts b/apps/web/src/app/vault/components/premium-badge.stories.ts index 5433dd9981..c61bbb46a5 100644 --- a/apps/web/src/app/vault/components/premium-badge.stories.ts +++ b/apps/web/src/app/vault/components/premium-badge.stories.ts @@ -4,15 +4,15 @@ import { of } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { MessageSender } from "@bitwarden/common/platform/messaging"; import { BadgeModule, I18nMockService } from "@bitwarden/components"; import { PremiumBadgeComponent } from "./premium-badge.component"; -class MockMessagingService implements MessagingService { - send(subscriber: string, arg?: any) { +class MockMessagingService implements MessageSender { + send = () => { alert("Clicked on badge"); - } + }; } export default { @@ -31,7 +31,7 @@ export default { }, }, { - provide: MessagingService, + provide: MessageSender, useFactory: () => { return new MockMessagingService(); }, diff --git a/libs/angular/src/platform/services/broadcaster.service.ts b/libs/angular/src/platform/services/broadcaster.service.ts deleted file mode 100644 index cf58d2b311..0000000000 --- a/libs/angular/src/platform/services/broadcaster.service.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Injectable } from "@angular/core"; - -import { BroadcasterService as BaseBroadcasterService } from "@bitwarden/common/platform/services/broadcaster.service"; - -@Injectable() -export class BroadcasterService extends BaseBroadcasterService {} diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 7d39078797..6fffe722fb 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -1,5 +1,5 @@ import { InjectionToken } from "@angular/core"; -import { Observable } from "rxjs"; +import { Observable, Subject } from "rxjs"; import { AbstractMemoryStorageService, @@ -8,6 +8,7 @@ import { } from "@bitwarden/common/platform/abstractions/storage.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; +import { Message } from "@bitwarden/common/platform/messaging"; declare const tag: unique symbol; /** @@ -49,3 +50,6 @@ export const LOG_MAC_FAILURES = new SafeInjectionToken("LOG_MAC_FAILURE export const SYSTEM_THEME_OBSERVABLE = new SafeInjectionToken>( "SYSTEM_THEME_OBSERVABLE", ); +export const INTRAPROCESS_MESSAGING_SUBJECT = new SafeInjectionToken>>( + "INTRAPROCESS_MESSAGING_SUBJECT", +); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index b0d84e7c3b..204ff5a294 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1,4 +1,5 @@ import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core"; +import { Subject } from "rxjs"; import { AuthRequestServiceAbstraction, @@ -116,7 +117,7 @@ import { BillingApiService } from "@bitwarden/common/billing/services/billing-ap import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service"; import { PaymentMethodWarningsService } from "@bitwarden/common/billing/services/payment-method-warnings.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -137,6 +138,9 @@ import { DefaultBiometricStateService, } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; +import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; +// eslint-disable-next-line no-restricted-imports -- Used for dependency injection +import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; import { devFlagEnabled, flagEnabled } from "@bitwarden/common/platform/misc/flags"; import { Account } from "@bitwarden/common/platform/models/domain/account"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; @@ -147,6 +151,7 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l import { CryptoService } from "@bitwarden/common/platform/services/crypto.service"; import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation"; +import { DefaultBroadcasterService } from "@bitwarden/common/platform/services/default-broadcaster.service"; import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service"; import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service"; import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service"; @@ -247,7 +252,6 @@ import { import { AuthGuard } from "../auth/guards/auth.guard"; import { UnauthGuard } from "../auth/guards/unauth.guard"; import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service"; -import { BroadcasterService } from "../platform/services/broadcaster.service"; import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service"; import { LoggingErrorHandler } from "../platform/services/logging-error-handler"; import { AngularThemingService } from "../platform/services/theming/angular-theming.service"; @@ -270,6 +274,7 @@ import { SYSTEM_LANGUAGE, SYSTEM_THEME_OBSERVABLE, WINDOW, + INTRAPROCESS_MESSAGING_SUBJECT, } from "./injection-tokens"; import { ModalService } from "./modal.service"; @@ -625,7 +630,11 @@ const safeProviders: SafeProvider[] = [ BillingAccountProfileStateService, ], }), - safeProvider({ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService, deps: [] }), + safeProvider({ + provide: BroadcasterService, + useClass: DefaultBroadcasterService, + deps: [MessageSender, MessageListener], + }), safeProvider({ provide: VaultTimeoutSettingsServiceAbstraction, useClass: VaultTimeoutSettingsService, @@ -1127,6 +1136,21 @@ const safeProviders: SafeProvider[] = [ useClass: LoggingErrorHandler, deps: [], }), + safeProvider({ + provide: INTRAPROCESS_MESSAGING_SUBJECT, + useFactory: () => new Subject>(), + deps: [], + }), + safeProvider({ + provide: MessageListener, + useFactory: (subject: Subject>) => new MessageListener(subject.asObservable()), + deps: [INTRAPROCESS_MESSAGING_SUBJECT], + }), + safeProvider({ + provide: MessageSender, + useFactory: (subject: Subject>) => new SubjectMessageSender(subject), + deps: [INTRAPROCESS_MESSAGING_SUBJECT], + }), safeProvider({ provide: ProviderApiServiceAbstraction, useClass: ProviderApiService, diff --git a/libs/common/src/platform/abstractions/messaging.service.ts b/libs/common/src/platform/abstractions/messaging.service.ts index ab4332c283..f24279f932 100644 --- a/libs/common/src/platform/abstractions/messaging.service.ts +++ b/libs/common/src/platform/abstractions/messaging.service.ts @@ -1,3 +1,3 @@ -export abstract class MessagingService { - abstract send(subscriber: string, arg?: any): void; -} +// Export the new message sender as the legacy MessagingService to minimize changes in the initial PR, +// team specific PR's will come after. +export { MessageSender as MessagingService } from "../messaging/message.sender"; diff --git a/libs/common/src/platform/messaging/helpers.spec.ts b/libs/common/src/platform/messaging/helpers.spec.ts new file mode 100644 index 0000000000..fcd36b4411 --- /dev/null +++ b/libs/common/src/platform/messaging/helpers.spec.ts @@ -0,0 +1,46 @@ +import { Subject, firstValueFrom } from "rxjs"; + +import { getCommand, isExternalMessage, tagAsExternal } from "./helpers"; +import { Message, CommandDefinition } from "./types"; + +describe("helpers", () => { + describe("getCommand", () => { + it("can get the command from just a string", () => { + const command = getCommand("myCommand"); + + expect(command).toEqual("myCommand"); + }); + + it("can get the command from a message definition", () => { + const commandDefinition = new CommandDefinition("myCommand"); + + const command = getCommand(commandDefinition); + + expect(command).toEqual("myCommand"); + }); + }); + + describe("tag integration", () => { + it("can tag and identify as tagged", async () => { + const messagesSubject = new Subject>(); + + const taggedMessages = messagesSubject.asObservable().pipe(tagAsExternal); + + const firstValuePromise = firstValueFrom(taggedMessages); + + messagesSubject.next({ command: "test" }); + + const result = await firstValuePromise; + + expect(isExternalMessage(result)).toEqual(true); + }); + }); + + describe("isExternalMessage", () => { + it.each([null, { command: "myCommand", test: "object" }, undefined] as Message< + Record + >[])("returns false when value is %s", (value: Message) => { + expect(isExternalMessage(value)).toBe(false); + }); + }); +}); diff --git a/libs/common/src/platform/messaging/helpers.ts b/libs/common/src/platform/messaging/helpers.ts new file mode 100644 index 0000000000..bf119432e0 --- /dev/null +++ b/libs/common/src/platform/messaging/helpers.ts @@ -0,0 +1,23 @@ +import { MonoTypeOperatorFunction, map } from "rxjs"; + +import { Message, CommandDefinition } from "./types"; + +export const getCommand = (commandDefinition: CommandDefinition | string) => { + if (typeof commandDefinition === "string") { + return commandDefinition; + } else { + return commandDefinition.command; + } +}; + +export const EXTERNAL_SOURCE_TAG = Symbol("externalSource"); + +export const isExternalMessage = (message: Message) => { + return (message as Record)?.[EXTERNAL_SOURCE_TAG] === true; +}; + +export const tagAsExternal: MonoTypeOperatorFunction> = map( + (message: Message) => { + return Object.assign(message, { [EXTERNAL_SOURCE_TAG]: true }); + }, +); diff --git a/libs/common/src/platform/messaging/index.ts b/libs/common/src/platform/messaging/index.ts new file mode 100644 index 0000000000..a9b4eca5ae --- /dev/null +++ b/libs/common/src/platform/messaging/index.ts @@ -0,0 +1,4 @@ +export { MessageListener } from "./message.listener"; +export { MessageSender } from "./message.sender"; +export { Message, CommandDefinition } from "./types"; +export { isExternalMessage } from "./helpers"; diff --git a/libs/common/src/platform/messaging/internal.ts b/libs/common/src/platform/messaging/internal.ts new file mode 100644 index 0000000000..08763d48bc --- /dev/null +++ b/libs/common/src/platform/messaging/internal.ts @@ -0,0 +1,5 @@ +// Built in implementations +export { SubjectMessageSender } from "./subject-message.sender"; + +// Helpers meant to be used only by other implementations +export { tagAsExternal, getCommand } from "./helpers"; diff --git a/libs/common/src/platform/messaging/message.listener.spec.ts b/libs/common/src/platform/messaging/message.listener.spec.ts new file mode 100644 index 0000000000..98bbf1fdc8 --- /dev/null +++ b/libs/common/src/platform/messaging/message.listener.spec.ts @@ -0,0 +1,47 @@ +import { Subject } from "rxjs"; + +import { subscribeTo } from "../../../spec/observable-tracker"; + +import { MessageListener } from "./message.listener"; +import { Message, CommandDefinition } from "./types"; + +describe("MessageListener", () => { + const subject = new Subject>(); + const sut = new MessageListener(subject.asObservable()); + + const testCommandDefinition = new CommandDefinition<{ test: number }>("myCommand"); + + describe("allMessages$", () => { + it("runs on all nexts", async () => { + const tracker = subscribeTo(sut.allMessages$); + + const pausePromise = tracker.pauseUntilReceived(2); + + subject.next({ command: "command1", test: 1 }); + subject.next({ command: "command2", test: 2 }); + + await pausePromise; + + expect(tracker.emissions[0]).toEqual({ command: "command1", test: 1 }); + expect(tracker.emissions[1]).toEqual({ command: "command2", test: 2 }); + }); + }); + + describe("messages$", () => { + it("runs on only my commands", async () => { + const tracker = subscribeTo(sut.messages$(testCommandDefinition)); + + const pausePromise = tracker.pauseUntilReceived(2); + + subject.next({ command: "notMyCommand", test: 1 }); + subject.next({ command: "myCommand", test: 2 }); + subject.next({ command: "myCommand", test: 3 }); + subject.next({ command: "notMyCommand", test: 4 }); + + await pausePromise; + + expect(tracker.emissions[0]).toEqual({ command: "myCommand", test: 2 }); + expect(tracker.emissions[1]).toEqual({ command: "myCommand", test: 3 }); + }); + }); +}); diff --git a/libs/common/src/platform/messaging/message.listener.ts b/libs/common/src/platform/messaging/message.listener.ts new file mode 100644 index 0000000000..df453c8422 --- /dev/null +++ b/libs/common/src/platform/messaging/message.listener.ts @@ -0,0 +1,41 @@ +import { EMPTY, Observable, filter } from "rxjs"; + +import { Message, CommandDefinition } from "./types"; + +/** + * A class that allows for listening to messages coming through the application, + * allows for listening of all messages or just the messages you care about. + * + * @note Consider NOT using messaging at all if you can. State Providers offer an observable stream of + * data that is persisted. This can serve messages that might have been used to notify of settings changes + * or vault data changes and those observables should be preferred over messaging. + */ +export class MessageListener { + constructor(private readonly messageStream: Observable>) {} + + /** + * A stream of all messages sent through the application. It does not contain type information for the + * other properties on the messages. You are encouraged to instead subscribe to an individual message + * through {@link messages$}. + */ + allMessages$ = this.messageStream; + + /** + * Creates an observable stream filtered to just the command given via the {@link CommandDefinition} and typed + * to the generic contained in the CommandDefinition. Be careful using this method unless all your messages are being + * sent through `MessageSender.send`, if that isn't the case you should have lower confidence in the message + * payload being the expected type. + * + * @param commandDefinition The CommandDefinition containing the information about the message type you care about. + */ + messages$(commandDefinition: CommandDefinition): Observable { + return this.allMessages$.pipe( + filter((msg) => msg?.command === commandDefinition.command), + ) as Observable; + } + + /** + * A helper property for returning a MessageListener that will never emit any messages and will immediately complete. + */ + static readonly EMPTY = new MessageListener(EMPTY); +} diff --git a/libs/common/src/platform/messaging/message.sender.ts b/libs/common/src/platform/messaging/message.sender.ts new file mode 100644 index 0000000000..6bf2661580 --- /dev/null +++ b/libs/common/src/platform/messaging/message.sender.ts @@ -0,0 +1,62 @@ +import { CommandDefinition } from "./types"; + +class MultiMessageSender implements MessageSender { + constructor(private readonly innerMessageSenders: MessageSender[]) {} + + send( + commandDefinition: string | CommandDefinition, + payload: object | T = {}, + ): void { + for (const messageSender of this.innerMessageSenders) { + messageSender.send(commandDefinition, payload); + } + } +} + +export abstract class MessageSender { + /** + * A method for sending messages in a type safe manner. The passed in command definition + * will require you to provide a compatible type in the payload parameter. + * + * @example + * const MY_COMMAND = new CommandDefinition<{ test: number }>("myCommand"); + * + * this.messageSender.send(MY_COMMAND, { test: 14 }); + * + * @param commandDefinition + * @param payload + */ + abstract send(commandDefinition: CommandDefinition, payload: T): void; + + /** + * A legacy method for sending messages in a non-type safe way. + * + * @remarks Consider defining a {@link CommandDefinition} and passing that in for the first parameter to + * get compilation errors when defining an incompatible payload. + * + * @param command The string based command of your message. + * @param payload Extra contextual information regarding the message. Be aware that this payload may + * be serialized and lose all prototype information. + */ + abstract send(command: string, payload?: object): void; + + /** Implementation of the other two overloads, read their docs instead. */ + abstract send( + commandDefinition: CommandDefinition | string, + payload: T | object, + ): void; + + /** + * A helper method for combine multiple {@link MessageSender}'s. + * @param messageSenders The message senders that should be combined. + * @returns A message sender that will relay all messages to the given message senders. + */ + static combine(...messageSenders: MessageSender[]) { + return new MultiMessageSender(messageSenders); + } + + /** + * A helper property for creating a {@link MessageSender} that sends to nowhere. + */ + static readonly EMPTY: MessageSender = new MultiMessageSender([]); +} diff --git a/libs/common/src/platform/messaging/subject-message.sender.spec.ts b/libs/common/src/platform/messaging/subject-message.sender.spec.ts new file mode 100644 index 0000000000..4278fca7bc --- /dev/null +++ b/libs/common/src/platform/messaging/subject-message.sender.spec.ts @@ -0,0 +1,65 @@ +import { Subject } from "rxjs"; + +import { subscribeTo } from "../../../spec/observable-tracker"; + +import { SubjectMessageSender } from "./internal"; +import { MessageSender } from "./message.sender"; +import { Message, CommandDefinition } from "./types"; + +describe("SubjectMessageSender", () => { + const subject = new Subject>(); + const subjectObservable = subject.asObservable(); + + const sut: MessageSender = new SubjectMessageSender(subject); + + describe("send", () => { + it("will send message with command from message definition", async () => { + const commandDefinition = new CommandDefinition<{ test: number }>("myCommand"); + + const tracker = subscribeTo(subjectObservable); + const pausePromise = tracker.pauseUntilReceived(1); + + sut.send(commandDefinition, { test: 1 }); + + await pausePromise; + + expect(tracker.emissions[0]).toEqual({ command: "myCommand", test: 1 }); + }); + + it("will send message with command from normal string", async () => { + const tracker = subscribeTo(subjectObservable); + const pausePromise = tracker.pauseUntilReceived(1); + + sut.send("myCommand", { test: 1 }); + + await pausePromise; + + expect(tracker.emissions[0]).toEqual({ command: "myCommand", test: 1 }); + }); + + it("will send message with object even if payload not given", async () => { + const tracker = subscribeTo(subjectObservable); + const pausePromise = tracker.pauseUntilReceived(1); + + sut.send("myCommand"); + + await pausePromise; + + expect(tracker.emissions[0]).toEqual({ command: "myCommand" }); + }); + + it.each([null, undefined])( + "will send message with object even if payload is null-ish (%s)", + async (payloadValue) => { + const tracker = subscribeTo(subjectObservable); + const pausePromise = tracker.pauseUntilReceived(1); + + sut.send("myCommand", payloadValue); + + await pausePromise; + + expect(tracker.emissions[0]).toEqual({ command: "myCommand" }); + }, + ); + }); +}); diff --git a/libs/common/src/platform/messaging/subject-message.sender.ts b/libs/common/src/platform/messaging/subject-message.sender.ts new file mode 100644 index 0000000000..94ae6f27f3 --- /dev/null +++ b/libs/common/src/platform/messaging/subject-message.sender.ts @@ -0,0 +1,17 @@ +import { Subject } from "rxjs"; + +import { getCommand } from "./internal"; +import { MessageSender } from "./message.sender"; +import { Message, CommandDefinition } from "./types"; + +export class SubjectMessageSender implements MessageSender { + constructor(private readonly messagesSubject: Subject>) {} + + send( + commandDefinition: string | CommandDefinition, + payload: object | T = {}, + ): void { + const command = getCommand(commandDefinition); + this.messagesSubject.next(Object.assign(payload ?? {}, { command: command })); + } +} diff --git a/libs/common/src/platform/messaging/types.ts b/libs/common/src/platform/messaging/types.ts new file mode 100644 index 0000000000..f30163344f --- /dev/null +++ b/libs/common/src/platform/messaging/types.ts @@ -0,0 +1,13 @@ +declare const tag: unique symbol; + +/** + * A class for defining information about a message, this is helpful + * alonside `MessageSender` and `MessageListener` for providing a type + * safe(-ish) way of sending and receiving messages. + */ +export class CommandDefinition { + [tag]: T; + constructor(readonly command: string) {} +} + +export type Message = { command: string } & T; diff --git a/libs/common/src/platform/services/broadcaster.service.ts b/libs/common/src/platform/services/broadcaster.service.ts deleted file mode 100644 index 9d823b00e0..0000000000 --- a/libs/common/src/platform/services/broadcaster.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - BroadcasterService as BroadcasterServiceAbstraction, - MessageBase, -} from "../abstractions/broadcaster.service"; - -export class BroadcasterService implements BroadcasterServiceAbstraction { - subscribers: Map void> = new Map< - string, - (message: MessageBase) => void - >(); - - send(message: MessageBase, id?: string) { - if (id != null) { - if (this.subscribers.has(id)) { - this.subscribers.get(id)(message); - } - return; - } - - this.subscribers.forEach((value) => { - value(message); - }); - } - - subscribe(id: string, messageCallback: (message: MessageBase) => void) { - this.subscribers.set(id, messageCallback); - } - - unsubscribe(id: string) { - if (this.subscribers.has(id)) { - this.subscribers.delete(id); - } - } -} diff --git a/libs/common/src/platform/services/default-broadcaster.service.ts b/libs/common/src/platform/services/default-broadcaster.service.ts new file mode 100644 index 0000000000..a16745c643 --- /dev/null +++ b/libs/common/src/platform/services/default-broadcaster.service.ts @@ -0,0 +1,36 @@ +import { Subscription } from "rxjs"; + +import { BroadcasterService, MessageBase } from "../abstractions/broadcaster.service"; +import { MessageListener, MessageSender } from "../messaging"; + +/** + * Temporary implementation that just delegates to the message sender and message listener + * and manages their subscriptions. + */ +export class DefaultBroadcasterService implements BroadcasterService { + subscriptions = new Map(); + + constructor( + private readonly messageSender: MessageSender, + private readonly messageListener: MessageListener, + ) {} + + send(message: MessageBase, id?: string) { + this.messageSender.send(message?.command, message); + } + + subscribe(id: string, messageCallback: (message: MessageBase) => void) { + this.subscriptions.set( + id, + this.messageListener.allMessages$.subscribe((message) => { + messageCallback(message); + }), + ); + } + + unsubscribe(id: string) { + const subscription = this.subscriptions.get(id); + subscription?.unsubscribe(); + this.subscriptions.delete(id); + } +} diff --git a/libs/common/src/platform/services/noop-messaging.service.ts b/libs/common/src/platform/services/noop-messaging.service.ts deleted file mode 100644 index d1a60bc5bc..0000000000 --- a/libs/common/src/platform/services/noop-messaging.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { MessagingService } from "../abstractions/messaging.service"; - -export class NoopMessagingService implements MessagingService { - send(subscriber: string, arg: any = {}) { - // Do nothing... - } -} From ec1af0cf9f7588e5ec476d573f2f041bbde0d722 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Fri, 19 Apr 2024 15:21:54 -0400 Subject: [PATCH 49/86] [PM-7610] [MV3] Guard overlay visibility and autofill on page load settings from awaiting indefinitely when there is no active account (#8833) * guard overlay visibility and autofill on page load settings from awaiting indefinitely when there is no active account * cleanup --- .../autofill-service.factory.ts | 8 +++++++- .../services/autofill.service.spec.ts | 1 + .../src/autofill/services/autofill.service.ts | 20 +++++++++++++++++-- .../browser/src/background/main.background.ts | 3 ++- .../src/popup/services/services.module.ts | 1 + 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/autofill/background/service_factories/autofill-service.factory.ts b/apps/browser/src/autofill/background/service_factories/autofill-service.factory.ts index 7b423ca4f4..bee5da18b5 100644 --- a/apps/browser/src/autofill/background/service_factories/autofill-service.factory.ts +++ b/apps/browser/src/autofill/background/service_factories/autofill-service.factory.ts @@ -1,3 +1,7 @@ +import { + accountServiceFactory, + AccountServiceInitOptions, +} from "../../../auth/background/service-factories/account-service.factory"; import { UserVerificationServiceInitOptions, userVerificationServiceFactory, @@ -50,7 +54,8 @@ export type AutoFillServiceInitOptions = AutoFillServiceOptions & LogServiceInitOptions & UserVerificationServiceInitOptions & DomainSettingsServiceInitOptions & - BrowserScriptInjectorServiceInitOptions; + BrowserScriptInjectorServiceInitOptions & + AccountServiceInitOptions; export function autofillServiceFactory( cache: { autofillService?: AbstractAutoFillService } & CachedServices, @@ -71,6 +76,7 @@ export function autofillServiceFactory( await userVerificationServiceFactory(cache, opts), await billingAccountProfileStateServiceFactory(cache, opts), await browserScriptInjectorServiceFactory(cache, opts), + await accountServiceFactory(cache, opts), ), ); } diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 5064d2e7df..d1fbf79bfa 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -87,6 +87,7 @@ describe("AutofillService", () => { userVerificationService, billingAccountProfileStateService, scriptInjectorService, + accountService, ); domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 6cf58558dc..8f85d65692 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -1,7 +1,9 @@ import { firstValueFrom } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; @@ -57,6 +59,7 @@ export default class AutofillService implements AutofillServiceInterface { private userVerificationService: UserVerificationService, private billingAccountProfileStateService: BillingAccountProfileStateService, private scriptInjectorService: ScriptInjectorService, + private accountService: AccountService, ) {} /** @@ -104,13 +107,26 @@ export default class AutofillService implements AutofillServiceInterface { frameId = 0, triggeringOnPageLoad = true, ): Promise { - const mainAutofillScript = (await this.getOverlayVisibility()) + // Autofill settings loaded from state can await the active account state indefinitely if + // not guarded by an active account check (e.g. the user is logged in) + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + + // These settings are not available until the user logs in + let overlayVisibility: InlineMenuVisibilitySetting = AutofillOverlayVisibility.Off; + let autoFillOnPageLoadIsEnabled = false; + + if (activeAccount) { + overlayVisibility = await this.getOverlayVisibility(); + } + const mainAutofillScript = overlayVisibility ? "bootstrap-autofill-overlay.js" : "bootstrap-autofill.js"; const injectedScripts = [mainAutofillScript]; - const autoFillOnPageLoadIsEnabled = await this.getAutofillOnPageLoad(); + if (activeAccount) { + autoFillOnPageLoadIsEnabled = await this.getAutofillOnPageLoad(); + } if (triggeringOnPageLoad && autoFillOnPageLoadIsEnabled) { injectedScripts.push("autofiller.js"); diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index f2003f9621..c627c0032b 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -29,6 +29,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/services/policy/p import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; @@ -137,7 +138,6 @@ import { EventUploadService } from "@bitwarden/common/services/event/event-uploa import { NotificationsService } from "@bitwarden/common/services/notifications.service"; import { SearchService } from "@bitwarden/common/services/search.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; -import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/avatar.service"; import { PasswordGenerationService, PasswordGenerationServiceAbstraction, @@ -807,6 +807,7 @@ export default class MainBackground { this.userVerificationService, this.billingAccountProfileStateService, this.scriptInjectorService, + this.accountService, ); this.auditService = new AuditService(this.cryptoFunctionService, this.apiService); diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 4ab1fe2368..123e901e4e 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -312,6 +312,7 @@ const safeProviders: SafeProvider[] = [ UserVerificationService, BillingAccountProfileStateService, ScriptInjectorService, + AccountServiceAbstraction, ], }), safeProvider({ From c8a3cb5708429b9807f4917f1b57fa12a1b76a83 Mon Sep 17 00:00:00 2001 From: MtnBurrit0 <77340197+mimartin12@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:39:06 -0600 Subject: [PATCH 50/86] [DEVOPS-1919] - Slack messages contain the incorrect git commit sha (#8813) * Initial run to see what data I can access * Update to use JQ * Use dev action * Implement artifact build sha - Moved notify job to happen post artifact check - Removed git sha job - Updated jobs to use real artifact sha * Update .github/workflows/deploy-web.yml Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> * Handle web build triggers - Update GH environment with commit as well --------- Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> --- .github/workflows/deploy-web.yml | 148 ++++++++++++++++--------------- 1 file changed, 77 insertions(+), 71 deletions(-) diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index 769e700588..6a5d9f1405 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -128,29 +128,90 @@ jobs: - name: Success Code run: exit 0 - get-branch-or-tag-sha: - name: Get Branch or Tag SHA + artifact-check: + name: Check if Web artifact is present runs-on: ubuntu-22.04 + needs: setup + env: + _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment-artifact }} outputs: - branch-or-tag-sha: ${{ steps.get-branch-or-tag-sha.outputs.sha }} + artifact-build-commit: ${{ steps.set-artifact-commit.outputs.commit }} steps: - - name: Checkout Branch - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: 'Download latest cloud asset using GitHub Run ID: ${{ inputs.build-web-run-id }}' + if: ${{ inputs.build-web-run-id }} + uses: bitwarden/gh-actions/download-artifacts@main + id: download-latest-artifacts-run-id + continue-on-error: true with: - ref: ${{ inputs.branch-or-tag }} - fetch-depth: 0 + workflow: build-web.yml + path: apps/web + workflow_conclusion: success + run_id: ${{ inputs.build-web-run-id }} + artifacts: ${{ env._ENVIRONMENT_ARTIFACT }} - - name: Get Branch or Tag SHA - id: get-branch-or-tag-sha + - name: 'Download latest cloud asset from branch/tag: ${{ inputs.branch-or-tag }}' + if: ${{ !inputs.build-web-run-id }} + uses: bitwarden/gh-actions/download-artifacts@main + id: download-latest-artifacts + continue-on-error: true + with: + workflow: build-web.yml + path: apps/web + workflow_conclusion: success + branch: ${{ inputs.branch-or-tag }} + artifacts: ${{ env._ENVIRONMENT_ARTIFACT }} + + - name: Login to Azure + if: ${{ steps.download-latest-artifacts.outcome == 'failure' }} + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets for Build trigger + if: ${{ steps.download-latest-artifacts.outcome == 'failure' }} + id: retrieve-secret + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "github-pat-bitwarden-devops-bot-repo-scope" + + - name: 'Trigger build web for missing branch/tag ${{ inputs.branch-or-tag }}' + if: ${{ steps.download-latest-artifacts.outcome == 'failure' }} + uses: convictional/trigger-workflow-and-wait@f69fa9eedd3c62a599220f4d5745230e237904be # v1.6.5 + id: trigger-build-web + with: + owner: bitwarden + repo: clients + github_token: ${{ steps.retrieve-secret.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + workflow_file_name: build-web.yml + ref: ${{ inputs.branch-or-tag }} + wait_interval: 100 + + - name: Set artifact build commit + id: set-artifact-commit + env: + GH_TOKEN: ${{ github.token }} run: | - echo "sha=$(git rev-parse origin/${{ inputs.branch-or-tag }})" >> $GITHUB_OUTPUT + # If run-id was used, get the commit from the download-latest-artifacts-run-id step + if [ "${{ inputs.build-web-run-id }}" ]; then + echo "commit=${{ steps.download-latest-artifacts-run-id.outputs.artifact-build-commit }}" >> $GITHUB_OUTPUT + + elif [ "${{ steps.download-latest-artifacts.outcome }}" == "failure" ]; then + # If the download-latest-artifacts step failed, query the GH API to get the commit SHA of the artifact that was just built with trigger-build-web. + commit=$(gh api /repos/bitwarden/clients/actions/runs/${{ steps.trigger-build-web.outputs.workflow_id }}/artifacts --jq '.artifacts[0].workflow_run.head_sha') + echo "commit=$commit" >> $GITHUB_OUTPUT + + else + # Set the commit to the output of step download-latest-artifacts. + echo "commit=${{ steps.download-latest-artifacts.outputs.artifact-build-commit }}" >> $GITHUB_OUTPUT + fi notify-start: name: Notify Slack with start message needs: - approval - setup - - get-branch-or-tag-sha + - artifact-check runs-on: ubuntu-22.04 if: ${{ always() && contains( inputs.environment , 'QA' ) }} outputs: @@ -165,66 +226,10 @@ jobs: tag: ${{ inputs.branch-or-tag }} slack-channel: team-eng-qa-devops event: 'start' - commit-sha: ${{ needs.get-branch-or-tag-sha.outputs.branch-or-tag-sha }} + commit-sha: ${{ needs.artifact-check.outputs.artifact-build-commit }} url: https://github.com/bitwarden/clients/actions/runs/${{ github.run_id }} AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - artifact-check: - name: Check if Web artifact is present - runs-on: ubuntu-22.04 - needs: setup - env: - _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment-artifact }} - steps: - - name: 'Download latest cloud asset using GitHub Run ID: ${{ inputs.build-web-run-id }}' - if: ${{ inputs.build-web-run-id }} - uses: bitwarden/gh-actions/download-artifacts@main - id: download-latest-artifacts - continue-on-error: true - with: - workflow: build-web.yml - path: apps/web - workflow_conclusion: success - run_id: ${{ inputs.build-web-run-id }} - artifacts: ${{ env._ENVIRONMENT_ARTIFACT }} - - - name: 'Download latest cloud asset from branch/tag: ${{ inputs.branch-or-tag }}' - if: ${{ !inputs.build-web-run-id }} - uses: bitwarden/gh-actions/download-artifacts@main - id: download-artifacts - continue-on-error: true - with: - workflow: build-web.yml - path: apps/web - workflow_conclusion: success - branch: ${{ inputs.branch-or-tag }} - artifacts: ${{ env._ENVIRONMENT_ARTIFACT }} - - - name: Login to Azure - if: ${{ steps.download-artifacts.outcome == 'failure' }} - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets for Build trigger - if: ${{ steps.download-artifacts.outcome == 'failure' }} - id: retrieve-secret - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "github-pat-bitwarden-devops-bot-repo-scope" - - - name: 'Trigger build web for missing branch/tag ${{ inputs.branch-or-tag }}' - if: ${{ steps.download-artifacts.outcome == 'failure' }} - uses: convictional/trigger-workflow-and-wait@f69fa9eedd3c62a599220f4d5745230e237904be # v1.6.5 - with: - owner: bitwarden - repo: clients - github_token: ${{ steps.retrieve-secret.outputs.github-pat-bitwarden-devops-bot-repo-scope }} - workflow_file_name: build-web.yml - ref: ${{ inputs.branch-or-tag }} - wait_interval: 100 - azure-deploy: name: Deploy Web Vault to ${{ inputs.environment }} Storage Account needs: @@ -248,6 +253,7 @@ jobs: environment: ${{ env._ENVIRONMENT_NAME }} task: 'deploy' description: 'Deployment from branch/tag: ${{ inputs.branch-or-tag }}' + ref: ${{ needs.artifact-check.outputs.artifact-build-commit }} - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -349,10 +355,10 @@ jobs: runs-on: ubuntu-22.04 if: ${{ always() && contains( inputs.environment , 'QA' ) }} needs: + - setup - notify-start - azure-deploy - - setup - - get-branch-or-tag-sha + - artifact-check steps: - uses: bitwarden/gh-actions/report-deployment-status-to-slack@main with: @@ -362,6 +368,6 @@ jobs: slack-channel: ${{ needs.notify-start.outputs.channel_id }} event: ${{ needs.azure-deploy.result }} url: https://github.com/bitwarden/clients/actions/runs/${{ github.run_id }} - commit-sha: ${{ needs.get-branch-or-tag-sha.outputs.branch-or-tag-sha }} + commit-sha: ${{ needs.artifact-check.outputs.artifact-build-commit }} update-ts: ${{ needs.notify-start.outputs.ts }} AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} From 26b3259c705178e499f55fbcbd29ccc77c2d8f94 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 19:40:20 +0000 Subject: [PATCH 51/86] Autosync the updated translations (#8837) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 9 ++ apps/web/src/locales/ar/messages.json | 9 ++ apps/web/src/locales/az/messages.json | 9 ++ apps/web/src/locales/be/messages.json | 9 ++ apps/web/src/locales/bg/messages.json | 63 +++++++----- apps/web/src/locales/bn/messages.json | 9 ++ apps/web/src/locales/bs/messages.json | 9 ++ apps/web/src/locales/ca/messages.json | 53 +++++----- apps/web/src/locales/cs/messages.json | 43 ++++---- apps/web/src/locales/cy/messages.json | 9 ++ apps/web/src/locales/da/messages.json | 9 ++ apps/web/src/locales/de/messages.json | 9 ++ apps/web/src/locales/el/messages.json | 9 ++ apps/web/src/locales/en_GB/messages.json | 9 ++ apps/web/src/locales/en_IN/messages.json | 9 ++ apps/web/src/locales/eo/messages.json | 9 ++ apps/web/src/locales/es/messages.json | 9 ++ apps/web/src/locales/et/messages.json | 9 ++ apps/web/src/locales/eu/messages.json | 9 ++ apps/web/src/locales/fa/messages.json | 9 ++ apps/web/src/locales/fi/messages.json | 119 ++++++++++++----------- apps/web/src/locales/fil/messages.json | 9 ++ apps/web/src/locales/fr/messages.json | 43 ++++---- apps/web/src/locales/gl/messages.json | 9 ++ apps/web/src/locales/he/messages.json | 9 ++ apps/web/src/locales/hi/messages.json | 9 ++ apps/web/src/locales/hr/messages.json | 9 ++ apps/web/src/locales/hu/messages.json | 11 ++- apps/web/src/locales/id/messages.json | 9 ++ apps/web/src/locales/it/messages.json | 11 ++- apps/web/src/locales/ja/messages.json | 63 +++++++----- apps/web/src/locales/ka/messages.json | 9 ++ apps/web/src/locales/km/messages.json | 9 ++ apps/web/src/locales/kn/messages.json | 9 ++ apps/web/src/locales/ko/messages.json | 9 ++ apps/web/src/locales/lv/messages.json | 15 ++- apps/web/src/locales/ml/messages.json | 9 ++ apps/web/src/locales/mr/messages.json | 9 ++ apps/web/src/locales/my/messages.json | 9 ++ apps/web/src/locales/nb/messages.json | 9 ++ apps/web/src/locales/ne/messages.json | 9 ++ apps/web/src/locales/nl/messages.json | 9 ++ apps/web/src/locales/nn/messages.json | 9 ++ apps/web/src/locales/or/messages.json | 9 ++ apps/web/src/locales/pl/messages.json | 9 ++ apps/web/src/locales/pt_BR/messages.json | 9 ++ apps/web/src/locales/pt_PT/messages.json | 9 ++ apps/web/src/locales/ro/messages.json | 9 ++ apps/web/src/locales/ru/messages.json | 9 ++ apps/web/src/locales/si/messages.json | 9 ++ apps/web/src/locales/sk/messages.json | 91 +++++++++-------- apps/web/src/locales/sl/messages.json | 9 ++ apps/web/src/locales/sr/messages.json | 9 ++ apps/web/src/locales/sr_CS/messages.json | 9 ++ apps/web/src/locales/sv/messages.json | 21 ++-- apps/web/src/locales/te/messages.json | 9 ++ apps/web/src/locales/th/messages.json | 9 ++ apps/web/src/locales/tr/messages.json | 9 ++ apps/web/src/locales/uk/messages.json | 9 ++ apps/web/src/locales/vi/messages.json | 9 ++ apps/web/src/locales/zh_CN/messages.json | 13 ++- apps/web/src/locales/zh_TW/messages.json | 9 ++ 62 files changed, 777 insertions(+), 219 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 51ba16e5e0..2c226ec0b8 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 4377e5655b..be25dead79 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 2e94d219d8..d53278e93a 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "Bu kolleksiya yalnız admin konsolundan əlçatandır" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 2cfb8af125..7c3b76b610 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 05a819f97a..f4ee30ba95 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -7607,7 +7607,7 @@ "message": "Портал за доставчици" }, "success": { - "message": "Success" + "message": "Успех" }, "viewCollection": { "message": "Преглед на колекцията" @@ -7907,7 +7907,7 @@ "message": "Не може да добавяте себе си към групи." }, "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "message": "Известие: от 2 май 2024г. неразпределените елементи на организациите вече няма се виждат в изгледа с „Всички трезори“ на различните устройства, а ще бъдат достъпни само през Административната конзола. Добавете тези елементи към някоя колекция в Административната конзола, за да станат видими." }, "unassignedItemsBannerNotice": { "message": "Известие: неразпределените елементи в организацията вече няма да се виждат в изгледа с всички трезори на различните устройства, а са достъпни само през Административната конзола." @@ -7966,79 +7966,88 @@ "message": "Грешка при задаването на целева папка." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "Интеграции и набори за разработка (SDK)", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Интеграции" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Автоматично синхронизиране на тайните от „Управлението на тайни“ на Битуорден към външна услуга." }, "sdks": { - "message": "SDKs" + "message": "Набори за разработка (SDK)" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "Използвайте набора за разработка (SDK) за Управлението на тайни на Битуорден със следните програмни езици, за да създадете свои собствени приложения." }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "Настройка на действия в Github" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "Настройка на GitLab CI/CD" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Настройка на Ansible" }, "cSharpSDKRepo": { - "message": "View C# repository" + "message": "Преглед на хранилището за C#" }, "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "message": "Преглед на хранилището за C++" }, "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "message": "Преглед на хранилището за JS WebAssembly" }, "javaSDKRepo": { - "message": "View Java repository" + "message": "Преглед на хранилището за Java" }, "pythonSDKRepo": { - "message": "View Python repository" + "message": "Преглед на хранилището за Python" }, "phpSDKRepo": { - "message": "View php repository" + "message": "Преглед на хранилището за php" }, "rubySDKRepo": { - "message": "View Ruby repository" + "message": "Преглед на хранилището за Ruby" }, "goSDKRepo": { - "message": "View Go repository" + "message": "Преглед на хранилището за Go" }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "Създайте нова организация, която да управлявате като доставчик. Допълнителните места ще бъдат отразени в следващия платежен период." }, "selectAPlan": { - "message": "Select a plan" + "message": "Изберете план" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "Отстъпка от 35%" }, "monthPerMember": { - "message": "month per member" + "message": "на месец за член" }, "seats": { - "message": "Seats" + "message": "Места" }, "addOrganization": { - "message": "Add organization" + "message": "Добавяне на организация" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "Новият клиент е създаден успешно" }, "noAccess": { - "message": "No access" + "message": "Нямате достъп" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "Тази колекция е достъпна само през административната конзола" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 46d0e574ce..9b60b1149b 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 85880f9cbf..3b2b0f91bf 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index cc072c3b96..bf3071b506 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -7607,7 +7607,7 @@ "message": "Portal del proveïdor" }, "success": { - "message": "Success" + "message": "Èxit" }, "viewCollection": { "message": "Mostra col·lecció" @@ -7637,10 +7637,10 @@ "message": "No s'ha assignat cap col·lecció" }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "Col·leccions assignades correctament" }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "Heu seleccionat $TOTAL_COUNT$ elements. No podeu actualitzar-ne $READONLY_COUNT$ dels quals perquè no teniu permisos d'edició.", "placeholders": { "total_count": { "content": "$1", @@ -7656,10 +7656,10 @@ "message": "Elements" }, "assignedSeats": { - "message": "Assigned seats" + "message": "Seients assignats" }, "assigned": { - "message": "Assigned" + "message": "Assignat" }, "used": { "message": "Utilitzat" @@ -7668,40 +7668,40 @@ "message": "Queden" }, "unlinkOrganization": { - "message": "Unlink organization" + "message": "Desenllaça l'organització" }, "manageSeats": { - "message": "MANAGE SEATS" + "message": "GESTIONA SEIENTS" }, "manageSeatsDescription": { - "message": "Adjustments to seats will be reflected in the next billing cycle." + "message": "Els ajustos dels seients es reflectiran en el pròxim cicle de facturació." }, "unassignedSeatsDescription": { - "message": "Unassigned subscription seats" + "message": "Seients de subscripció no assignats" }, "purchaseSeatDescription": { - "message": "Additional seats purchased" + "message": "Seients addicionals adquirits" }, "assignedSeatCannotUpdate": { - "message": "Assigned Seats can not be updated. Please contact your organization owner for assistance." + "message": "Els seients assignats no es poden actualitzar. Poseu-vos en contacte amb el propietari de l'organització per obtenir ajuda." }, "subscriptionUpdateFailed": { - "message": "Subscription update failed" + "message": "L'actualització de la subscripció ha fallat" }, "trial": { - "message": "Trial", + "message": "Prova", "description": "A subscription status label." }, "pastDue": { - "message": "Past due", + "message": "Vençuda", "description": "A subscription status label" }, "subscriptionExpired": { - "message": "Subscription expired", + "message": "La subscripció ha caducat", "description": "The date header used when a subscription is past due." }, "pastDueWarningForChargeAutomatically": { - "message": "You have a grace period of $DAYS$ days from your subscription expiration date to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.", + "message": "Teniu un període de gràcia de $DAYS$ dies a partir de la data de caducitat de la vostra subscripció per mantenir-la. Resoleu les factures vençudes abans de $SUSPENSION_DATE$.", "placeholders": { "days": { "content": "$1", @@ -7715,7 +7715,7 @@ "description": "A warning shown to the user when their subscription is past due and they are charged automatically." }, "pastDueWarningForSendInvoice": { - "message": "You have a grace period of $DAYS$ days from the date your first unpaid invoice is due to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.", + "message": "Teniu un període de gràcia de $DAYS$ dies a partir de la data en què cal que la primera factura no pagada mantinga la subscripció. Resoleu les factures vençudes abans de $SUSPENSION_DATE$.", "placeholders": { "days": { "content": "$1", @@ -7729,22 +7729,22 @@ "description": "A warning shown to the user when their subscription is past due and they pay via invoice." }, "unpaidInvoice": { - "message": "Unpaid invoice", + "message": "Factura no pagada", "description": "The header of a warning box shown to a user whose subscription is unpaid." }, "toReactivateYourSubscription": { - "message": "To reactivate your subscription, please resolve the past due invoices.", + "message": "Per reactivar la subscripció, resoleu les factures vençudes.", "description": "The body of a warning box shown to a user whose subscription is unpaid." }, "cancellationDate": { - "message": "Cancellation date", + "message": "Data de cancel·lació", "description": "The date header used when a subscription is cancelled." }, "machineAccountsCannotCreate": { - "message": "Machine accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "No es poden crear comptes de màquina en organitzacions suspeses. Poseu-vos en contacte amb el propietari de l'organització per obtenir ajuda." }, "machineAccount": { - "message": "Machine account", + "message": "Compte de màquina", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "machineAccounts": { @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 6b82766cd3..b6e62aef9a 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -7907,7 +7907,7 @@ "message": "Do skupiny nemůžete přidat sami sebe." }, "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "message": "Upozornění: Dne 2. května 2024 již nebudou nepřiřazené položky organizace viditelné v zobrazení Všechny trezory ve všech zařízeních a budou přístupné jen prostřednictvím konzoly správce. Přiřaďte tyto položky do kolekce z konzoly pro správce, aby byly viditelné." }, "unassignedItemsBannerNotice": { "message": "Upozornění: Nepřiřazené položky organizace již nejsou viditelné ve vašem zobrazení všech trezorů napříč zařízeními a jsou nyní přístupné pouze v konzoli správce." @@ -7966,53 +7966,53 @@ "message": "Chyba při přiřazování cílové složky." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "Integrace a SDK", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Integrace" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Automaticky synchronizuje tajné klíče se správce tajných klíčů Bitwardenu do služby třetí strany." }, "sdks": { - "message": "SDKs" + "message": "SDK" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "Použije SDK správce tajných klíčů Bitwardenu v následujících programovacích jazycích k vytvoření vlastních aplikací." }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "Nastavit akce GitHubu" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "Nastavit GitLab CI/CD" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Nastavit Ansible" }, "cSharpSDKRepo": { - "message": "View C# repository" + "message": "Zobrazit repozitář C#" }, "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "message": "Zobrazit repozitář C++" }, "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "message": "Zobrazit repozitář JS WebAssembly" }, "javaSDKRepo": { - "message": "View Java repository" + "message": "Zobrazit repozitář Java" }, "pythonSDKRepo": { - "message": "View Python repository" + "message": "Zobrazit repozitář Python" }, "phpSDKRepo": { - "message": "View php repository" + "message": "Zobrazit repozitář PHP" }, "rubySDKRepo": { - "message": "View Ruby repository" + "message": "Zobrazit repozitář Ruby" }, "goSDKRepo": { - "message": "View Go repository" + "message": "Zobrazit repozitář Go" }, "createNewClientToManageAsProvider": { "message": "Vytvořte novou klientskou organizaci pro správu jako poskytovatele. Další uživatelé budou reflektováni v dalším platebním cyklu." @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 7394c3fe2a..961b3c93a3 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 081fef7865..e5042229fe 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "Denne samling er kun tilgængelig via Admin-konsol" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 3fb7598416..bd2e0946f6 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 742a76402d..ed374326fe 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 66f728cb4d..3b0a99715f 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index c0c77d3d78..1868130a1c 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 2a6db2ea4b..88509a8299 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 9f0f085e9d..e851539df1 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 0c3f7cb300..1cef83a823 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index c315d62520..bf79583e58 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 32feda3edd..0621239c5e 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 377f7b084f..4cc682caa2 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -630,7 +630,7 @@ "message": "Suojausavain ei kelpaa. Yritä uudelleen." }, "twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn": { - "message": "Kaksivaiheista salausavainkirjautumista ei tueta. Päivitä sovellus kirjautuaksesi sisään." + "message": "Kaksivaiheista suojausavainkirjautumista ei tueta. Päivitä sovellus kirjautuaksesi sisään." }, "loginWithPasskeyInfo": { "message": "Käytä generoitua suojausavainta, joka kirjaa sinut automaattisesti sisään ilman salasanaa. Henkilöllisyytesi vahvistetaan kasvojen tunnistuksen tai sormenjäljen kataisilla biometrisillä tiedoilla, tai jollakin muulla FIDO2-suojausratkaisulla." @@ -2799,7 +2799,7 @@ "message": "Verkkoholvi" }, "cli": { - "message": "komentorivi" + "message": "Komentorivi" }, "bitWebVault": { "message": "Bitwarden Verkkoholvi" @@ -4948,10 +4948,10 @@ "message": "Uusi asiakasorganisaatio" }, "newClientOrganizationDesc": { - "message": "Luo uusi asiakasorganisaatio, joka liitetään sinuun toimittajana. Voit käyttää ja hallita tätä organisaatiota." + "message": "Luo uusi asiakasorganisaatio, jonka toimittajaksi sinut määritetään. Voit käyttää ja hallita tätä organisaatiota." }, "newClient": { - "message": "Uusi pääte" + "message": "Uusi asiakas" }, "addExistingOrganization": { "message": "Lisää olemassa oleva organisaatio" @@ -6238,7 +6238,7 @@ "description": "Title for creating a new project." }, "softDeleteSecretWarning": { - "message": "Salaisuuksien poistaminen voi vaikuttaa olemassa oleviin integrointeihin.", + "message": "Salaisuuksien poistaminen voi vaikuttaa olemassa oleviin integraatioihin.", "description": "Warns that deleting secrets can have consequences on integrations" }, "softDeletesSuccessToast": { @@ -7071,7 +7071,7 @@ "description": "The individual description shown to the user when the user doesn't have access to delete a project." }, "smProjectsDeleteBulkConfirmation": { - "message": "Seuraavien projektien poistaminen ei ole mahdollista. Haluatko jatkaa?", + "message": "Seuraavia projekteja ei ole mahdollista poistaa. Haluatko jatkaa?", "description": "The message shown to the user when bulk deleting projects and the user doesn't have access to some projects." }, "updateKdfSettings": { @@ -7217,7 +7217,7 @@ "message": "Tilille ei ole asetettu pääsalasanaa" }, "removeOrgUserNoMasterPasswordDesc": { - "message": "Käyttäjän $USER$ poistaminen asettamatta hänen tililleen pääsalasanaa voi estää häntä kirjautumasta hänen omalle tililleen. Haluatko varmasti jatkaa?", + "message": "Käyttäjän $USER$ poistaminen asettamatta hänen tililleen pääsalasanaa voi estää häntä kirjautumasta heidän omalle tililleen. Haluatko varmasti jatkaa?", "placeholders": { "user": { "content": "$1", @@ -7484,7 +7484,7 @@ "message": "Laajennuksella voit tallentaa kirjautumistietoja ja automaattitäyttää lomakkeita avaamatta verkkosovellusta." }, "projectAccessUpdated": { - "message": "Projektin käyttöoikeudet on muutettu" + "message": "Projektin käyttöoikeuksia muutettiin" }, "unexpectedErrorSend": { "message": "Odottamaton virhe ladattaessa Sendiä. Yritä myöhemmin uudelleen." @@ -7514,7 +7514,7 @@ "message": "Ylläpitäjät voivat käyttää ja hallinnoida kokoelmia." }, "serviceAccountAccessUpdated": { - "message": "Palvelutilin oikeuksia muutettiin" + "message": "Palvelutilin käyttöoikeuksia muutettiin" }, "commonImportFormats": { "message": "Yleiset muodot", @@ -7595,7 +7595,7 @@ "message": "Ilmainen 1 vuoden ajan" }, "newWebApp": { - "message": "Tervetuloa uuteen, entistä parempaan verkkosovellukseen. Lue lisää siitä, mikä on muuttunut." + "message": "Tervetuloa uuteen, entistä parempaan verkkosovellukseen. Katso mikä on muuttunut." }, "releaseBlog": { "message": "Lue julkaisublogia" @@ -7607,10 +7607,10 @@ "message": "Toimittajaportaali" }, "success": { - "message": "Success" + "message": "Onnistui" }, "viewCollection": { - "message": "Tarkastele kokoelmaa" + "message": "Näytä kokoelma" }, "restrictedGroupAccess": { "message": "Et voi lisätä itseäsi ryhmiin." @@ -7640,7 +7640,7 @@ "message": "Kokoelmat määritettiin" }, "bulkCollectionAssignmentWarning": { - "message": "Olet valinnut $TOTAL_COUNT$ kohdetta. Et voi päivittää näistä $READONLY_COUNT$ kohdetta, koska käyttöoikeutesi eivät salli muokkausta.", + "message": "Olet valinnut $TOTAL_COUNT$ kohdetta. Näistä $READONLY_COUNT$ et voi muuttaa, koska käyttöoikeutesi eivät salli muokkausta.", "placeholders": { "total_count": { "content": "$1", @@ -7668,7 +7668,7 @@ "message": "Jäljellä" }, "unlinkOrganization": { - "message": "Irrota organisaatio" + "message": "Poista organisaatioliitos" }, "manageSeats": { "message": "HALLITSE KÄYTTÄJÄPAIKKOJA" @@ -7904,33 +7904,33 @@ "message": "Konetilin oikeuksia muutettiin" }, "restrictedGroupAccessDesc": { - "message": "You cannot add yourself to a group." + "message": "Et voi lisätä itseäsi ryhmään." }, "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "message": "Huomioi: Alkaen 2. toukokuuta 2024, määrittämättömät organisaatiokohteet eivät enää näy laitteidesi \"Kaikki holvit\" -näkymissä, vaan ne ovat käytettävissä vain Hallintapaneelista." }, "unassignedItemsBannerNotice": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + "message": "Huomioi: Määrittämättömät organisaatiokohteet eivät enää näy laitteidesi \"Kaikki holvit\" -näkymissä, vaan ne ovat käytettävissä vain Hallintapaneelista." }, "unassignedItemsBannerSelfHostNotice": { - "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + "message": "Huomioi: Alkaen 16. toukokuuta 2024, määrittämättömät organisaatiokohteet eivät enää näy laitteidesi \"Kaikki holvit\" -näkymissä, vaan ne ovat käytettävissä vain Hallintapaneelista." }, "unassignedItemsBannerCTAPartOne": { - "message": "Assign these items to a collection from the", + "message": "Määritä nämä kohteet kokoelmaan", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "unassignedItemsBannerCTAPartTwo": { - "message": "to make them visible.", + "message": ", jotta ne näkyvät.", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "deleteProvider": { - "message": "Delete provider" + "message": "Poista toimittaja" }, "deleteProviderConfirmation": { - "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + "message": "Toimittajan poisto on pysyvää ja peruuttamatonta. Vahvista palveluntarjoajan ja kaikkien siihen liittyvien tietojen poisto syöttämällä pääsalasanasi." }, "deleteProviderName": { - "message": "Cannot delete $ID$", + "message": "Toimittajaa $ID$ ei ole mahdollista poistaa", "placeholders": { "id": { "content": "$1", @@ -7939,7 +7939,7 @@ } }, "deleteProviderWarningDesc": { - "message": "You must unlink all clients before you can delete $ID$", + "message": "Kaikki liitetyt asiakkaat on poistettava ennen toimittajan $ID$ poistoa.", "placeholders": { "id": { "content": "$1", @@ -7948,97 +7948,106 @@ } }, "providerDeleted": { - "message": "Provider deleted" + "message": "Toimittaja poistettiin" }, "providerDeletedDesc": { - "message": "The Provider and all associated data has been deleted." + "message": "Toimittaja ja kaikki siihen liittyvät tiedot on poistettu." }, "deleteProviderRecoverConfirmDesc": { - "message": "You have requested to delete this Provider. Use the button below to confirm." + "message": "Olet pyytänyt tämän toimittajan poistoa. Vahvista alla olevalla painikeella." }, "deleteProviderWarning": { - "message": "Deleting your provider is permanent. It cannot be undone." + "message": "Toimittajasi poisto on pysyvä toimenpide, eikä sen peruminen ole mahdollista." }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Virhe määritettäessä kohdekokoelmaa." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Virhe määritettäessä kohdekansiota." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "Integraatiot ja SDK:t", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Integraatiot" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Synkronoi salaisuudet automaattisesti Bitwardenin Salaisuushallinnan ja ulkopuolisen palvelun välillä." }, "sdks": { - "message": "SDKs" + "message": "SDK:t" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "Bitwardenin Salaisuushallinnan SDK:n avulla voit kehittää omia sovelluksiasi seuraavilla ohjelmointikielillä." }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "Määritä GitHub Actions" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "Määritä GitLab CI/CD" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Määritä Ansible" }, "cSharpSDKRepo": { - "message": "View C# repository" + "message": "Näytä C#-arkisto" }, "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "message": "Näytä C++-arkisto" }, "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "message": "Näytä JS WebAssembly -arkisto" }, "javaSDKRepo": { - "message": "View Java repository" + "message": "Näytä Java-arkisto" }, "pythonSDKRepo": { - "message": "View Python repository" + "message": "Näytä Python-arkisto" }, "phpSDKRepo": { - "message": "View php repository" + "message": "Näytä php-arkisto" }, "rubySDKRepo": { - "message": "View Ruby repository" + "message": "Näytä Ruby-arkisto" }, "goSDKRepo": { - "message": "View Go repository" + "message": "Näytä Go-arkisto" }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "Luo uusi asiakasorganisaatio, jota hallitset toimittajana. Uudet käyttäjäpaikat näkyvät seuraavalla laskutusjaksolla." }, "selectAPlan": { - "message": "Select a plan" + "message": "Valitse tilaus" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "35 %:n alennus" }, "monthPerMember": { - "message": "month per member" + "message": "kuukaudessa/jäsen" }, "seats": { - "message": "Seats" + "message": "Käyttäjäpaikat" }, "addOrganization": { - "message": "Add organization" + "message": "Lisää organisaatio" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "Uuden asiakkaan luonti onnistui." }, "noAccess": { - "message": "No access" + "message": "Käyttöoikeutta ei ole" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "Tämä kokoelma on käytettävissä vain hallintakonsolista" + }, + "organizationOptionsMenu": { + "message": "Näytä/piilota organisaatiovalikko" + }, + "vaultItemSelect": { + "message": "Valitse holvin kohde" + }, + "collectionItemSelect": { + "message": "Valitse kokoelman kohde" } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 50c9f8d4e8..eb3094e043 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 2934176956..1f1f343897 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -7907,7 +7907,7 @@ "message": "Vous ne pouvez pas vous ajouter vous-même à un groupe." }, "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "message": "Remarque : le 2 mai 2024, les éléments d'organisation non assignés ne seront plus visibles dans votre vue Tous les coffres sur les appareils et seront uniquement accessibles via la Console Admin. Assignez ces éléments à une collection à partir de la Console Admin pour les rendre visibles." }, "unassignedItemsBannerNotice": { "message": "Remarque : Les éléments d'organisation non assignés ne sont plus visibles dans la vue Tous les coffres sur les appareils et ne sont maintenant accessibles que via la Console Admin." @@ -7966,53 +7966,53 @@ "message": "Erreur lors de l'assignation du dossier cible." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "Intégrations & SDKs", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Intégrations" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Synchroniser automatiquement les secrets à partir du Secrets Manager de Bitwarden vers un service tiers." }, "sdks": { - "message": "SDKs" + "message": "SDK" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "Utilisez Bitwarden Secrets Manager SDK dans les langages de programmation suivants pour construire vos propres applications." }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "Configurer Github Actions" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "Configurer GitLab CI/CD" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Configurer Ansible" }, "cSharpSDKRepo": { - "message": "View C# repository" + "message": "Afficher le dépôt C#" }, "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "message": "Afficher le dépôt C++" }, "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "message": "Afficher le dépôt JS WebAssembly" }, "javaSDKRepo": { - "message": "View Java repository" + "message": "Afficher le dépôt Java" }, "pythonSDKRepo": { - "message": "View Python repository" + "message": "Afficher le dépôt Python" }, "phpSDKRepo": { - "message": "View php repository" + "message": "Afficher le dépôt php" }, "rubySDKRepo": { - "message": "View Ruby repository" + "message": "Afficher le dépôt Ruby" }, "goSDKRepo": { - "message": "View Go repository" + "message": "Afficher le dépôt Go" }, "createNewClientToManageAsProvider": { "message": "Créez une nouvelle organisation de clients à gérer en tant que Fournisseur. Des sièges supplémentaires seront reflétés lors du prochain cycle de facturation." @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "Cette collection n'est accessible qu'à partir de la Console Admin" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index ed04c3a3ef..13fa5f60c0 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index b7b4d59227..b57005a76b 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 23321b27ef..818cc1cef5 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index edc6adaf3b..ffeb8c2201 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 4ec6f15029..b85733325b 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -7607,7 +7607,7 @@ "message": "Szolgáltató portál" }, "success": { - "message": "Success" + "message": "Sikeres" }, "viewCollection": { "message": "Gyűjtemény megtekintése" @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "Ez a gyűjtemény csak az adminisztrátori konzolról érhető el." + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 3bc9ef4e62..df1af27271 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 11dec69959..04342f13b1 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -7607,7 +7607,7 @@ "message": "Portale Fornitori" }, "success": { - "message": "Success" + "message": "Successo" }, "viewCollection": { "message": "Visualizza raccolta" @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Attiva/Disattiva Menu Organizzazione" + }, + "vaultItemSelect": { + "message": "Seleziona elemento della cassaforte" + }, + "collectionItemSelect": { + "message": "Seleziona elemento della raccolta" } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 17b58a4299..a701dd6e8d 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -7607,7 +7607,7 @@ "message": "プロバイダーポータル" }, "success": { - "message": "Success" + "message": "成功" }, "viewCollection": { "message": "コレクションを表示" @@ -7907,7 +7907,7 @@ "message": "あなた自身をグループに追加することはできません。" }, "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "message": "お知らせ:2024年5月2日に、 割り当てられていない組織アイテムはデバイス間のすべての保管庫ビューに表示されなくなり、管理コンソールからのみアクセス可能になります。 管理コンソールからコレクションにこれらのアイテムを割り当てると、表示できるようになります。" }, "unassignedItemsBannerNotice": { "message": "注意: 割り当てられていない組織アイテムは、デバイス間のすべての保管庫ビューでは表示されなくなり、管理コンソールからのみアクセスできるようになりました。" @@ -7966,79 +7966,88 @@ "message": "ターゲットフォルダーの割り当てに失敗しました。" }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "連携&SDK", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "システム連携" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Bitwarden シークレットマネージャーのシークレットを、サードパーティーのサービスに自動的に同期します。" }, "sdks": { - "message": "SDKs" + "message": "SDK" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "Bitwarden シークレットマネージャー SDK を以下のプログラミング言語で使用して、独自のアプリを構築できます。" }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "Github アクションを設定" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "GitLab CI/CD の設定" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Ansible を設定" }, "cSharpSDKRepo": { - "message": "View C# repository" + "message": "C# リポジトリを表示" }, "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "message": "C++ リポジトリを表示" }, "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "message": "JS WebAssembly リポジトリを表示" }, "javaSDKRepo": { - "message": "View Java repository" + "message": "Java リポジトリを表示" }, "pythonSDKRepo": { - "message": "View Python repository" + "message": "Python リポジトリを表示" }, "phpSDKRepo": { - "message": "View php repository" + "message": "PHP リポジトリを表示" }, "rubySDKRepo": { - "message": "View Ruby repository" + "message": "Ruby リポジトリを表示" }, "goSDKRepo": { - "message": "View Go repository" + "message": "Go リポジトリを表示" }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "プロバイダーとして管理するための新しいクライアント組織を作成します。次の請求サイクルに追加のシートが反映されます。" }, "selectAPlan": { - "message": "Select a plan" + "message": "プランを選択" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "35%割引" }, "monthPerMember": { - "message": "month per member" + "message": "月/メンバーあたり" }, "seats": { - "message": "Seats" + "message": "シート" }, "addOrganization": { - "message": "Add organization" + "message": "組織を追加" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "新しいクライアントを作成しました" }, "noAccess": { - "message": "No access" + "message": "アクセス不可" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "このコレクションは管理コンソールからのみアクセス可能です" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 6d34252b88..defe9e621d 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index ed04c3a3ef..13fa5f60c0 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index ae6f2c2a89..cfb188b580 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index f9240f21aa..9dd856e656 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 0dc0dff097..b20f350231 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -7607,7 +7607,7 @@ "message": "Nodrošinātāju portāls" }, "success": { - "message": "Success" + "message": "Izdevās" }, "viewCollection": { "message": "Skatīt krājumu" @@ -8036,9 +8036,18 @@ "message": "Successfully created new client" }, "noAccess": { - "message": "No access" + "message": "Nav piekļuves" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "Šis krājums ir pieejams tikai pārvaldības konsolē" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 9b6d1ab565..c2b83a5e28 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index ed04c3a3ef..13fa5f60c0 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index ed04c3a3ef..13fa5f60c0 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 4e5fb13d15..2ffdc5214f 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index f411847571..7854d49a7b 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 7bf9b4018f..b9f405cb06 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "Deze collectie is alleen toegankelijk vanaf de admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 3d54ea70fd..53ac66738e 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index ed04c3a3ef..13fa5f60c0 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 57e0eb1929..c54b72c21c 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "Ta kolekcja jest dostępna tylko z konsoli administracyjnej" + }, + "organizationOptionsMenu": { + "message": "Przełącz menu organizacji" + }, + "vaultItemSelect": { + "message": "Wybierz element sejfu" + }, + "collectionItemSelect": { + "message": "Wybierz element kolekcji" } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 4f43c2aad7..2b296694bb 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index c2f2fbeee3..961d4531fa 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "Esta coleção só é acessível a partir da Consola de administração" + }, + "organizationOptionsMenu": { + "message": "Alternar menu da organização" + }, + "vaultItemSelect": { + "message": "Selecionar item do cofre" + }, + "collectionItemSelect": { + "message": "Selecionar item da coleção" } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 1a47866ebb..bd735d8f75 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 85ba7d5785..67b23b0e89 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "Эта коллекция доступна только из консоли администратора" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index e2c1e42427..0cd67041d3 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 234a4670b9..756723129f 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -344,7 +344,7 @@ "message": "Krstné meno" }, "middleName": { - "message": "Druhé meno" + "message": "Stredné meno" }, "lastName": { "message": "Priezvisko" @@ -2814,7 +2814,7 @@ "message": "Zmenené heslo k účtu." }, "enabledUpdated2fa": { - "message": "Dvojstupňové prihlasovanie zapnuté/aktualizované." + "message": "Dvojstupňové prihlasovanie uložené" }, "disabled2fa": { "message": "Dvojstupňové prihlasovanie vypnuté." @@ -3234,10 +3234,10 @@ "message": "Skupinový prístup" }, "groupAccessUserDesc": { - "message": "Upraviť skupiny, do ktorých patrí používateľ." + "message": "Povoľte členom prístup k zbierkam ich pridaním do jednej alebo viac skupín." }, "invitedUsers": { - "message": "Používatelia pozvaní." + "message": "Používatelia pozvaní" }, "resendInvitation": { "message": "Znovu poslať pozvánku" @@ -3246,7 +3246,7 @@ "message": "Znovu poslať e-mail" }, "hasBeenReinvited": { - "message": "$USER$ bol znovu pozvaný.", + "message": "$USER$ bol znovu pozvaný", "placeholders": { "user": { "content": "$1", @@ -3270,7 +3270,7 @@ } }, "confirmUsers": { - "message": "Potvrdiť používateľov" + "message": "Potvrdiť členov" }, "usersNeedConfirmed": { "message": "Máte používateľov, ktorí prijali pozvanie, ale ešte ich musíte potvrdiť. Používatelia nebudú mať prístup k organizácii, kým nebudú potvrdení." @@ -3294,13 +3294,13 @@ "message": "Skontrolujte si doručenú poštu, mali by ste obdržať odkaz pre verifikáciu." }, "emailVerified": { - "message": "Vaša emailová adresa bola overená." + "message": "Emailová adresa konta bola overená" }, "emailVerifiedFailed": { "message": "Overovanie zlyhalo. Skúste si odoslať nový verifikačný e-mail." }, "emailVerificationRequired": { - "message": "Vyžaduje sa overenie e-mailu" + "message": "Vyžaduje sa overenie e-mailom" }, "emailVerificationRequiredDesc": { "message": "Na používanie tejto funkcie musíte overiť svoj e-mail." @@ -7907,7 +7907,7 @@ "message": "You cannot add yourself to a group." }, "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "message": "Upozornenie: 2. mája nepriradené položky organizácie už nebudú viditeľné v zobrazení Všetky Trezory a budú prístupné len cez administrátorskú konzolu. Aby boli viditeľné, priraďte tieto položky do kolekcie z konzoly administrátora." }, "unassignedItemsBannerNotice": { "message": "Upozornenie: Nepriradené položky organizácie už nie sú viditeľné v zobrazení Všetky trezory a sú prístupné iba cez Správcovskú konzolu." @@ -7924,13 +7924,13 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "deleteProvider": { - "message": "Delete provider" + "message": "Odstrániť poskytovateľa" }, "deleteProviderConfirmation": { - "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + "message": "Odstránenie poskytovateľa je trvalé a nenávratné. Zadajte vaše hlavné heslo pre potvrdenie odstránenia poskytovateľa a súvisiacich dát." }, "deleteProviderName": { - "message": "Cannot delete $ID$", + "message": "$ID$ sa nedá odstrániť", "placeholders": { "id": { "content": "$1", @@ -7939,7 +7939,7 @@ } }, "deleteProviderWarningDesc": { - "message": "You must unlink all clients before you can delete $ID$", + "message": "Pred tým než budete môcť odstrániť $ID$, musíte odpojiť všetkých klientov", "placeholders": { "id": { "content": "$1", @@ -7948,16 +7948,16 @@ } }, "providerDeleted": { - "message": "Provider deleted" + "message": "Poskytovateľ odstránený" }, "providerDeletedDesc": { - "message": "The Provider and all associated data has been deleted." + "message": "Poskytovateľ a všetky súvisiace dáta boli odstránené." }, "deleteProviderRecoverConfirmDesc": { - "message": "You have requested to delete this Provider. Use the button below to confirm." + "message": "Požiadali ste o odstránenie tohto Poskytovateľa. Pre potvrdenie operácie použite tlačidlo nižšie." }, "deleteProviderWarning": { - "message": "Deleting your provider is permanent. It cannot be undone." + "message": "Odstránenie poskytovateľa je trvalé. Nedá sa vrátiť späť." }, "errorAssigningTargetCollection": { "message": "Chyba pri priraďovaní cieľovej kolekcie." @@ -7966,79 +7966,88 @@ "message": "Chyba pri priraďovaní cieľového priečinka." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "Integrácie a SDK", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Integrácie" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Automaticky synchronizovať položky z Bitwarden Secrets Manager do služby tretej strany." }, "sdks": { - "message": "SDKs" + "message": "SDK" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "Použite Bitwarden Secrets Manager SDK v následujúcich programovacích jazykoch pre vytvorenie vašej vlastnej aplikácie." }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "Nastaviť Github Actions" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "Nastaviť GitLab CI/CD" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Nastaviť Ansible" }, "cSharpSDKRepo": { - "message": "View C# repository" + "message": "Zobraziť C# repozitár" }, "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "message": "Zobraziť C++ repozitár" }, "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "message": "Zobraziť JS WebAssembly repozitár" }, "javaSDKRepo": { - "message": "View Java repository" + "message": "Zobraziť Java repozitár" }, "pythonSDKRepo": { - "message": "View Python repository" + "message": "Zobraziť Python repozitár" }, "phpSDKRepo": { - "message": "View php repository" + "message": "Zobraziť php repozitár" }, "rubySDKRepo": { - "message": "View Ruby repository" + "message": "Zobraziť Ruby repozitár" }, "goSDKRepo": { - "message": "View Go repository" + "message": "Zobraziť Go repozitár" }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "Vytvoriť novú klientskú organizáciu ktorú môžete spravovať ako Poskytovateľ. Dodatočné sedenia sa prejavia v najbližšom fakturačnom období." }, "selectAPlan": { - "message": "Select a plan" + "message": "Vyberte plán" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "Zľava 35%" }, "monthPerMember": { "message": "month per member" }, "seats": { - "message": "Seats" + "message": "Sedenia" }, "addOrganization": { - "message": "Add organization" + "message": "Pridať organizáciu" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "Nový klient úspešne vytvorený" }, "noAccess": { - "message": "No access" + "message": "Žiadny prístup" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "Táto zbierka je dostupná iba z administrátorskej konzoly" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index c209ef05bd..dfde437380 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index a59b6a8855..f927113fad 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 1ff89501f3..d6c354f4da 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 389f1682a8..21a100c5af 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -7966,17 +7966,17 @@ "message": "Error assigning target folder." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "Integrationer och SDK:er", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Integrationer" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Synkronisera automatiskt hemligheter från Bitwarden Secrets Manager till en tredjepartstjänst." }, "sdks": { - "message": "SDKs" + "message": "SDK:er" }, "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." @@ -7985,10 +7985,10 @@ "message": "Set up Github Actions" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "Ställ in GitLab CI/CD" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Ställ in Ansible" }, "cSharpSDKRepo": { "message": "View C# repository" @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index ed04c3a3ef..13fa5f60c0 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index ec114aad90..929c91e74b 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 9177321cb5..1087bc89bb 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index ecb31b6da0..c9434d72d6 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "Ця збірка доступна тільки з консолі адміністратора" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 0696e226e0..81fd3c2820 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index f6093d9bc7..a9a0d85457 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -7939,7 +7939,7 @@ } }, "deleteProviderWarningDesc": { - "message": "You must unlink all clients before you can delete $ID$", + "message": "删除 $ID$ 之前,您必须取消链接所有的客户端。", "placeholders": { "id": { "content": "$1", @@ -8018,7 +8018,7 @@ "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." }, "selectAPlan": { - "message": "Select a plan" + "message": "选择套餐" }, "thirtyFivePercentDiscount": { "message": "35% Discount" @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 1bc56d2157..42e47a6e31 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -8040,5 +8040,14 @@ }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" + }, + "organizationOptionsMenu": { + "message": "Toggle Organization Menu" + }, + "vaultItemSelect": { + "message": "Select vault item" + }, + "collectionItemSelect": { + "message": "Select collection item" } } From a2fc666823a696c39b6a0adcd7305d73250baf9a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 19:40:56 +0000 Subject: [PATCH 52/86] Autosync the updated translations (#8838) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 4 +- apps/browser/src/_locales/az/messages.json | 4 +- apps/browser/src/_locales/be/messages.json | 4 +- apps/browser/src/_locales/bg/messages.json | 6 +- apps/browser/src/_locales/bn/messages.json | 4 +- apps/browser/src/_locales/bs/messages.json | 4 +- apps/browser/src/_locales/ca/messages.json | 4 +- apps/browser/src/_locales/cs/messages.json | 4 +- apps/browser/src/_locales/cy/messages.json | 4 +- apps/browser/src/_locales/da/messages.json | 4 +- apps/browser/src/_locales/de/messages.json | 4 +- apps/browser/src/_locales/el/messages.json | 4 +- apps/browser/src/_locales/en_GB/messages.json | 4 +- apps/browser/src/_locales/en_IN/messages.json | 4 +- apps/browser/src/_locales/es/messages.json | 4 +- apps/browser/src/_locales/et/messages.json | 4 +- apps/browser/src/_locales/eu/messages.json | 4 +- apps/browser/src/_locales/fa/messages.json | 4 +- apps/browser/src/_locales/fi/messages.json | 14 +-- apps/browser/src/_locales/fil/messages.json | 4 +- apps/browser/src/_locales/fr/messages.json | 4 +- apps/browser/src/_locales/gl/messages.json | 4 +- apps/browser/src/_locales/he/messages.json | 4 +- apps/browser/src/_locales/hi/messages.json | 4 +- apps/browser/src/_locales/hr/messages.json | 4 +- apps/browser/src/_locales/hu/messages.json | 6 +- apps/browser/src/_locales/id/messages.json | 4 +- apps/browser/src/_locales/it/messages.json | 6 +- apps/browser/src/_locales/ja/messages.json | 4 +- apps/browser/src/_locales/ka/messages.json | 4 +- apps/browser/src/_locales/km/messages.json | 4 +- apps/browser/src/_locales/kn/messages.json | 4 +- apps/browser/src/_locales/ko/messages.json | 4 +- apps/browser/src/_locales/lt/messages.json | 4 +- apps/browser/src/_locales/lv/messages.json | 6 +- apps/browser/src/_locales/ml/messages.json | 4 +- apps/browser/src/_locales/mr/messages.json | 4 +- apps/browser/src/_locales/my/messages.json | 4 +- apps/browser/src/_locales/nb/messages.json | 4 +- apps/browser/src/_locales/ne/messages.json | 4 +- apps/browser/src/_locales/nl/messages.json | 4 +- apps/browser/src/_locales/nn/messages.json | 4 +- apps/browser/src/_locales/or/messages.json | 4 +- apps/browser/src/_locales/pl/messages.json | 4 +- apps/browser/src/_locales/pt_BR/messages.json | 4 +- apps/browser/src/_locales/pt_PT/messages.json | 4 +- apps/browser/src/_locales/ro/messages.json | 4 +- apps/browser/src/_locales/ru/messages.json | 4 +- apps/browser/src/_locales/si/messages.json | 4 +- apps/browser/src/_locales/sk/messages.json | 6 +- apps/browser/src/_locales/sl/messages.json | 4 +- apps/browser/src/_locales/sr/messages.json | 4 +- apps/browser/src/_locales/sv/messages.json | 4 +- apps/browser/src/_locales/te/messages.json | 4 +- apps/browser/src/_locales/th/messages.json | 4 +- apps/browser/src/_locales/tr/messages.json | 4 +- apps/browser/src/_locales/uk/messages.json | 4 +- apps/browser/src/_locales/vi/messages.json | 4 +- apps/browser/src/_locales/zh_CN/messages.json | 12 +- apps/browser/src/_locales/zh_TW/messages.json | 4 +- apps/browser/store/locales/ar/copy.resx | 108 ++++++++++-------- apps/browser/store/locales/az/copy.resx | 107 +++++++++-------- apps/browser/store/locales/be/copy.resx | 104 +++++++++++------ apps/browser/store/locales/bg/copy.resx | 107 +++++++++-------- apps/browser/store/locales/bn/copy.resx | 103 ++++++++++------- apps/browser/store/locales/bs/copy.resx | 103 ++++++++++------- apps/browser/store/locales/ca/copy.resx | 107 +++++++++-------- apps/browser/store/locales/cs/copy.resx | 107 +++++++++-------- apps/browser/store/locales/cy/copy.resx | 107 +++++++++-------- apps/browser/store/locales/da/copy.resx | 107 +++++++++-------- apps/browser/store/locales/de/copy.resx | 107 +++++++++-------- apps/browser/store/locales/el/copy.resx | 107 +++++++++-------- apps/browser/store/locales/en_GB/copy.resx | 103 ++++++++++------- apps/browser/store/locales/en_IN/copy.resx | 103 ++++++++++------- apps/browser/store/locales/es/copy.resx | 107 +++++++++-------- apps/browser/store/locales/et/copy.resx | 103 ++++++++++------- apps/browser/store/locales/eu/copy.resx | 107 +++++++++-------- apps/browser/store/locales/fa/copy.resx | 108 ++++++++++-------- apps/browser/store/locales/fi/copy.resx | 107 +++++++++-------- apps/browser/store/locales/fil/copy.resx | 103 ++++++++++++----- apps/browser/store/locales/fr/copy.resx | 107 +++++++++-------- apps/browser/store/locales/gl/copy.resx | 107 +++++++++-------- apps/browser/store/locales/he/copy.resx | 103 ++++++++++------- apps/browser/store/locales/hi/copy.resx | 103 ++++++++++------- apps/browser/store/locales/hr/copy.resx | 108 ++++++++++-------- apps/browser/store/locales/hu/copy.resx | 104 ++++++++++------- apps/browser/store/locales/id/copy.resx | 103 ++++++++++------- apps/browser/store/locales/it/copy.resx | 107 +++++++++-------- apps/browser/store/locales/ja/copy.resx | 107 +++++++++-------- apps/browser/store/locales/ka/copy.resx | 103 ++++++++++------- apps/browser/store/locales/km/copy.resx | 103 ++++++++++------- apps/browser/store/locales/kn/copy.resx | 108 ++++++++++-------- apps/browser/store/locales/ko/copy.resx | 107 +++++++++-------- apps/browser/store/locales/lt/copy.resx | 107 +++++++++-------- apps/browser/store/locales/lv/copy.resx | 107 +++++++++-------- apps/browser/store/locales/ml/copy.resx | 99 ++++++++++------ apps/browser/store/locales/mr/copy.resx | 103 ++++++++++------- apps/browser/store/locales/my/copy.resx | 103 ++++++++++------- apps/browser/store/locales/nb/copy.resx | 103 ++++++++++------- apps/browser/store/locales/ne/copy.resx | 103 ++++++++++------- apps/browser/store/locales/nl/copy.resx | 108 ++++++++++-------- apps/browser/store/locales/nn/copy.resx | 103 ++++++++++------- apps/browser/store/locales/or/copy.resx | 103 ++++++++++------- apps/browser/store/locales/pl/copy.resx | 108 ++++++++++-------- apps/browser/store/locales/pt_BR/copy.resx | 107 +++++++++-------- apps/browser/store/locales/pt_PT/copy.resx | 107 +++++++++-------- apps/browser/store/locales/ro/copy.resx | 107 +++++++++-------- apps/browser/store/locales/ru/copy.resx | 108 ++++++++++-------- apps/browser/store/locales/si/copy.resx | 103 ++++++++++------- apps/browser/store/locales/sk/copy.resx | 107 +++++++++-------- apps/browser/store/locales/sl/copy.resx | 108 ++++++++++-------- apps/browser/store/locales/sr/copy.resx | 107 +++++++++-------- apps/browser/store/locales/sv/copy.resx | 107 +++++++++-------- apps/browser/store/locales/te/copy.resx | 103 ++++++++++------- apps/browser/store/locales/th/copy.resx | 107 +++++++++-------- apps/browser/store/locales/tr/copy.resx | 107 +++++++++-------- apps/browser/store/locales/uk/copy.resx | 107 +++++++++-------- apps/browser/store/locales/vi/copy.resx | 107 +++++++++-------- apps/browser/store/locales/zh_CN/copy.resx | 108 ++++++++++-------- apps/browser/store/locales/zh_TW/copy.resx | 108 ++++++++++-------- 120 files changed, 3848 insertions(+), 2760 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index b7e26f3362..e08894be0b 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - مدير كلمات مرور مجاني", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "مدير كلمات مرور مجاني وآمن لجميع أجهزتك.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 20834af27f..1e5062d8c6 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Ödənişsiz Parol Meneceri", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bütün cihazlarınız üçün güvənli və ödənişsiz bir parol meneceri.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 630ad48ee6..91ff397b3a 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden – бясплатны менеджар пароляў", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Бяспечны і бясплатны менеджар пароляў для ўсіх вашых прылад.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index edfcd8a9b4..33be2608b4 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -3,11 +3,11 @@ "message": "Битуорден (Bitwarden)" }, "extName": { - "message": "Bitwarden", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Безопасно и безплатно управление за всичките ви устройства.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -3001,7 +3001,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Успех" }, "removePasskey": { "message": "Премахване на секретния ключ" diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 2ab44bf7e1..a12308648a 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "আপনার সমস্ত ডিভাইসের জন্য একটি সুরক্ষিত এবং বিনামূল্যের পাসওয়ার্ড ব্যবস্থাপক।", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 8d2a25db9c..7f406fabee 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 30d4c4e636..7c8bd63aea 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Administrador de contrasenyes segur i gratuït per a tots els vostres dispositius.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 42a4f9edb1..bd3c6882df 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden – Bezplatný správce hesel", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bezpečný a bezplatný správce hesel pro všechna Vaše zařízení.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 52d3cf7d56..c718c1d876 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Rheolydd cyfineiriau am ddim", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Rheolydd cyfrineiriau diogel a rhad ac am ddim ar gyfer eich holl ddyfeisiau.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 08aebe98e1..777c3b484f 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Gratis adgangskodemanager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "En sikker og gratis adgangskodemanager til alle dine enheder.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 4edca5557c..8f2a59af1e 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Kostenloser Passwortmanager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Ein sicherer und kostenloser Passwortmanager für all deine Geräte.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 75cd5ef2fa..8c65e61e53 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Δωρεάν Διαχειριστής Κωδικών", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Ένας ασφαλής και δωρεάν διαχειριστής κωδικών, για όλες σας τις συσκευές.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index b4b7b314db..e4d90adf1a 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 6dd78dc292..7cc17240d2 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 20b9a91814..3e488bce4c 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bitwarden es un gestor de contraseñas seguro y gratuito para todos tus dispositivos.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index c832528847..785a3e4986 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Tasuta paroolihaldur", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Turvaline ja tasuta paroolihaldur kõikidele sinu seadmetele.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 9504f06c65..9a07b9d9ae 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Pasahitz kudeatzailea", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bitwarden, zure gailu guztietarako pasahitzen kudeatzaile seguru eta doakoa da.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index c96c5c35cf..c68dc43ef4 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - مدیریت کلمه عبور رایگان", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "یک مدیریت کننده کلمه عبور رایگان برای تمامی دستگاه‌هایتان.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index b3602afd82..2cdb6a2379 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden – Ilmainen salasanahallinta", + "message": "Bitwarden – Salasanahallinta", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Turvallinen ja ilmainen salasanahallinta kaikille laitteillesi.", + "message": "Kotona, töissä tai reissussa, Bitwarden suojaa helposti kaikki salasanasi, avainkoodisi ja arkaluonteiset tietosi.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -3001,7 +3001,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Onnistui" }, "removePasskey": { "message": "Poista suojausavain" @@ -3010,10 +3010,10 @@ "message": "Suojausavain poistettiin" }, "unassignedItemsBannerNotice": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." + "message": "Huomioi: Määrittämättömät organisaatiokohteet eivät enää näy \"Kaikki holvit\" -näkymässä, vaan ne ovat käytettävissä vain Hallintapaneelista." }, "unassignedItemsBannerSelfHostNotice": { - "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + "message": "Huomioi: Alkaen 16. toukokuuta 2024, määrittämättömät organisaatiokohteet eivät enää näy \"Kaikki holvit\" -näkymässä, vaan ne ovat käytettävissä vain Hallintapaneelista." }, "unassignedItemsBannerCTAPartOne": { "message": "Määritä nämä kohteet kokoelmaan", @@ -3027,9 +3027,9 @@ "message": "Hallintapaneelista" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Virhe määritettäessä kohdekokoelmaa." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Virhe määritettäessä kohdekansiota." } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 28418d984d..0dfb4a39c9 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Libreng Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Isang ligtas at libreng password manager para sa lahat ng iyong mga aparato.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 206f07800d..742e31ee58 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Gestion des mots de passe", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Un gestionnaire de mots de passe sécurisé et gratuit pour tous vos appareils.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 95b880d1a5..b4c151eeb0 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index efb82e64ec..61482da54a 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - מנהל סיסמאות חינמי", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "מנהל סיסמאות חינמי ומאובטח עבור כל המכשירים שלך.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 9c13fa6efa..b76405eed8 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -3,11 +3,11 @@ "message": "bitwarden" }, "extName": { - "message": "Bitwarden", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "bitwarden is a secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index ee4c4e7859..2dc500bc1e 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - besplatni upravitelj lozinki", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bitwarden je siguran i besplatan upravitelj lozinki za sve tvoje uređaje.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 7d6a8a208b..e47f2cda1f 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Ingyenes jelszókezelő", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Egy biztonságos és ingyenes jelszókezelő az összes eszközre.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -3001,7 +3001,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Sikeres" }, "removePasskey": { "message": "Jelszó eltávolítása" diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 92b60324ad..d4399d8e15 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Pengelola Sandi Gratis", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bitwarden adalah sebuah pengelola sandi yang aman dan gratis untuk semua perangkat Anda.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 6887b134df..93ae682190 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Password Manager Gratis", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Un password manager sicuro e gratis per tutti i tuoi dispositivi.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -3001,7 +3001,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Successo" }, "removePasskey": { "message": "Rimuovi passkey" diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 744c76a509..52ff21727a 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - 無料パスワードマネージャー", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bitwarden はあらゆる端末で使える、安全な無料パスワードマネージャーです。", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index ab7b84d22e..2c18502eca 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 67e1f24787..5d1b024c60 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 9b363cba1f..047270808e 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -3,11 +3,11 @@ "message": "ಬಿಟ್ವಾರ್ಡೆನ್" }, "extName": { - "message": "ಬಿಟ್‌ವಾರ್ಡೆನ್ - ಉಚಿತ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕ", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "ನಿಮ್ಮ ಎಲ್ಲಾ ಸಾಧನಗಳಿಗೆ ಸುರಕ್ಷಿತ ಮತ್ತು ಉಚಿತ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕ.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 3e4f5769c0..4bc4302f8b 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - 무료 비밀번호 관리자", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "당신의 모든 기기에서 사용할 수 있는, 안전한 무료 비밀번호 관리자입니다.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index c690c2727b..b1a2c857e0 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Saugi ir nemokama slaptažodžių tvarkyklė visiems įrenginiams.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index d26b10a30c..4055693486 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Drošs bezmaksas paroļu pārvaldnieks visām Tavām ierīcēm.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -3001,7 +3001,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Izdevās" }, "removePasskey": { "message": "Noņemt piekļuves atslēgu" diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 1db5f6458b..d9703137fe 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - സൗജന്യ പാസ്സ്‌വേഡ് മാനേജർ ", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "നിങ്ങളുടെ എല്ലാ ഉപകരണങ്ങൾക്കും സുരക്ഷിതവും സൗജന്യവുമായ പാസ്‌വേഡ് മാനേജർ.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 06cf84efff..f67f617d3b 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - विनामूल्य पासवर्ड व्यवस्थापक", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "तुमच्या सर्व उपकरणांसाठी एक सुरक्षित व विनामूल्य पासवर्ड व्यवस्थापक.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 67e1f24787..5d1b024c60 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 220fe95e23..649163a8dc 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden — Fri passordbehandling", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bitwarden er en sikker og fri passordbehandler for alle dine PCer og mobiler.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 67e1f24787..5d1b024c60 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 808e599e70..5a52b4f7ef 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Gratis wachtwoordbeheer", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Een veilige en gratis oplossing voor wachtwoordbeheer voor al je apparaten.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 67e1f24787..5d1b024c60 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 67e1f24787..5d1b024c60 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 1ef79bac42..e768a70d52 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - darmowy menedżer haseł", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bezpieczny i darmowy menedżer haseł dla wszystkich Twoich urządzeń.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 9322e680d2..c6e62fbd4f 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Um gerenciador de senhas seguro e gratuito para todos os seus dispositivos.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index d4fdc1be81..06ba8eed26 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Gestor de Palavras-passe", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Um gestor de palavras-passe seguro e gratuito para todos os seus dispositivos.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 5e8c82e70b..b3e0a2066f 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Manager de parole gratuit", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Un manager de parole sigur și gratuit pentru toate dispozitivele dvs.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 046fe2b931..e594dbdce2 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - бесплатный менеджер паролей", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Защищенный и бесплатный менеджер паролей для всех ваших устройств.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index e576a50dd7..05e2dc3edd 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -3,11 +3,11 @@ "message": "බිට්වාඩන්" }, "extName": { - "message": "බිට්වාඩන් - නොමිලේ මුරපදය කළමනාකරු", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "ඔබගේ සියලු උපාංග සඳහා ආරක්ෂිත සහ නොමිලේ මුරපද කළමණාකරුවෙකු.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index e34fa525be..eab1d105eb 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Bezplatný správca hesiel", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "bitwarden je bezpečný a bezplatný správca hesiel pre všetky vaše zariadenia.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -1754,7 +1754,7 @@ } }, "send": { - "message": "Odoslať", + "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "searchSends": { diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index c7f8ed0481..935678efc8 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Brezplačni upravitelj gesel", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Varen in brezplačen upravitelj gesel za vse vaše naprave.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 8d1ee8264f..5819546800 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - бесплатни менаџер лозинки", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Сигурни и бесплатни менаџер лозинки за све ваше уређаје.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index ebe5e6d281..d96e86b8d3 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Gratis lösenordshanterare", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bitwarden är en säker och gratis lösenordshanterare för alla dina enheter.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 67e1f24787..5d1b024c60 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "A secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 718440438e..794d0e6c22 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -3,11 +3,11 @@ "message": "bitwarden" }, "extName": { - "message": "bitwarden - Free Password Manager", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "bitwarden is a secure and free password manager for all of your devices.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 7633b47258..8408253b86 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Ücretsiz Parola Yöneticisi", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Tüm cihazlarınız için güvenli ve ücretsiz bir parola yöneticisi.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index b09d966142..b590b92041 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bitwarden - це захищений і безкоштовний менеджер паролів для всіх ваших пристроїв.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index af518878b9..ab1d0d515b 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Quản lý mật khẩu miễn phí", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Trình quản lý mật khẩu an toàn và miễn phí cho mọi thiết bị của bạn.", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 5842d4f4c1..a2f856c31b 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - 免费密码管理器", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "安全且免费的跨平台密码管理器。", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -3001,7 +3001,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "成功" }, "removePasskey": { "message": "移除通行密钥" @@ -3024,12 +3024,12 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "adminConsole": { - "message": "Admin Console" + "message": "管理控制台" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "分配目标集合时出错。" }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "分配目标文件夹时出错。" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index a8163dab98..1ecfdfc50e 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - 免費密碼管理工具", + "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bitwarden 是一款安全、免費、跨平台的密碼管理工具。", + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/store/locales/ar/copy.resx b/apps/browser/store/locales/ar/copy.resx index e74606ff15..e1bfa48b44 100644 --- a/apps/browser/store/locales/ar/copy.resx +++ b/apps/browser/store/locales/ar/copy.resx @@ -1,17 +1,17 @@  - @@ -118,42 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - مدير كلمات مرور مجاني + Bitwarden Password Manager - مدير كلمات مرور مجاني وآمن لجميع أجهزتك + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - شركة Bitwarden, Inc هي الشركة الأم لشركة 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -تم تصنيف Bitwarden كأفصل مدير كلمات مرور بواسطة كل من The Verge، U.S News & World Report، CNET، وغيرهم. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -قم بادراة وحفظ وتأمين كلمات المرور الخاصة بك، ومشاركتها بين اجهزتك من اي مكان. -يوفر Bitwarden حل مفتوح المصدر لادارة كلمات المرور للجميع، سواء في المنزل، في العمل او في اي مكان. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -قم بانشاء كلمات مرور قوية وفريدة وعشوائية حسب متطلبات الأمان للصفحات التي تزورها. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -يوفر Bitwarden Send امكانية ارسال البيانات --- النصوص والملفات --- بطريقة مشفرة وسريعة لأي شخص. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -يوفر Bitwarden خطط خاصة للفرق والشركات والمؤسسات لتمكنك من مشاركة كلمات المرور مع زملائك في العمل. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -لماذا قد تختار Bitwarden: -تشفير على مستوى عالمي -كلمات المرور محمية بتشفير متقدم تام (end-to-end encryption) من نوعية AES-256 bit، مع salted hashing، و PBKDF2 SHA-256. كل هذا لابقاء بياناتك محمية وخاصة. +More reasons to choose Bitwarden: -مولد كلمات المرور المدمج -قم بانشاء كلمات مرور قوية وفريدة وعشوائية حسب متطلبات الأمان للصفحات التي تزورها. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -الترجمات العالمية -يتوفر Bitwarden باكثر من 40 لغة، وتتنامى الترجمات بفضل مجتمعنا العالمي. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -تطبيقات متعددة المنصات -قم بحماية ومشاركة بياناتاك الحساسة عبر خزنة Bitwarden من اي متصفح ويب، او هاتف ذكي، او جهاز كمبيوتر، وغيرها. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - مدير كلمات مرور مجاني وآمن لجميع أجهزتك + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. مزامنة خزنتك والوصول إليها من عدة أجهزة diff --git a/apps/browser/store/locales/az/copy.resx b/apps/browser/store/locales/az/copy.resx index cb05f8e5d9..2a3d507df2 100644 --- a/apps/browser/store/locales/az/copy.resx +++ b/apps/browser/store/locales/az/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Ödənişsiz Parol Meneceri + Bitwarden Password Manager - Bütün cihazlarınız üçün güvənli və ödənişsiz bir parol meneceri + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc., 8bit Solutions LLC-nin ana şirkətidir. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -THE VERGE, U.S. NEWS & WORLD REPORT, CNET VƏ BİR ÇOXUNA GÖRƏ ƏN YAXŞI PAROL MENECERİDİR. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Hər yerdən limitsiz cihazda limitsiz parolu idarə edin, saxlayın, qoruyun və paylaşın. Bitwarden evdə, işdə və ya yolda hər kəsə açıq mənbəli parol idarəetmə həllərini təqdim edir. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Çox istifadə etdiyiniz hər veb sayt üçün təhlükəsizlik tələblərinə görə güclü, unikal və təsadüfi parollar yaradın. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send şifrələnmiş məlumatların (fayl və sadə mətnləri) birbaşa və sürətli göndərilməsini təmin edir. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden, parolları iş yoldaşlarınızla təhlükəsiz paylaşa bilməyiniz üçün şirkətlərə Teams və Enterprise planları təklif edir. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Nəyə görə Bitwarden-i seçməliyik: -Yüksək səviyyə şifrələmə -Parollarınız qabaqcıl ucdan-uca şifrələmə (AES-256 bit, salted hashtag və PBKDF2 SHA-256) ilə qorunur, beləcə datanızın güvənli və gizli qalmasını təmin edir. +More reasons to choose Bitwarden: -Daxili parol yaradıcı -Çox istifadə etdiyiniz hər veb sayt üçün təhlükəsizlik tələblərinə görə güclü, unikal və təsadüfi şifrələr yaradın. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Qlobal tərcümələr -Bitwarden tərcümələri 40 dildə mövcuddur və qlobal cəmiyyətimiz sayəsində böyüməyə davam edir. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Çarpaz platform tətbiqləri -Bitwarden anbarındakı həssas verilənləri, istənilən brauzerdən, mobil cihazdan və ya masaüstü əməliyyat sistemindən və daha çoxundan qoruyub paylaşın. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Bütün cihazlarınız üçün güvənli və ödənişsiz bir parol meneceri + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Anbarınıza bir neçə cihazdan eyniləşdirərək müraciət edin diff --git a/apps/browser/store/locales/be/copy.resx b/apps/browser/store/locales/be/copy.resx index f84dd699a7..65c337826b 100644 --- a/apps/browser/store/locales/be/copy.resx +++ b/apps/browser/store/locales/be/copy.resx @@ -1,17 +1,17 @@  - @@ -118,24 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – бясплатны менеджар пароляў + Bitwarden Password Manager - Бяспечны і бясплатны менеджар пароляў для ўсіх вашых прылад + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden - просты і бяспечны спосаб захоўваць усе вашы імёны карыстальніка і паролі, а таксама лёгка іх сінхранізаваць паміж усімі вашымі прыладамі. Пашырэнне праграмы Bitwarden дазваляе хутка ўвайсці на любы вэб-сайт з дапамогай Safari або Chrome і падтрымліваецца сотнямі іншых папулярных праграм. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -Крадзеж пароляў — сур'ёзная праблема. Сайты і праграмы, якія вы выкарыстоўваеце падвяргаюцца нападам кожны дзень. Праблемы ў іх бяспецы могуць прывесці да крадзяжу вашага пароля. Акрамя таго, калі вы выкарыстоўваеце адны і тыя ж паролі на розных сайтах і праграмах, то хакеры могуць лёгка атрымаць доступ да некалькіх вашых уліковых запісаў адразу (да паштовай скрыні, да банкаўскага рахунку ды г. д.). +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Эксперты па бяспецы рэкамендуюць выкарыстоўваць розныя выпадкова знегерыраваныя паролі для кожнага створанага вамі ўліковага запісу. Але як жа кіраваць усімі гэтымі паролямі? Bitwarden дазваляе вам лёгка атрымаць доступ да вашых пароляў, а гэтак жа ствараць і захоўваць іх. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Bitwarden захоўвае ўсе вашы імёны карыстальніка і паролі ў зашыфраваным сховішчы, якое сінхранізуецца паміж усімі вашымі прыладамі. Да таго, як даныя пакінуць вашу прыладу, яны будуць зашыфраваны і толькі потым адпраўлены. Мы ў Bitwarden не зможам прачытаць вашы даныя, нават калі мы гэтага захочам. Вашы даныя зашыфраваны пры дапамозе алгарытму AES-256 і PBKDF2 SHA-256. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden — гэта праграмнае забеспячэнне з адкрытым на 100% зыходным кодам. Зыходны код Bitwarden размешчаны на GitHub, і кожны можа свабодна праглядаць, правяраць і рабіць унёсак у код Bitwarden. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. + +Use Bitwarden to secure your workforce and share sensitive information with colleagues. + + +More reasons to choose Bitwarden: + +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. + +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. + +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - Бяспечны і бясплатны менеджар пароляў для ўсіх вашых прылад + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Сінхранізацыя і доступ да сховішча з некалькіх прылад diff --git a/apps/browser/store/locales/bg/copy.resx b/apps/browser/store/locales/bg/copy.resx index 29c468f045..bc08f6a107 100644 --- a/apps/browser/store/locales/bg/copy.resx +++ b/apps/browser/store/locales/bg/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden — безплатен управител на пароли + Bitwarden Password Manager - Сигурен и свободен управител на пароли за всички устройства + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - „Bitwarden, Inc.“ е компанията-майка на „8bit Solutions LLC“. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -ОПРЕДЕЛЕН КАТО НАЙ-ДОБРИЯТ УПРАВИТЕЛ НА ПАРОЛИ ОТ „THE VERGE“, „U.S. NEWS & WORLD REPORT“, „CNET“ И ОЩЕ. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Управлявайте, съхранявайте, защитавайте и споделяйте неограничен брой пароли на неограничен брой устройства от всяка точка на света. Битуорден предоставя решение за управление на паролите с отворен код, от което може да се възползва всеки, било то у дома, на работа или на път. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Създавайте сигурни, уникални и случайни пароли според изискванията за сигурност на всеки уеб сайт, който посещавате. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -С Изпращанията на Битуорден можете незабавно да предавате шифрована информация под формата на файлове и обикновен текст – директно и с всекиго. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Битуорден предлага планове за екипи и големи фирми, така че служителите в компаниите да могат безопасно да споделят пароли помежду си. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Защо да изберете Битуорден: -Шифроване от най-висока класа -Паролите са защитени със сложен шифър „от край до край“ (AES-256 bit, salted hashtag и PBKDF2 SHA-256), така че данните Ви остават да са защитени и неприкосновени. +More reasons to choose Bitwarden: -Вграден генератор на пароли -Създавайте сигурни, уникални и случайни пароли според изискванията за сигурност на всеки уеб сайт, който посещавате. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Глобални преводи -Битуорден е преведен на 40 езика и този брой не спира да расте, благодарение на нашата глобална общност. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Приложения за всяка система -Защитете и споделяйте поверителните данни от своя трезор в Битуорден от всеки браузър, мобилно устройство или компютър. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Сигурен и свободен управител на пароли за всички устройства + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Удобен достъп до трезора, който се синхронизира от всички устройства diff --git a/apps/browser/store/locales/bn/copy.resx b/apps/browser/store/locales/bn/copy.resx index a8eb4f7c75..1bcfb19001 100644 --- a/apps/browser/store/locales/bn/copy.resx +++ b/apps/browser/store/locales/bn/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – বিনামূল্যের পাসওয়ার্ড ব্যবস্থাপক + Bitwarden Password Manager - আপনার সমস্ত ডিভাইসের জন্য একটি সুরক্ষিত এবং বিনামূল্যের পাসওয়ার্ড ব্যবস্থাপক + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - আপনার সমস্ত ডিভাইসের জন্য একটি সুরক্ষিত এবং বিনামূল্যের পাসওয়ার্ড ব্যবস্থাপক + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. একাধিক ডিভাইস থেকে আপনার ভল্ট সিঙ্ক এবং ব্যাবহার করুন diff --git a/apps/browser/store/locales/bs/copy.resx b/apps/browser/store/locales/bs/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/bs/copy.resx +++ b/apps/browser/store/locales/bs/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/ca/copy.resx b/apps/browser/store/locales/ca/copy.resx index 0bd454addb..27e685841b 100644 --- a/apps/browser/store/locales/ca/copy.resx +++ b/apps/browser/store/locales/ca/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Administrador de contrasenyes gratuït + Bitwarden Password Manager - Administrador de contrasenyes segur i gratuït per a tots els vostres dispositius + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. és la companyia matriu de solucions de 8 bits LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -Nomenada Millor gestor de contrasenyes per THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Gestioneu, emmagatzemeu, segures i compartiu contrasenyes il·limitades a través de dispositius il·limitats des de qualsevol lloc. Bitwarden lliura solucions de gestió de contrasenyes de codi obert a tothom, ja siga a casa, a la feina o sobre la marxa. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Genereu contrasenyes fortes, úniques i aleatòries basades en els requisits de seguretat per a cada lloc web que freqüenteu. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send transmet ràpidament informació xifrada --- Fitxers i text complet - directament a qualsevol persona. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden ofereix equips i plans empresarials per a empreses perquè pugueu compartir amb seguretat contrasenyes amb els companys. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Per què triar Bitwarden: -Xifratge de classe mundial -Les contrasenyes estan protegides amb un xifratge avançat fi-a-fi (AES-256 bit, salted hashtag, and PBKDF2 SHA-256), de manera que les vostres dades es mantenen segures i privades. +More reasons to choose Bitwarden: -Generador de contrasenyes integrat -Genereu contrasenyes fortes, úniques i aleatòries basades en els requisits de seguretat per a cada lloc web que freqüenteu. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Traduccions globals -Les traduccions de Bitwarden existeixen en 40 idiomes i creixen, gràcies a la nostra comunitat global. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Aplicacions de plataforma creuada -Assegureu-vos i compartiu dades sensibles a la vostra caixa forta de Bitwarden des de qualsevol navegador, dispositiu mòbil o S.O. d'escriptori, i molt més. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Administrador de contrasenyes segur i gratuït per a tots els vostres dispositius + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sincronitzeu i accediu a la vostra caixa forta des de diversos dispositius diff --git a/apps/browser/store/locales/cs/copy.resx b/apps/browser/store/locales/cs/copy.resx index 6b711e2863..59d8c60b40 100644 --- a/apps/browser/store/locales/cs/copy.resx +++ b/apps/browser/store/locales/cs/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Bezplatný správce hesel + Bitwarden Password Manager - Bezpečný a bezplatný správce hesel pro všechna Vaše zařízení + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. je mateřskou společností 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -THE VERGE, U.S. NEWS & WORLD REPORT, CNET A DALŠÍ JI OZNAČILY ZA NEJLEPŠÍHO SPRÁVCE HESEL. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Spravujte, ukládejte, zabezpečujte a sdílejte neomezený počet hesel na neomezeném počtu zařízení odkudkoliv. Bitwarden poskytuje open source řešení pro správu hesel všem, ať už doma, v práci nebo na cestách. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generujte silná, jedinečná a náhodná hesla na základě bezpečnostních požadavků pro každou webovou stránku, kterou navštěvujete. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send rychle přenáší šifrované informace --- soubory a prostý text -- přímo a komukoli. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden nabízí plány Teams a Enterprise pro firmy, takže můžete bezpečně sdílet hesla s kolegy. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Proč si vybrat Bitwarden: -Šifrování na světové úrovni -Hesla jsou chráněna pokročilým koncovým šifrováním (AES-256 bit, salted hashování a PBKDF2 SHA-256), takže Vaše data zůstanou bezpečná a soukromá. +More reasons to choose Bitwarden: -Vestavěný generátor hesel -Generujte silná, jedinečná a náhodná hesla na základě bezpečnostních požadavků pro každou webovou stránku, kterou navštěvujete. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Globální překlady -Překlady Bitwarden existují ve 40 jazycích a díky naší globální komunitě se stále rozšiřují. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Aplikace pro více platforem -Zabezpečte a sdílejte citlivá data v rámci svého trezoru Bitwarden z jakéhokoli prohlížeče, mobilního zařízení nebo operačního systému pro počítač. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Bezpečný a bezplatný správce hesel pro všechna Vaše zařízení + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Synchronizujte a přistupujte ke svému trezoru z různých zařízení diff --git a/apps/browser/store/locales/cy/copy.resx b/apps/browser/store/locales/cy/copy.resx index 8222329630..983a112c07 100644 --- a/apps/browser/store/locales/cy/copy.resx +++ b/apps/browser/store/locales/cy/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Rheolydd cyfineiriau am ddim + Bitwarden Password Manager - Rheolydd diogel a rhad ac am ddim ar gyfer eich holl ddyfeisiau + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. yw rhiant-gwmni 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -Y RHEOLYDD CYFRINEIRIAU GORAU YN ÔL THE VERGE, US NEWS & WORLD REPORT, CNET, A MWY. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Rheolwch, storiwch, diogelwch a rhannwch gyfrineiriau di-ri ar draws dyfeiriau di-ri o unrhyw le. Mae Bitwarden yn cynnig rhaglenni rheoli cyfrineiriau cod-agored i bawb, boed gartref, yn y gwaith, neu ar fynd. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Gallwch gynhyrchu cyfrineiriau cryf, unigryw ac ar hap yn seiliedig ar ofynion diogelwch ar gyfer pob gwefan rydych chi'n ei defnyddio. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Mae Bitwarden Send yn trosglwyddo gwybodaeth wedi'i hamgryptio yn gyflym -- ffeiliau a thestun plaen -- yn uniongyrchol i unrhyw un. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Mae Bitwarden yn cynnig cynlluniau Teams ac Enterprise i gwmnïau er mwyn i chi allu rhannu cyfrineiriau gyda chydweithwyr yn ddiogel. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Pam dewis Bitwarden: -Amgryptio o'r radd flaenaf -Mae cyfrineiriau wedi'u hamddiffyn ag amgryptio datblygedig o un pen i'r llall (AES-256 bit, hashio â halen, a PBKDF2 SHA-256) er mwyn i'ch data aros yn ddiogel ac yn breifat. +More reasons to choose Bitwarden: -Cynhyrchydd cyfrineiriau -Gallwch gynhyrchu cyfrineiriau cryf, unigryw ac ar hap yn seiliedig ar ofynion diogelwch ar gyfer pob gwefan rydych chi'n ei defnyddio. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Ar gael yn eich iaith chi -Mae Bitwarden wedi'i gyfieithu i dros 40 o ieithoedd, diolch i'n cymuned fyd-eang. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Rhaglenni traws-blatfform -Diogelwch a rhannwch ddata sensitif yn eich cell Bitwarden o unrhyw borwr, dyfais symudol, neu system weithredu, a mwy. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Rheolydd diogel a rhad ac am ddim ar gyfer eich holl ddyfeisiau + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Gallwch gael mynediad at, a chysoni, eich cell o sawl dyfais diff --git a/apps/browser/store/locales/da/copy.resx b/apps/browser/store/locales/da/copy.resx index 858c56dea9..775a3edd81 100644 --- a/apps/browser/store/locales/da/copy.resx +++ b/apps/browser/store/locales/da/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Gratis adgangskodemanager + Bitwarden Password Manager - En sikker og gratis adgangskodemanager til alle dine enheder + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. er moderselskab for 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -UDNÆVNT BEDSTE PASSWORD MANAGER AF THE VERGE, U.S. NEWS & WORLD REPORT, CNET OG FLERE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Administrér, gem, sikr og del adgangskoder ubegrænset på tværs af enheder hvor som helst. Bitwarden leverer open source adgangskodeadministrationsløsninger til alle, hvad enten det er hjemme, på arbejdspladsen eller på farten. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generér stærke, unikke og tilfældige adgangskoder baseret på sikkerhedskrav til hvert websted, du besøger. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send overfører hurtigt krypterede oplysninger --- filer og almindelig tekst - direkte til enhver. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden tilbyder Teams og Enterprise-planer for virksomheder, så du sikkert kan dele adgangskoder med kolleger. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Hvorfor vælge Bitwarden: -Kryptering i verdensklasse -Adgangskoder er beskyttet med avanceret end-to-end-kryptering (AES-256 bit, saltet hashing og PBKDF2 SHA-256), så dine data forbliver sikre og private. +More reasons to choose Bitwarden: -Indbygget adgangskodegenerator -Generér stærke, unikke og tilfældige adgangskoder baseret på sikkerhedskrav til hvert websted, du besøger. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Globale oversættelser -Bitwarden findes på 40 sprog, og flere kommer til, takket være vores globale fællesskab. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Applikationer på tværs af platforme -Beskyt og del følsomme data i din Bitwarden boks fra enhver browser, mobilenhed eller desktop OS og mere. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - En sikker og gratis adgangskodemanager til alle dine enheder + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Synkroniser og få adgang til din boks fra flere enheder diff --git a/apps/browser/store/locales/de/copy.resx b/apps/browser/store/locales/de/copy.resx index 139a6026fd..2267c6c85e 100644 --- a/apps/browser/store/locales/de/copy.resx +++ b/apps/browser/store/locales/de/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Kostenloser Passwort-Manager + Bitwarden Passwort-Manager - Ein sicherer und kostenloser Passwort-Manager für all deine Geräte + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. ist die Muttergesellschaft von 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -AUSGEZEICHNET ALS BESTER PASSWORTMANAGER VON THE VERGE, U.S. NEWS & WORLD REPORT, CNET UND ANDEREN. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Verwalte, speichere, sichere und teile unbegrenzte Passwörter von überall auf unbegrenzten Geräten. Bitwarden liefert Open-Source-Passwort-Management-Lösungen für alle, sei es zu Hause, am Arbeitsplatz oder unterwegs. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generiere starke, einzigartige und zufällige Passwörter basierend auf Sicherheitsanforderungen für jede Website, die du häufig besuchst. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send überträgt schnell verschlüsselte Informationen - Dateien und Klartext - direkt an jeden. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden bietet Teams und Enterprise Pläne für Unternehmen an, damit du Passwörter sicher mit Kollegen teilen kannst. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Warum Bitwarden: -Weltklasse Verschlüsselung -Passwörter sind durch erweiterte Ende-zu-Ende-Verschlüsselung (AES-256 Bit, salted hashing und PBKDF2 SHA-256) so bleiben deine Daten sicher und privat. +More reasons to choose Bitwarden: -Integrierter Passwortgenerator -Generiere starke, einzigartige und zufällige Passwörter basierend auf Sicherheitsanforderungen für jede Website, die du häufig besuchst. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Globale Übersetzungen -Bitwarden Übersetzungen existieren in 40 Sprachen und wachsen dank unserer globalen Community. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Plattformübergreifende Anwendungen -Sichere und teile vertrauliche Daten in deinem Bitwarden Tresor von jedem Browser, jedem mobilen Gerät oder Desktop-Betriebssystem und mehr. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Ein sicherer und kostenloser Passwort-Manager für all deine Geräte + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Synchronisiere und greife auf deinen Tresor von unterschiedlichen Geräten aus zu diff --git a/apps/browser/store/locales/el/copy.resx b/apps/browser/store/locales/el/copy.resx index 01def6ea5a..fb50f95bdc 100644 --- a/apps/browser/store/locales/el/copy.resx +++ b/apps/browser/store/locales/el/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Δωρεάν Διαχειριστής Κωδικών + Bitwarden Password Manager - Ένας ασφαλής και δωρεάν διαχειριστής κωδικών για όλες τις συσκευές σας + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Η Bitwarden, Inc. είναι η μητρική εταιρεία της 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -ΟΝΟΜΑΣΘΗΚΕ ΩΣ Ο ΚΑΛΥΤΕΡΟΣ ΔΙΑΧΕΙΡΙΣΤΗΣ ΚΩΔΙΚΩΝ ΠΡΟΣΒΑΣΗΣ ΑΠΟ ΤΟ VERGE, το U.S. NEWS & WORLD REPORT, το CNET και άλλα. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Διαχειριστείτε, αποθηκεύστε, ασφαλίστε και μοιραστείτε απεριόριστους κωδικούς πρόσβασης σε απεριόριστες συσκευές από οπουδήποτε. Το Bitwarden παρέχει λύσεις διαχείρισης κωδικών πρόσβασης ανοιχτού κώδικα σε όλους, στο σπίτι, στη δουλειά ή εν κινήσει. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Δημιουργήστε ισχυρούς, μοναδικούς και τυχαίους κωδικούς πρόσβασης βάσει των απαιτήσεων ασφαλείας, για κάθε ιστότοπο που συχνάζετε. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Το Bitwarden Send αποστέλλει γρήγορα κρυπτογραφημένες πληροφορίες --- αρχεία και απλό κείμενο -- απευθείας σε οποιονδήποτε. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Το Bitwarden προσφέρει προγράμματα Teams και Enterprise για εταιρείες, ώστε να μπορείτε να μοιράζεστε με ασφάλεια τους κωδικούς πρόσβασης με τους συναδέλφους σας. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Γιατί να επιλέξετε το Bitwarden: -Κρυπτογράφηση παγκόσμιας κλάσης -Οι κωδικοί πρόσβασης προστατεύονται με προηγμένη end-to-end κρυπτογράφηση (AES-256 bit, salted hashing και PBKDF2 SHA-256), ώστε τα δεδομένα σας να παραμένουν ασφαλή και ιδιωτικά. +More reasons to choose Bitwarden: -Ενσωματωμένη Γεννήτρια Κωδικών Πρόσβασης -Δημιουργήστε ισχυρούς, μοναδικούς και τυχαίους κωδικούς πρόσβασης βάσει των απαιτήσεων ασφαλείας, για κάθε ιστότοπο που συχνάζετε. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Παγκόσμιες Μεταφράσεις -Υπάρχουν μεταφράσεις για το Bitwarden σε 40 γλώσσες και αυξάνονται συνεχώς, χάρη στην παγκόσμια κοινότητά μας. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Εφαρμογές για όλες τις πλατφόρμες -Ασφαλίστε και μοιραστείτε ευαίσθητα δεδομένα εντός του Bitwarden Vault από οποιοδήποτε πρόγραμμα περιήγησης, κινητή συσκευή ή λειτουργικό σύστημα υπολογιστών, και πολλά άλλα. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Ένας ασφαλής και δωρεάν διαχειριστής κωδικών για όλες τις συσκευές σας + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Συγχρονίστε και αποκτήστε πρόσβαση στο θησαυροφυλάκιό σας από πολλαπλές συσκευές diff --git a/apps/browser/store/locales/en_GB/copy.resx b/apps/browser/store/locales/en_GB/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/en_GB/copy.resx +++ b/apps/browser/store/locales/en_GB/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/en_IN/copy.resx b/apps/browser/store/locales/en_IN/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/en_IN/copy.resx +++ b/apps/browser/store/locales/en_IN/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/es/copy.resx b/apps/browser/store/locales/es/copy.resx index dc7484777a..472697d825 100644 --- a/apps/browser/store/locales/es/copy.resx +++ b/apps/browser/store/locales/es/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Gestor de contraseñas gratuito + Bitwarden Password Manager - Un gestor de contraseñas seguro y gratuito para todos tus dispositivos + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. es la empresa matriz de 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NOMBRADO MEJOR ADMINISTRADOR DE CONTRASEÑAS POR THE VERGE, U.S. NEWS & WORLD REPORT, CNET Y MÁS. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Administre, almacene, proteja y comparta contraseñas ilimitadas en dispositivos ilimitados desde cualquier lugar. Bitwarden ofrece soluciones de gestión de contraseñas de código abierto para todos, ya sea en casa, en el trabajo o en mientras estás de viaje. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Genere contraseñas seguras, únicas y aleatorias en función de los requisitos de seguridad de cada sitio web que frecuenta. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send transmite rápidamente información cifrada --- archivos y texto sin formato, directamente a cualquier persona. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden ofrece planes Teams y Enterprise para empresas para que pueda compartir contraseñas de forma segura con colegas. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -¿Por qué elegir Bitwarden? -Cifrado de clase mundial -Las contraseñas están protegidas con cifrado avanzado de extremo a extremo (AES-256 bits, salted hashing y PBKDF2 SHA-256) para que sus datos permanezcan seguros y privados. +More reasons to choose Bitwarden: -Generador de contraseñas incorporado -Genere contraseñas fuertes, únicas y aleatorias en función de los requisitos de seguridad de cada sitio web que frecuenta. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Traducciones Globales -Las traducciones de Bitwarden existen en 40 idiomas y están creciendo, gracias a nuestra comunidad global. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Aplicaciones multiplataforma -Proteja y comparta datos confidenciales dentro de su Caja Fuerte de Bitwarden desde cualquier navegador, dispositivo móvil o sistema operativo de escritorio, y más. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Un gestor de contraseñas seguro y gratuito para todos tus dispositivos + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sincroniza y accede a tu caja fuerte desde múltiples dispositivos diff --git a/apps/browser/store/locales/et/copy.resx b/apps/browser/store/locales/et/copy.resx index 2014ec88a8..eccbeba1ed 100644 --- a/apps/browser/store/locales/et/copy.resx +++ b/apps/browser/store/locales/et/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Tasuta paroolihaldur + Bitwarden Password Manager - Tasuta ja turvaline paroolihaldur kõikidele sinu seadmetele + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Tasuta ja turvaline paroolihaldur kõikidele Sinu seadmetele + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sünkroniseeri ja halda oma kontot erinevates seadmetes diff --git a/apps/browser/store/locales/eu/copy.resx b/apps/browser/store/locales/eu/copy.resx index e5b3d542e3..e4271e8ae3 100644 --- a/apps/browser/store/locales/eu/copy.resx +++ b/apps/browser/store/locales/eu/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden — Doaneko pasahitz kudeatzailea + Bitwarden Password Manager - Bitwarden, zure gailu guztietarako pasahitzen kudeatzaile seguru eta doakoa + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. 8bit Solutions LLC-ren enpresa matrizea da. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -THE VERGE, U.S. NEWS & WORLD REPORT ETA CNET ENPRESEK PASAHITZ-ADMINISTRATZAILE ONENA izendatu dute, besteak beste. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Gailu guztien artean pasahitz mugagabeak kudeatu, biltegiratu, babestu eta partekatzen ditu. Bitwardenek kode irekiko pasahitzak administratzeko irtenbideak eskaintzen ditu, bai etxean, bai lanean, bai bidaiatzen ari zaren bitartean. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Pasahitz sendoak, bakarrak eta ausazkoak sortzen ditu, webgune bakoitzaren segurtasun-baldintzetan oinarrituta. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send-ek azkar transmititzen du zifratutako informazioa --- artxiboak eta testu sinplea -- edozein pertsonari zuzenean. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden-ek Taldeak eta Enpresak planak eskaintzen ditu, enpresa bereko lankideek pasahitzak modu seguruan parteka ditzaten. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Zergatik aukeratu Bitwarden: -Mundu-mailako zifratzea -Pasahitzak muturretik muturrerako zifratze aurreratuarekin babestuta daude (AES-256 bit, salted hashtag eta PBKDF2 SHA-256), zure informazioa seguru eta pribatu egon dadin. +More reasons to choose Bitwarden: -Pasahitzen sortzailea -Pasahitz sendoak, bakarrak eta ausazkoak sortzen ditu, web gune bakoitzaren segurtasun-baldintzetan oinarrituta. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Itzulpenak -Bitwarden 40 hizkuntzatan dago, eta gero eta gehiago dira, gure komunitate globalari esker. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Plataforma anitzeko aplikazioak -Babestu eta partekatu zure Bitwarden kutxa gotorraren informazio konfidentziala edozein nabigatzailetatik, gailu mugikorretatik, mahaigaineko aplikaziotik eta gehiagotatik. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Zure gailu guztietarako pasahitzen kudeatzaile seguru eta doakoa + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sinkronizatu eta sartu zure kutxa gotorrean hainbat gailutatik diff --git a/apps/browser/store/locales/fa/copy.resx b/apps/browser/store/locales/fa/copy.resx index 23cb3f3bf0..67095435ac 100644 --- a/apps/browser/store/locales/fa/copy.resx +++ b/apps/browser/store/locales/fa/copy.resx @@ -1,17 +1,17 @@  - @@ -118,40 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - مدیریت کلمه عبور رایگان + Bitwarden Password Manager - یک مدیریت کننده کلمه عبور رایگان برای تمامی دستگاه‌هایتان + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden، Inc. شرکت مادر 8bit Solutions LLC است. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -به عنوان بهترین مدیر کلمه عبور توسط VERGE، US News & WORLD REPORT، CNET و دیگران انتخاب شد. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -کلمه‌های عبور با تعداد نامحدود را در دستگاه‌های نامحدود از هر کجا مدیریت کنید، ذخیره کنید، ایمن کنید و به اشتراک بگذارید. Bitwarden راه حل های مدیریت رمز عبور منبع باز را به همه ارائه می دهد، چه در خانه، چه در محل کار یا در حال حرکت. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -بر اساس الزامات امنیتی برای هر وب سایتی که بازدید می کنید، کلمه‌های عبور قوی، منحصر به فرد و تصادفی ایجاد کنید. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send به سرعت اطلاعات رمزگذاری شده --- فایل ها و متن ساده - را مستقیماً به هر کسی منتقل می کند. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden برنامه‌های Teams و Enterprise را برای شرکت‌ها ارائه می‌دهد تا بتوانید به‌طور ایمن کلمه‌های را با همکاران خود به اشتراک بگذارید. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -چرا Bitwarden را انتخاب کنید: -رمزگذاری در کلاس جهانی -کلمه‌های عبور با رمزگذاری پیشرفته (AES-256 بیت، هشتگ سالت و PBKDF2 SHA-256) محافظت می‌شوند تا داده‌های شما امن و خصوصی بمانند. +More reasons to choose Bitwarden: -تولیدکننده کلمه عبور داخلی -بر اساس الزامات امنیتی برای هر وب سایتی که بازدید می کنید، کلمه‌های عبور قوی، منحصر به فرد و تصادفی ایجاد کنید. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -ترجمه های جهانی -ترجمه Bitwarden به 40 زبان وجود دارد و به لطف جامعه جهانی ما در حال رشد است. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -برنامه های کاربردی چند پلتفرمی -داده‌های حساس را در Bitwarden Vault خود از هر مرورگر، دستگاه تلفن همراه یا سیستم عامل دسکتاپ و غیره ایمن کنید و به اشتراک بگذارید. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - یک مدیریت کننده کلمه عبور رایگان برای تمامی دستگاه‌هایتان + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. همگام‌سازی و دسترسی به گاوصندوق خود از دستگاه های مختلف diff --git a/apps/browser/store/locales/fi/copy.resx b/apps/browser/store/locales/fi/copy.resx index 4440603239..42e914a13f 100644 --- a/apps/browser/store/locales/fi/copy.resx +++ b/apps/browser/store/locales/fi/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Ilmainen salasanahallinta + Bitwarden – Salasanahallinta - Turvallinen ja ilmainen salasanahallinta kaikille laitteillesi + Kotona, töissä tai reissussa, Bitwarden suojaa helposti kaikki salasanasi, avainkoodisi ja arkaluonteiset tietosi. - Bitwarden, Inc. on 8bit Solutions LLC:n emoyhtiö. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NIMENNYT PARHAAKSI SALASANOJEN HALLINNAKSI MM. THE VERGE, U.S. NEWS & WORLD REPORT JA CNET. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Hallinnoi, säilytä, suojaa ja jaa salasanoja rajattomalta laitemäärältä mistä tahansa. Bitwarden tarjoaa avoimeen lähdekoodin perustuvan salasanojen hallintaratkaisun kaikille, olitpa sitten kotona, töissä tai liikkeellä. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Luo usein käyttämillesi sivustoille automaattisesti vahvoja, yksilöllisiä ja satunnaisia salasanoja. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send -ominaisuudella lähetät tietoa nopeasti salattuna — tiedostoja ja tekstiä — suoraan kenelle tahansa. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Yritystoimintaan Bitwarden tarjoaa yrityksille Teams- ja Enterprise-tilaukset, jotta salasanojen jakaminen kollegoiden kesken on turvallista. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Miksi Bitwarden?: -Maailmanluokan salaus -Salasanat on suojattu tehokkaalla päästä päähän salauksella (AES-256 bit, suolattu hajautus ja PBKDF2 SHA-256), joten tietosi pysyvät turvassa ja yksityisinä. +More reasons to choose Bitwarden: -Sisäänrakennettu salasanageneraattori -Luo usein käyttämillesi sivustoille vahvoja, yksilöllisiä ja satunnaisia salasanoja. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Monikielinen -Bitwardenin sovelluksia on käännetty yli 40 kielelle ja määrä kasvaa jatkuvasti, kiitos kansainvälisen yhteisömme. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Alustariippumattomaton -Suojaa, käytä ja jaa Bitwarden-holvisi arkaluontoisia tietoja kaikilla selaimilla, mobiililaitteilla, pöytätietokoneilla ja muissa järjestelmissä. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Turvallinen ja ilmainen salasanahallinta kaikille laitteillesi + Kotona, töissä tai reissussa, Bitwarden suojaa helposti kaikki salasanasi, avainkoodisi ja arkaluonteiset tietosi. Synkronoi ja hallitse holviasi useilla laitteilla diff --git a/apps/browser/store/locales/fil/copy.resx b/apps/browser/store/locales/fil/copy.resx index 4947fa6996..0f68a90bfa 100644 --- a/apps/browser/store/locales/fil/copy.resx +++ b/apps/browser/store/locales/fil/copy.resx @@ -1,17 +1,17 @@  - @@ -118,17 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Libreng Password Manager + Bitwarden Password Manager - Isang ligtas at libreng password manager para sa lahat ng iyong mga aparato. + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Si Bitwarden, Inc. ang parent company ng 8bit Solutions LLC. Tinawag na Pinakamahusay na Password Manager ng The Verge, U.S. News & World Report, CNET at iba pa. I-manage, i-store, i-secure at i-share ang walang limitasyong mga password sa walang limitasyong mga device mula sa kahit saan. Bitwarden nagbibigay ng mga open source na solusyon sa password management sa lahat, kahit sa bahay, sa trabaho o habang nasa daan. Lumikha ng mga matatas, natatanging, at mga random na password na naka-base sa mga pangangailangan ng seguridad para sa bawat website na madalas mong bisitahin. Ang Bitwarden Send ay nagpapadala ng maayos na naka-encrypt na impormasyon - mga file at plaintext - diretso sa sinuman. Ang Bitwarden ay nag-aalok ng mga Teams at Enterprise plans para sa m. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. + +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. + +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. + +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. + +Use Bitwarden to secure your workforce and share sensitive information with colleagues. + + +More reasons to choose Bitwarden: + +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. + +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. + +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Isang ligtas at libreng password manager para sa lahat ng iyong mga aparato. + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. I-sync at i-access ang iyong kahadeyero mula sa maraming mga aparato diff --git a/apps/browser/store/locales/fr/copy.resx b/apps/browser/store/locales/fr/copy.resx index 9d311fe7cf..9927f885d3 100644 --- a/apps/browser/store/locales/fr/copy.resx +++ b/apps/browser/store/locales/fr/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Gestion des mots de passe + Bitwarden Password Manager - Un gestionnaire de mots de passe sécurisé et gratuit pour tous vos appareils + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. est la société mère de 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NOMMÉ MEILLEUR GESTIONNAIRE DE MOTS DE PASSE PAR THE VERGE, U.S. NEWS & WORLD REPORT, CNET, ET PLUS ENCORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Gérez, stockez, sécurisez et partagez un nombre illimité de mots de passe sur un nombre illimité d'appareils, où que vous soyez. Bitwarden fournit des solutions de gestion de mots de passe open source à tout le monde, que ce soit chez soi, au travail ou en déplacement. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Générez des mots de passe robustes, uniques et aléatoires basés sur des exigences de sécurité pour chaque site web que vous fréquentez. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send transmet rapidement des informations chiffrées --- fichiers et texte --- directement à quiconque. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden propose des plans Teams et Enterprise pour les sociétés afin que vous puissiez partager des mots de passe en toute sécurité avec vos collègues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Pourquoi choisir Bitwarden : -Un chiffrement de classe internationale -Les mots de passe sont protégés par un cryptage avancé de bout en bout (AES-256 bit, hachage salé et PBKDF2 SHA-256) afin que vos données restent sécurisées et privées. +More reasons to choose Bitwarden: -Générateur de mots de passe intégré -Générez des mots de passe forts, uniques et aléatoires en fonction des exigences de sécurité pour chaque site web que vous fréquentez. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Traductions internationales -Les traductions de Bitwarden existent dans 40 langues et ne cessent de croître, grâce à notre communauté globale. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Applications multiplateformes -Sécurisez et partagez des données sensibles dans votre coffre Bitwarden à partir de n'importe quel navigateur, appareil mobile ou système d'exploitation de bureau, et plus encore. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Un gestionnaire de mots de passe sécurisé et gratuit pour tous vos appareils + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Synchroniser et accéder à votre coffre depuis plusieurs appareils diff --git a/apps/browser/store/locales/gl/copy.resx b/apps/browser/store/locales/gl/copy.resx index 04e84f6677..0fdb224988 100644 --- a/apps/browser/store/locales/gl/copy.resx +++ b/apps/browser/store/locales/gl/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Xestor de contrasinais gratuíto + Bitwarden Password Manager - Un xestor de contrasinais seguro e gratuíto para todos os teus dispositivos + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. é a empresa matriz de 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NOMEADO MELLOR ADMINISTRADOR DE CONTRASINAIS POR THE VERGE, Ou.S. NEWS & WORLD REPORT, CNET E MÁS. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Administre, almacene, protexa e comparta contrasinais ilimitados en dispositivos ilimitados desde calquera lugar. Bitwarden ofrece solucións de xestión de contrasinais de código aberto para todos, xa sexa en casa, no traballo ou en mentres estás de viaxe. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Xere contrasinais seguros, únicas e aleatorias en función dos requisitos de seguridade de cada sitio web que frecuenta. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send transmite rapidamente información cifrada --- arquivos e texto sen formato, directamente a calquera persoa. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden ofrece plans Teams e Enterprise para empresas para que poida compartir contrasinais de forma segura con colegas. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Por que elixir Bitwarden? -Cifrado de clase mundial -Os contrasinais están protexidas con cifrado avanzado de extremo a extremo (AES-256 bits, salted hashing e PBKDF2 XA-256) para que os seus datos permanezan seguros e privados. +More reasons to choose Bitwarden: -Xerador de contrasinais incorporado -Xere contrasinais fortes, únicas e aleatorias en función dos requisitos de seguridade de cada sitio web que frecuenta. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Traducións Globais -As traducións de Bitwarden existen en 40 idiomas e están a crecer, grazas á nosa comunidade global. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Aplicacións multiplataforma -Protexa e comparta datos confidenciais dentro da súa Caixa Forte de Bitwarden desde calquera navegador, dispositivo móbil ou sistema operativo de escritorio, e máis. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Un xestor de contrasinais seguro e gratuíto para todos os teus dispositivos + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sincroniza e accede á túa caixa forte desde múltiples dispositivos diff --git a/apps/browser/store/locales/he/copy.resx b/apps/browser/store/locales/he/copy.resx index cd980970fc..7f366f0e93 100644 --- a/apps/browser/store/locales/he/copy.resx +++ b/apps/browser/store/locales/he/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – מנהל ססמאות חינמי + Bitwarden Password Manager - מנהל ססמאות חינמי ומאובטח עבור כל המכשירים שלך + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - מנהל סיסמאות חינמי ומאובטח עבור כל המכשירים שלך + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. סנכרון וגישה לכספת שלך ממגוון מכשירים diff --git a/apps/browser/store/locales/hi/copy.resx b/apps/browser/store/locales/hi/copy.resx index 8db837a3c3..1ea7314d52 100644 --- a/apps/browser/store/locales/hi/copy.resx +++ b/apps/browser/store/locales/hi/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - बिटवार्डन - मुक्त कूटशब्द प्रबंधक + Bitwarden Password Manager - आपके सभी उपकरणों के लिए एक सुरक्षित और नि: शुल्क कूटशब्द प्रबंधक + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - आपके सभी उपकरणों के लिए एक सुरक्षित और नि: शुल्क पासवर्ड प्रबंधक + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. अनेक उपकरणों से अपने तिजोरी सिंक और एक्सेस करें diff --git a/apps/browser/store/locales/hr/copy.resx b/apps/browser/store/locales/hr/copy.resx index 5ff2bcbe01..dff95b3796 100644 --- a/apps/browser/store/locales/hr/copy.resx +++ b/apps/browser/store/locales/hr/copy.resx @@ -1,17 +1,17 @@  - @@ -118,40 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - besplatni upravitelj lozinki + Bitwarden Password Manager - Siguran i besplatan upravitelj lozinki za sve vaše uređaje + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. je vlasnik tvrtke 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -THE VERGE, U.S. NEWS & WORLD REPORT, CNET I DRUGI ODABRALI SU BITWARDEN NAJBOLJIM UPRAVITELJEM LOZINKI. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Upravljajte, spremajte, osigurajte i dijelite neograničen broj lozinki na neograničenom broju uređaja bilo gdje. Bitwarden omogućuje upravljanje lozinkama, bazirano na otvorenom kodu, svima, bilo kod kuće, na poslu ili u pokretu. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generirajte jake, jedinstvene i nasumične lozinke bazirane na sigurnosnim zahtjevima za svaku web stranicu koju često posjećujete. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send omoguzćuje jednostavno i brzo slanje šifriranih podataka --- datoteki ili teksta -- direktno, bilo kome. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden nudi Teams i Enterprise planove za tvrtke kako biste sigurno mogli dijeliti lozinke s kolegama na poslu. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Zašto odabrati Bitwarden? -Svjetski priznata enkripcija -Lozinke su zaštićene naprednim end-to-end šifriranjem (AES-256 bit, salted hashtag i PBKDF2 SHA-256) kako bi vaši osobni podaci ostali sigurni i samo vaši. +More reasons to choose Bitwarden: -Ugrađen generator lozinki -Generirajte jake, jedinstvene i nasumične lozinke bazirane na sigurnosnim zahtjevima za svako web mjesto koje često posjećujete. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Svjetski dostupan -Bitwarden je, zahvaljujući našoj globalnoj zajednici, dostupan na više od 40 jezika. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Podržani svi OS -Osigurajte i sigurno dijelite osjetljive podatke sadržane u vašem Bitwarden trezoru iz bilo kojeg preglednika, mobilnog uređaja ili stolnog računala s bilo kojim OS. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - Siguran i besplatan upravitelj lozinki za sve tvoje uređaje + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sinkroniziraj i pristupi svojem trezoru s više uređaja diff --git a/apps/browser/store/locales/hu/copy.resx b/apps/browser/store/locales/hu/copy.resx index 0b3761a8ad..3e6b8e42d4 100644 --- a/apps/browser/store/locales/hu/copy.resx +++ b/apps/browser/store/locales/hu/copy.resx @@ -1,17 +1,17 @@  - @@ -118,36 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Ingyenes jelszókezelő + Bitwarden Password Manager - Egy biztonságos és ingyenes jelszókezelő az összes eszközre. + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - A Bitwarden, Inc. a 8bit Solutions LLC anyavállalata. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -A VERGE, A US NEWS & WORLD REPORT, a CNET ÉS MÁSOK LEGJOBB JELSZÓKEZELŐJE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Korlátlan számú jelszavak kezelése, tárolása, védelme és megosztása korlátlan eszközökön bárhonnan. A Bitwarden nyílt forráskódú jelszókezelési megoldásokat kínál mindenkinek, legyen az otthon, a munkahelyen vagy útközben. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Hozzunk létre erős, egyedi és véletlenszerű jelszavakat a biztonsági követelmények alapján minden webhelyre, amelyet gyakran látogatunk. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -A Bitwarden Send gyorsan továbbítja a titkosított információkat-fájlokat és egyszerű szöveget közvetlenül bárkinek. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -A Bitwarden csapatokat és vállalati terveket kínál a vállalatok számára, így biztonságosan megoszthatja jelszavait kollégáival. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Miért válasszuk a Bitwardent: -Világszínvonalú titkosítási jelszavak fejlett végpontok közötti titkosítással (AES-256 bit, titkosított hashtag és PBKDF2 SHA-256) védettek, így az adatok biztonságban és titokban maradnak. +More reasons to choose Bitwarden: -Beépített jelszógenerátor A biztonsági követelmények alapján erős, egyedi és véletlenszerű jelszavakat hozhat létre minden gyakran látogatott webhelyen. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Globális fordítások +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -A Bitwarden fordítások 40 nyelven léteznek és globális közösségünknek köszönhetően egyre bővülnek. Többplatformos alkalmazások Biztonságos és megoszthatja az érzékeny adatokat a Bitwarden Széfben bármely böngészőből, mobileszközről vagy asztali operációs rendszerből stb. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - Egy biztonságos és ingyenes jelszókezelő az összes eszközre + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. A széf szinkronizálása és elérése több eszközön. diff --git a/apps/browser/store/locales/id/copy.resx b/apps/browser/store/locales/id/copy.resx index b52252a342..b0791fa3b1 100644 --- a/apps/browser/store/locales/id/copy.resx +++ b/apps/browser/store/locales/id/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Pengelola Sandi Gratis + Bitwarden Password Manager - Pengelola sandi yang aman dan gratis untuk semua perangkat Anda + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Pengelola sandi yang aman dan gratis untuk semua perangkat Anda + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sinkronkan dan akses brankas Anda dari beberapa perangkat diff --git a/apps/browser/store/locales/it/copy.resx b/apps/browser/store/locales/it/copy.resx index 56bf9a907c..bcbbe10512 100644 --- a/apps/browser/store/locales/it/copy.resx +++ b/apps/browser/store/locales/it/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Password Manager Gratis + Bitwarden Password Manager - Un password manager sicuro e gratis per tutti i tuoi dispositivi + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. è la società madre di 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NOMINATO MIGLIOR PASSWORD MANAGER DA THE VERGE, U.S. NEWS & WORLD REPORT, CNET, E ALTRO. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Gestisci, archivia, proteggi, e condividi password illimitate su dispositivi illimitati da qualsiasi luogo. Bitwarden offre soluzioni di gestione delle password open-source a tutti, a casa, al lavoro, o in viaggio. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Genera password forti, uniche, e casuali in base ai requisiti di sicurezza per ogni sito web che frequenti. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send trasmette rapidamente informazioni crittate - via file e testo in chiaro - direttamente a chiunque. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offre piani Teams ed Enterprise per le aziende così puoi condividere le password in modo sicuro con i tuoi colleghi. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Perché Scegliere Bitwarden: -Crittografia Di Livello Mondiale -Le password sono protette con crittografia end-to-end avanzata (AES-256 bit, salted hashing, e PBKDF2 SHA-256) per tenere i tuoi dati al sicuro e privati. +More reasons to choose Bitwarden: -Generatore Di Password Integrato -Genera password forti, uniche e casuali in base ai requisiti di sicurezza per ogni sito web che frequenti. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Traduzioni Globali -Le traduzioni di Bitwarden esistono in 40 lingue e sono in crescita grazie alla nostra comunità globale. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Applicazioni Multipiattaforma -Proteggi e condividi i dati sensibili all'interno della tua cassaforte di Bitwarden da qualsiasi browser, dispositivo mobile, o sistema operativo desktop, e altro. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Un password manager sicuro e gratis per tutti i tuoi dispositivi + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sincronizza e accedi alla tua cassaforte da più dispositivi diff --git a/apps/browser/store/locales/ja/copy.resx b/apps/browser/store/locales/ja/copy.resx index 13ce1bc4e9..67c479fcde 100644 --- a/apps/browser/store/locales/ja/copy.resx +++ b/apps/browser/store/locales/ja/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - 無料パスワードマネージャー + Bitwarden Password Manager - あらゆる端末で使える、安全な無料パスワードマネージャー + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc.は8bit Solutions LLC.の親会社です。 + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -THE VERGEやU.S. NEWS、WORLD REPORT、CNETなどからベストパスワードマネージャーに選ばれました。 +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -端末や場所を問わずパスワードの管理・保存・保護・共有を無制限にできます。Bitwardenは自宅や職場、外出先でもパスワード管理をすべての人に提供し、プログラムコードは公開されています。 +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -よく利用するどのWebサイトでも、セキュリティ条件にそった強力でユニークなパスワードをランダムに生成することができます。 +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Sendは、暗号化した情報(ファイルや平文)をすぐに誰にでも直接送信することができます。 +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwardenは企業向けにTeamsとEnterpriseのプランを提供しており、パスワードを同僚と安全に共有することができます。 +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Bitwardenを選ぶ理由は? -・世界最高レベルの暗号化 -パスワードは高度なエンドツーエンド暗号化(AES-256 bit、salted hashing、PBKDF2 SHA-256)で保護されるので、データは安全に非公開で保たれます。 +More reasons to choose Bitwarden: -・パスワード生成機能 -よく利用するどのWebサイトでも、セキュリティ条件にそった強力でユニークなパスワードをランダムに生成することができます。 +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -・グローバルな翻訳 -Bitwardenは40ヶ国語に翻訳されており、グローバルなコミュニティのおかげで増え続けています。 +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -・クロスプラットフォームアプリケーション -あなたのBitwarden Vaultで、ブラウザ・モバイル機器・デスクトップOSなどの垣根を超えて、機密データを保護・共有することができます。 +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - あらゆる端末で使える、安全な無料パスワードマネージャー + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. 複数の端末で保管庫に同期&アクセス diff --git a/apps/browser/store/locales/ka/copy.resx b/apps/browser/store/locales/ka/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/ka/copy.resx +++ b/apps/browser/store/locales/ka/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/km/copy.resx b/apps/browser/store/locales/km/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/km/copy.resx +++ b/apps/browser/store/locales/km/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/kn/copy.resx b/apps/browser/store/locales/kn/copy.resx index 6928f557e4..f68f2c25da 100644 --- a/apps/browser/store/locales/kn/copy.resx +++ b/apps/browser/store/locales/kn/copy.resx @@ -1,17 +1,17 @@  - @@ -118,40 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - ಬಿಟ್ವರ್ಡ್ – ಉಚಿತ ಪಾಸ್ವರ್ಡ್ ನಿರ್ವಾಹಕ + Bitwarden Password Manager - ನಿಮ್ಮ ಎಲ್ಲಾ ಸಾಧನಗಳಿಗೆ ಸುರಕ್ಷಿತ ಮತ್ತು ಉಚಿತ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕ + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - ಬಿಟ್ವಾರ್ಡೆನ್, ಇಂಕ್. 8 ಬಿಟ್ ಸೊಲ್ಯೂಷನ್ಸ್ ಎಲ್ಎಲ್ ಸಿ ಯ ಮೂಲ ಕಂಪನಿಯಾಗಿದೆ. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -ವರ್ಜ್, ಯು.ಎಸ್. ನ್ಯೂಸ್ & ವರ್ಲ್ಡ್ ರಿಪೋರ್ಟ್, ಸಿನೆಟ್ ಮತ್ತು ಹೆಚ್ಚಿನದರಿಂದ ಉತ್ತಮ ಪಾಸ್‌ವರ್ಡ್ ವ್ಯವಸ್ಥಾಪಕ ಎಂದು ಹೆಸರಿಸಲಾಗಿದೆ. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -ಎಲ್ಲಿಂದಲಾದರೂ ಅನಿಯಮಿತ ಸಾಧನಗಳಲ್ಲಿ ಅನಿಯಮಿತ ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ, ಸಂಗ್ರಹಿಸಿ, ಸುರಕ್ಷಿತಗೊಳಿಸಿ ಮತ್ತು ಹಂಚಿಕೊಳ್ಳಿ. ಮನೆಯಲ್ಲಿ, ಕೆಲಸದಲ್ಲಿ ಅಥವಾ ಪ್ರಯಾಣದಲ್ಲಿರಲಿ ಪ್ರತಿಯೊಬ್ಬರಿಗೂ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಓಪನ್ ಸೋರ್ಸ್ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಹಣಾ ಪರಿಹಾರಗಳನ್ನು ನೀಡುತ್ತದೆ. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -ನೀವು ಆಗಾಗ್ಗೆ ಪ್ರತಿ ವೆಬ್‌ಸೈಟ್‌ಗೆ ಸುರಕ್ಷತಾ ಅವಶ್ಯಕತೆಗಳನ್ನು ಆಧರಿಸಿ ಬಲವಾದ, ಅನನ್ಯ ಮತ್ತು ಯಾದೃಚ್ pass ಿಕ ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ರಚಿಸಿ. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -ಬಿಟ್‌ವಾರ್ಡೆನ್ ಕಳುಹಿಸಿ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಮಾಹಿತಿಯನ್ನು ತ್ವರಿತವಾಗಿ ರವಾನಿಸುತ್ತದೆ --- ಫೈಲ್‌ಗಳು ಮತ್ತು ಸರಳ ಪಠ್ಯ - ನೇರವಾಗಿ ಯಾರಿಗಾದರೂ. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -ಬಿಟ್‌ವಾರ್ಡೆನ್ ಕಂಪೆನಿಗಳಿಗೆ ತಂಡಗಳು ಮತ್ತು ಎಂಟರ್‌ಪ್ರೈಸ್ ಯೋಜನೆಗಳನ್ನು ನೀಡುತ್ತದೆ ಆದ್ದರಿಂದ ನೀವು ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ಸಹೋದ್ಯೋಗಿಗಳೊಂದಿಗೆ ಸುರಕ್ಷಿತವಾಗಿ ಹಂಚಿಕೊಳ್ಳಬಹುದು. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -ಬಿಟ್‌ವಾರ್ಡೆನ್ ಅನ್ನು ಏಕೆ ಆರಿಸಬೇಕು: -ವಿಶ್ವ ದರ್ಜೆಯ ಗೂ ry ಲಿಪೀಕರಣ -ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ಸುಧಾರಿತ ಎಂಡ್-ಟು-ಎಂಡ್ ಎನ್‌ಕ್ರಿಪ್ಶನ್ (ಎಇಎಸ್ -256 ಬಿಟ್, ಉಪ್ಪುಸಹಿತ ಹ್ಯಾಶ್‌ಟ್ಯಾಗ್ ಮತ್ತು ಪಿಬಿಕೆಡಿಎಫ್ 2 ಎಸ್‌ಎಚ್‌ಎ -256) ನೊಂದಿಗೆ ರಕ್ಷಿಸಲಾಗಿದೆ ಆದ್ದರಿಂದ ನಿಮ್ಮ ಡೇಟಾ ಸುರಕ್ಷಿತ ಮತ್ತು ಖಾಸಗಿಯಾಗಿರುತ್ತದೆ. +More reasons to choose Bitwarden: -ಅಂತರ್ನಿರ್ಮಿತ ಪಾಸ್ವರ್ಡ್ ಜನರೇಟರ್ -ನೀವು ಆಗಾಗ್ಗೆ ಪ್ರತಿ ವೆಬ್‌ಸೈಟ್‌ಗೆ ಸುರಕ್ಷತಾ ಅವಶ್ಯಕತೆಗಳನ್ನು ಆಧರಿಸಿ ಬಲವಾದ, ಅನನ್ಯ ಮತ್ತು ಯಾದೃಚ್ pass ಿಕ ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ರಚಿಸಿ. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -ಜಾಗತಿಕ ಅನುವಾದಗಳು -ಬಿಟ್ವಾರ್ಡೆನ್ ಅನುವಾದಗಳು 40 ಭಾಷೆಗಳಲ್ಲಿ ಅಸ್ತಿತ್ವದಲ್ಲಿವೆ ಮತ್ತು ಬೆಳೆಯುತ್ತಿವೆ, ನಮ್ಮ ಜಾಗತಿಕ ಸಮುದಾಯಕ್ಕೆ ಧನ್ಯವಾದಗಳು. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -ಕ್ರಾಸ್ ಪ್ಲಾಟ್‌ಫಾರ್ಮ್ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು -ಯಾವುದೇ ಬ್ರೌಸರ್, ಮೊಬೈಲ್ ಸಾಧನ, ಅಥವಾ ಡೆಸ್ಕ್‌ಟಾಪ್ ಓಎಸ್ ಮತ್ತು ಹೆಚ್ಚಿನವುಗಳಿಂದ ನಿಮ್ಮ ಬಿಟ್‌ವಾರ್ಡನ್ ವಾಲ್ಟ್‌ನಲ್ಲಿ ಸೂಕ್ಷ್ಮ ಡೇಟಾವನ್ನು ಸುರಕ್ಷಿತಗೊಳಿಸಿ ಮತ್ತು ಹಂಚಿಕೊಳ್ಳಿ. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - ನಿಮ್ಮ ಎಲ್ಲಾ ಸಾಧನಗಳಿಗೆ ಸುರಕ್ಷಿತ ಮತ್ತು ಉಚಿತ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕ + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. ಅನೇಕ ಸಾಧನಗಳಿಂದ ನಿಮ್ಮ ವಾಲ್ಟ್ ಅನ್ನು ಸಿಂಕ್ ಮಾಡಿ ಮತ್ತು ಪ್ರವೇಶಿಸಿ diff --git a/apps/browser/store/locales/ko/copy.resx b/apps/browser/store/locales/ko/copy.resx index 0fb5dd713f..fdfb93ad6a 100644 --- a/apps/browser/store/locales/ko/copy.resx +++ b/apps/browser/store/locales/ko/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - 무료 비밀번호 관리자 + Bitwarden Password Manager - 당신의 모든 기기에서 사용할 수 있는, 안전한 무료 비밀번호 관리자 + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc.은 8bit Solutions LLC.의 모회사입니다. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -VERGE, U.S. NEWS, WORLD REPORT, CNET 등에서 최고의 비밀번호 관리자라고 평가했습니다! +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -관리하고, 보관하고, 보호하고, 어디에서든 어떤 기기에서나 무제한으로 비밀번호를 공유하세요. Bitwarden은 모두에게 오픈소스 비밀번호 관리 솔루션을 제공합니다. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -강하고, 독특하고, 랜덤한 비밀번호를 모든 웹사이트의 보안 요구사항에 따라 생성할 수 있습니다. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send는 빠르게 암호화된 파일과 텍스트를 모두에게 전송할 수 있습니다. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden은 회사들을 위해 팀과 기업 플랜을 제공해서 동료에게 안전하게 비밀번호를 공유할 수 있습니다. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Bitwarden을 선택하는 이유: -세계 최고의 암호화 -비밀번호는 고급 종단간 암호화 (AES-256 bit, salted hashtag, 그리고 PBKDF2 SHA-256)을 이용하여 보호되기 때문에 데이터를 안전하게 보관할 수 있습니다. +More reasons to choose Bitwarden: -내장 비밀번호 생성기 -강하고, 독특하고, 랜덤한 비밀번호를 모든 웹사이트의 보안 요구사항에 따라 생성할 수 있습니다. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -언어 지원 -Bitwarden 번역은 전 세계의 커뮤니티 덕분에 40개의 언어를 지원하고 더 성장하고 있습니다. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -크로스 플랫폼 애플리케이션 -Bitwarden 보관함에 있는 민감한 정보를 어떠한 브라우저, 모바일 기기, 데스크톱 OS 등을 이용하여 보호하고 공유하세요. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - 당신의 모든 기기에서 사용할 수 있는, 안전한 무료 비밀번호 관리자 + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. 여러 기기에서 보관함에 접근하고 동기화할 수 있습니다. diff --git a/apps/browser/store/locales/lt/copy.resx b/apps/browser/store/locales/lt/copy.resx index 92009c5c6d..d83c6ca99a 100644 --- a/apps/browser/store/locales/lt/copy.resx +++ b/apps/browser/store/locales/lt/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – nemokamas slaptažodžių tvarkyklė + Bitwarden Password Manager - Saugi ir nemokama slaptažodžių tvarkyklė visiems įrenginiams + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. yra patronuojančioji 8bit Solutions LLC įmonė. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -GERIAUSIU SLAPTAŽODŽIŲ TVARKYTOJU PRIPAŽINTAS THE VERGE, U.S. NEWS & WORLD REPORT, CNET IR KT. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Tvarkyk, laikyk, saugok ir bendrink neribotą skaičių slaptažodžių neribotuose įrenginiuose iš bet kurios vietos. Bitwarden teikia atvirojo kodo slaptažodžių valdymo sprendimus visiems – tiek namuose, tiek darbe, ar keliaujant. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generuok stiprius, unikalius ir atsitiktinius slaptažodžius pagal saugos reikalavimus kiekvienai lankomai svetainei. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send greitai perduoda užšifruotą informaciją – failus ir paprastą tekstą – tiesiogiai bet kam. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden siūlo komandoms ir verslui planus įmonėms, kad galėtum saugiai dalytis slaptažodžiais su kolegomis. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Kodėl rinktis Bitwarden: -Pasaulinės klasės šifravimas -Slaptažodžiai yra saugomi su pažangiu šifravimu nuo galo iki galo (AES-256 bitų, sūdytu šifravimu ir PBKDF2 SHA-256), todėl tavo duomenys išliks saugūs ir privatūs. +More reasons to choose Bitwarden: -Integruotas slaptažodžių generatorius -Generuok stiprius, unikalius ir atsitiktinius slaptažodžius pagal saugos reikalavimus kiekvienai dažnai lankomai svetainei. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Visuotiniai vertimai -Mūsų pasaulinės bendruomenės dėka Bitwarden vertimai egzistuoja 40 kalbose ir vis daugėja. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Įvairių platformų programos -Apsaugok ir bendrink neskelbtinus duomenis savo Bitwarden Vault iš bet kurios naršyklės, mobiliojo įrenginio ar darbalaukio OS ir kt. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Saugi ir nemokama slaptažodžių tvarkyklė visiems įrenginiams + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Pasiekite savo saugyklą iš kelių įrenginių diff --git a/apps/browser/store/locales/lv/copy.resx b/apps/browser/store/locales/lv/copy.resx index aec5e836c1..e64cc2eb3a 100644 --- a/apps/browser/store/locales/lv/copy.resx +++ b/apps/browser/store/locales/lv/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Bezmaksas Paroļu Pārvaldnieks + Bitwarden Password Manager - Drošs un bezmaksas paroļu pārvaldnieks priekš visām jūsu ierīcēm. + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. ir 8bit Solutions LLC mātesuzņēmums. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -THE VERGE, U.S. NEWS & WORLD REPORT, CNET UN CITI ATZINA PAR LABĀKO PAROĻU PĀRVALDNIEKU. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Pārvaldi, uzglabā, aizsargā un kopīgo neierobežotu skaitu paroļu neierobežotā skaitā ierīču no jebkuras vietas. Bitwarden piedāvā atvērtā koda paroļu pārvaldības risinājumus ikvienam - gan mājās, gan darbā, gan ceļā. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Ģenerē spēcīgas, unikālas un nejaušas paroles, pamatojoties uz drošības prasībām, katrai bieži apmeklētai vietnei. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send ātri pārsūta šifrētu informāciju - failus un atklātu tekstu - tieši jebkuram. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden piedāvā Teams un Enterprise plānus uzņēmumiem, lai tu varētu droši kopīgot paroles ar kolēģiem. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Kāpēc izvēlēties Bitwarden: -Pasaules klases šifrēšana -Paroles tiek aizsargātas ar modernu end-to-end šifrēšanu (AES-256 bitu, sālītu šifrēšanu un PBKDF2 SHA-256), lai tavi dati paliktu droši un privāti. +More reasons to choose Bitwarden: -Iebūvēts paroļu ģenerators -Ģenerē spēcīgas, unikālas un nejaušas paroles, pamatojoties uz drošības prasībām katrai bieži apmeklētai vietnei. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Globālie tulkojumi -Bitwarden tulkojumi ir pieejami 40 valodās, un to skaits turpina pieaugt, pateicoties mūsu globālajai kopienai. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Starpplatformu lietojumprogrammas -Nodrošini un kopīgo sensitīvus datus savā Bitwarden Seifā no jebkuras pārlūkprogrammas, mobilās ierīces vai darbvirsmas operētājsistēmas un daudz ko citu. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Drošs un bezmaksas paroļu pārvaldnieks priekš visām jūsu ierīcēm. + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sinhronizē un piekļūsti savai glabātavai no vairākām ierīcēm diff --git a/apps/browser/store/locales/ml/copy.resx b/apps/browser/store/locales/ml/copy.resx index cf9b631227..e22993d5b7 100644 --- a/apps/browser/store/locales/ml/copy.resx +++ b/apps/browser/store/locales/ml/copy.resx @@ -1,17 +1,17 @@  - @@ -118,27 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - സൗജന്യ പാസ്സ്‌വേഡ് മാനേജർ + Bitwarden Password Manager - നിങ്ങളുടെ എല്ലാ ഉപകരണങ്ങൾക്കും സുരക്ഷിതവും സൗജന്യവുമായ പാസ്‌വേഡ് മാനേജർ + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - നിങ്ങളുടെ എല്ലാ ലോഗിനുകളും പാസ്‌വേഡുകളും സംഭരിക്കുന്നതിനുള്ള ഏറ്റവും എളുപ്പവും സുരക്ഷിതവുമായ മാർഗ്ഗമാണ് Bitwarden, ഒപ്പം നിങ്ങളുടെ എല്ലാ ഉപകരണങ്ങളും തമ്മിൽ സമന്വയിപ്പിക്കുകയും ചെയ്യുന്നു. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -പാസ്‌വേഡ് മോഷണം ഗുരുതരമായ പ്രശ്‌നമാണ്. നിങ്ങൾ ഉപയോഗിക്കുന്ന വെബ്‌സൈറ്റുകളും അപ്ലിക്കേഷനുകളും എല്ലാ ദിവസവും ആക്രമണത്തിലാണ്. സുരക്ഷാ ലംഘനങ്ങൾ സംഭവിക്കുകയും നിങ്ങളുടെ പാസ്‌വേഡുകൾ മോഷ്‌ടിക്കപ്പെടുകയും ചെയ്യുന്നു. അപ്ലിക്കേഷനുകളിലും വെബ്‌സൈറ്റുകളിലും ഉടനീളം സമാന പാസ്‌വേഡുകൾ നിങ്ങൾ വീണ്ടും ഉപയോഗിക്കുമ്പോൾ ഹാക്കർമാർക്ക് നിങ്ങളുടെ ഇമെയിൽ, ബാങ്ക്, മറ്റ് പ്രധാനപ്പെട്ട അക്കൗണ്ടുകൾ എന്നിവ എളുപ്പത്തിൽ ആക്‌സസ്സുചെയ്യാനാകും. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -നിങ്ങളുടെ എല്ലാ ഉപകരണങ്ങളിലും സമന്വയിപ്പിക്കുന്ന ഒരു എൻ‌ക്രിപ്റ്റ് ചെയ്ത വാൾട്ടിൽ Bitwarden നിങ്ങളുടെ എല്ലാ ലോഗിനുകളും സംഭരിക്കുന്നു. നിങ്ങളുടെ ഉപകരണം വിടുന്നതിനുമുമ്പ് ഇത് പൂർണ്ണമായും എൻ‌ക്രിപ്റ്റ് ചെയ്‌തിരിക്കുന്നതിനാൽ, നിങ്ങളുടെ ഡാറ്റ നിങ്ങൾക്ക് മാത്രമേ ആക്‌സസ് ചെയ്യാൻ കഴിയൂ . Bitwarden ടീമിന് പോലും നിങ്ങളുടെ ഡാറ്റ വായിക്കാൻ കഴിയില്ല. നിങ്ങളുടെ ഡാറ്റ AES-256 ബിറ്റ് എൻ‌ക്രിപ്ഷൻ, സാൾട്ടിങ് ഹാഷിംഗ്, PBKDF2 SHA-256 എന്നിവ ഉപയോഗിച്ച് അടച്ചിരിക്കുന്നു. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -100% ഓപ്പൺ സോഴ്‌സ് സോഫ്റ്റ്വെയറാണ് Bitwarden . Bitwarden സോഴ്‌സ് കോഡ് GitHub- ൽ ഹോസ്റ്റുചെയ്‌തിരിക്കുന്നു, മാത്രമല്ല എല്ലാവർക്കും ഇത് അവലോകനം ചെയ്യാനും ഓഡിറ്റുചെയ്യാനും ബിറ്റ് വാർഡൻ കോഡ്ബേസിലേക്ക് സംഭാവന ചെയ്യാനും സ്വാതന്ത്ര്യമുണ്ട്. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. + +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. + +Use Bitwarden to secure your workforce and share sensitive information with colleagues. +More reasons to choose Bitwarden: +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. + +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - നിങ്ങളുടെ എല്ലാ ഉപകരണങ്ങൾക്കും സുരക്ഷിതവും സൗജന്യവുമായ പാസ്‌വേഡ് മാനേജർ. + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. ഒന്നിലധികം ഉപകരണങ്ങളിൽ നിന്ന് നിങ്ങളുടെ വാൾട് സമന്വയിപ്പിച്ച് ആക്‌സസ്സുചെയ്യുക diff --git a/apps/browser/store/locales/mr/copy.resx b/apps/browser/store/locales/mr/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/mr/copy.resx +++ b/apps/browser/store/locales/mr/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/my/copy.resx b/apps/browser/store/locales/my/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/my/copy.resx +++ b/apps/browser/store/locales/my/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/nb/copy.resx b/apps/browser/store/locales/nb/copy.resx index 74a8558db6..26a09cc855 100644 --- a/apps/browser/store/locales/nb/copy.resx +++ b/apps/browser/store/locales/nb/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden — Fri passordbehandling + Bitwarden Password Manager - En sikker og fri passordbehandler for alle dine PCer og mobiler + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - En sikker og fri passordbehandler for alle dine PCer og mobiler + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Synkroniser og få tilgang til ditt hvelv fra alle dine enheter diff --git a/apps/browser/store/locales/ne/copy.resx b/apps/browser/store/locales/ne/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/ne/copy.resx +++ b/apps/browser/store/locales/ne/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/nl/copy.resx b/apps/browser/store/locales/nl/copy.resx index e0779ba777..44dd02b439 100644 --- a/apps/browser/store/locales/nl/copy.resx +++ b/apps/browser/store/locales/nl/copy.resx @@ -1,17 +1,17 @@  - @@ -118,40 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Gratis wachtwoordbeheer + Bitwarden Password Manager - Een veilige en gratis oplossing voor wachtwoordbeheer voor al je apparaten + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is het moederbedrijf van 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -BESTE WACHTWOORDBEHEERDER VOLGENS THE VERGE, U.S. NEWS & WORLD REPORT, CNET EN ANDEREN. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Beheer, bewaar, beveilig en deel een onbeperkt aantal wachtwoorden op een onbeperkt aantal apparaten, waar je ook bent. Bitwarden levert open source wachtwoordbeheeroplossingen voor iedereen, of dat nu thuis, op het werk of onderweg is. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Genereer sterke, unieke en willekeurige wachtwoorden op basis van beveiligingsvereisten voor elke website die je bezoekt. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send verzendt snel versleutelde informatie --- bestanden en platte tekst -- rechtstreeks naar iedereen. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden biedt Teams- en Enterprise-abonnementen voor bedrijven, zodat je veilig wachtwoorden kunt delen met collega's. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Waarom Bitwarden: -Versleuteling van wereldklasse -Wachtwoorden worden beschermd met geavanceerde end-to-end-codering (AES-256 bit, salted hashtag en PBKDF2 SHA-256) zodat jouw gegevens veilig en privé blijven. +More reasons to choose Bitwarden: -Ingebouwde wachtwoordgenerator -Genereer sterke, unieke en willekeurige wachtwoorden op basis van beveiligingsvereisten voor elke website die je bezoekt. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Wereldwijde vertalingen -Bitwarden-vertalingen bestaan ​​in 40 talen en groeien dankzij onze wereldwijde community. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Platformoverschrijdende toepassingen -Beveilig en deel gevoelige gegevens binnen uw Bitwarden Vault vanuit elke browser, mobiel apparaat of desktop-besturingssysteem, en meer. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - Een veilige en gratis oplossing voor wachtwoordbeheer voor al uw apparaten + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Synchroniseer en gebruik je kluis op meerdere apparaten diff --git a/apps/browser/store/locales/nn/copy.resx b/apps/browser/store/locales/nn/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/nn/copy.resx +++ b/apps/browser/store/locales/nn/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/or/copy.resx b/apps/browser/store/locales/or/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/or/copy.resx +++ b/apps/browser/store/locales/or/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/pl/copy.resx b/apps/browser/store/locales/pl/copy.resx index 5b3941cb7e..60709c7d4d 100644 --- a/apps/browser/store/locales/pl/copy.resx +++ b/apps/browser/store/locales/pl/copy.resx @@ -1,17 +1,17 @@  - @@ -118,40 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - darmowy menedżer haseł + Bitwarden Password Manager - Bezpieczny i darmowy menedżer haseł dla wszystkich Twoich urządzeń + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. jest macierzystą firmą 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAZWANY NAJLEPSZYM MENEDŻEREM HASEŁ PRZEZ THE VERGE, US NEWS & WORLD REPORT, CNET I WIĘCEJ. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Zarządzaj, przechowuj, zabezpieczaj i udostępniaj nieograniczoną liczbę haseł na nieograniczonej liczbie urządzeń z każdego miejsca. Bitwarden dostarcza rozwiązania do zarządzania hasłami z otwartym kodem źródłowym każdemu, niezależnie od tego, czy jest w domu, w pracy, czy w podróży. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generuj silne, unikalne i losowe hasła w oparciu o wymagania bezpieczeństwa dla każdej odwiedzanej strony. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Funkcja Bitwarden Send szybko przesyła zaszyfrowane informacje --- pliki i zwykły tekst -- bezpośrednio do każdego. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden oferuje plany dla zespołów i firm, dzięki czemu możesz bezpiecznie udostępniać hasła współpracownikom. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Dlaczego warto wybrać Bitwarden: -Szyfrowanie światowej klasy -Hasła są chronione za pomocą zaawansowanego szyfrowania typu end-to-end (AES-256 bitów, dodatkowy ciąg zaburzający i PBKDF2 SHA-256), dzięki czemu Twoje dane pozostają bezpieczne i prywatne. +More reasons to choose Bitwarden: -Wbudowany generator haseł -Generuj silne, unikalne i losowe hasła w oparciu o wymagania bezpieczeństwa dla każdej odwiedzanej strony. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Przetłumaczone aplikacje -Tłumaczenia Bitwarden są dostępne w 40 językach i rosną dzięki naszej globalnej społeczności. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Aplikacje wieloplatformowe -Zabezpiecz i udostępniaj poufne dane w swoim sejfie Bitwarden z dowolnej przeglądarki, urządzenia mobilnego, systemu operacyjnego i nie tylko. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - Bezpieczny i darmowy menedżer haseł dla wszystkich Twoich urządzeń + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Synchronizacja i dostęp do sejfu z różnych urządzeń diff --git a/apps/browser/store/locales/pt_BR/copy.resx b/apps/browser/store/locales/pt_BR/copy.resx index 48111fa814..8b99c436d0 100644 --- a/apps/browser/store/locales/pt_BR/copy.resx +++ b/apps/browser/store/locales/pt_BR/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Gerenciador de Senhas Gratuito + Bitwarden Password Manager - Um gerenciador de senhas gratuito e seguro para todos os seus dispositivos + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. é a empresa matriz da 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NOMEADA MELHOR GERENCIADORA DE SENHAS PELA VERGE, U.S. NEWS & WORLD REPORT, CNET, E MUITO MAIS. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Gerenciar, armazenar, proteger e compartilhar senhas ilimitadas através de dispositivos ilimitados de qualquer lugar. Bitwarden fornece soluções de gerenciamento de senhas de código aberto para todos, seja em casa, no trabalho ou em viagem. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Gere senhas fortes, únicas e aleatórias com base nos requisitos de segurança para cada site que você frequenta. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -A Bitwarden Send transmite rapidamente informações criptografadas --- arquivos e texto em formato de placa -- diretamente para qualquer pessoa. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden oferece equipes e planos empresariais para empresas para que você possa compartilhar senhas com colegas com segurança. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Por que escolher Bitwarden: -Criptografia de Classe Mundial -As senhas são protegidas com criptografia avançada de ponta a ponta (AES-256 bit, salted hashing e PBKDF2 SHA-256) para que seus dados permaneçam seguros e privados. +More reasons to choose Bitwarden: -Gerador de senhas embutido -Gerar senhas fortes, únicas e aleatórias com base nos requisitos de segurança para cada site que você freqüenta. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Traduções globais -As traduções Bitwarden existem em 40 idiomas e estão crescendo, graças à nossa comunidade global. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Aplicações multiplataforma -Proteja e compartilhe dados sensíveis dentro de seu Bitwarden Vault a partir de qualquer navegador, dispositivo móvel ou SO desktop, e muito mais. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Um gerenciador de senhas gratuito e seguro para todos os seus dispositivos + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sincronize e acesse o seu cofre através de múltiplos dispositivos diff --git a/apps/browser/store/locales/pt_PT/copy.resx b/apps/browser/store/locales/pt_PT/copy.resx index 845a94a3ca..d310629612 100644 --- a/apps/browser/store/locales/pt_PT/copy.resx +++ b/apps/browser/store/locales/pt_PT/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Gestor de Palavras-passe Gratuito + Bitwarden Password Manager - Um gestor de palavras-passe seguro e gratuito para todos os seus dispositivos + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - A Bitwarden, Inc. é a empresa-mãe da 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NOMEADO O MELHOR GESTOR DE PALAVRAS-PASSE PELO THE VERGE, U.S. NEWS & WORLD REPORT, CNET E MUITO MAIS. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Gerir, armazenar, proteger e partilhar palavras-passe ilimitadas em dispositivos ilimitados a partir de qualquer lugar. O Bitwarden fornece soluções de gestão de palavras-passe de código aberto para todos, seja em casa, no trabalho ou onde estiver. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Gera palavras-passe fortes, únicas e aleatórias com base em requisitos de segurança para todos os sites que frequenta. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -O Bitwarden Send transmite rapidamente informações encriptadas - ficheiros e texto simples - diretamente a qualquer pessoa. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -O Bitwarden oferece os planos Equipas e Empresarial destinados a empresas, para que possa partilhar de forma segura as palavras-passe com os seus colegas. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Razões para escolher o Bitwarden: -Encriptação de classe mundial -As palavras-passe são protegidas com encriptação avançada de ponta a ponta (AES-256 bit, salted hashtag e PBKDF2 SHA-256) para que os seus dados permaneçam seguros e privados. +More reasons to choose Bitwarden: -Gerador de palavras-passe incorporado -Gera palavras-passe fortes, únicas e aleatórias com base nos requisitos de segurança para todos os sites que frequenta. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Traduções globais -O Bitwarden está traduzido em 40 idiomas e está a crescer, graças à nossa comunidade global. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Aplicações multiplataforma -Proteja e partilhe dados confidenciais no seu cofre Bitwarden a partir de qualquer navegador, dispositivo móvel ou sistema operativo de computador, e muito mais. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Um gestor de palavras-passe seguro e gratuito para todos os seus dispositivos + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sincronize e aceda ao seu cofre através de vários dispositivos diff --git a/apps/browser/store/locales/ro/copy.resx b/apps/browser/store/locales/ro/copy.resx index 0e12b289af..7b0070fad2 100644 --- a/apps/browser/store/locales/ro/copy.resx +++ b/apps/browser/store/locales/ro/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Manager de parole gratuit + Bitwarden Password Manager - Un manager de parole sigur și gratuit pentru toate dispozitivele dvs. + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. este compania mamă a 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NUMIT CEL MAI BUN MANAGER DE PAROLE DE CĂTRE THE VERGE, U.S. NEWS & WORLD REPORT, CNET ȘI MULȚI ALȚII. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Gestionați, stocați, securizați și partajați un număr nelimitat de parole pe un număr nelimitat de dispozitive, de oriunde. Bitwarden oferă soluții open source de gestionare a parolelor pentru toată lumea, fie că se află acasă, la serviciu sau în mișcare. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generați parole puternice, unice și aleatorii, bazate pe cerințe de securitate pentru fiecare site web pe care îl frecventați. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send transmite rapid informații criptate --- fișiere și text simple -- direct către oricine. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden oferă planuri Teams și Enterprise pentru companii, astfel încât să puteți partaja în siguranță parolele cu colegii. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -De ce să alegeți Bitwarden: -Criptare de clasă mondială -Parolele sunt protejate cu criptare avansată end-to-end (AES-256 bit, salted hashing și PBKDF2 SHA-256), astfel încât datele dvs. să rămână sigure și private. +More reasons to choose Bitwarden: -Generator de parole încorporat -Generați parole puternice, unice și aleatorii, bazate pe cerințele de securitate pentru fiecare site web pe care îl frecventați. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Traduceri la nivel mondial -Bitwarden este deja tradus în 40 de limbi și numărul lor crește, datorită comunității noastre mondiale. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Aplicații multi-platformă -Protejați și partajați date sensibile în seiful Bitwarden de pe orice browser, dispozitiv mobil sau sistem de operare desktop și multe altele. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Un manager de parole sigur și gratuit, pentru toate dispozitivele dvs. + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sincronizează și accesează seiful dvs. de pe multiple dispozitive diff --git a/apps/browser/store/locales/ru/copy.resx b/apps/browser/store/locales/ru/copy.resx index 4e48ecbc88..212a899f76 100644 --- a/apps/browser/store/locales/ru/copy.resx +++ b/apps/browser/store/locales/ru/copy.resx @@ -1,17 +1,17 @@  - @@ -118,40 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – бесплатный менеджер паролей + Bitwarden Password Manager - Защищенный и бесплатный менеджер паролей для всех ваших устройств + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. является материнской компанией 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -НАЗВАН ЛУЧШИМ ДИСПЕТЧЕРОМ ПАРОЛЕЙ VERGE, US NEWS & WORLD REPORT, CNET И МНОГИМИ ДРУГИМИ. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Управляйте, храните, защищайте и делитесь неограниченным количеством паролей на неограниченном количестве устройств из любого места. Bitwarden предоставляет решения с открытым исходным кодом по управлению паролями для всех, дома, на работе или в дороге. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Создавайте надежные, уникальные и случайные пароли на основе требований безопасности для каждого посещаемого вами сайта. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send быстро передает зашифрованную информацию - файлы и простой текст - напрямую кому угодно. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden предлагает для компаний планы Teams и Enterprise, чтобы вы могли безопасно делиться паролями с коллегами. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Почему выбирают Bitwarden: -Шифрование мирового класса -Пароли защищены передовым сквозным шифрованием (AES-256 bit, соленый хэштег и PBKDF2 SHA-256), поэтому ваши данные остаются в безопасности и конфиденциальности. +More reasons to choose Bitwarden: -Встроенный генератор паролей -Создавайте надежные, уникальные и случайные пароли на основе требований безопасности для каждого посещаемого вами сайта. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. - Глобальные переводы - Переводы Bitwarden существуют на 40 языках и постоянно растут благодаря нашему глобальному сообществу. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. - Кросс-платформенные приложения - Защищайте и делитесь конфиденциальными данными в вашем Bitwarden Vault из любого браузера, мобильного устройства, настольной ОС и т. д. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - Защищенный и бесплатный менеджер паролей для всех ваших устройств + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Синхронизация и доступ к хранилищу с нескольких устройств diff --git a/apps/browser/store/locales/si/copy.resx b/apps/browser/store/locales/si/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/si/copy.resx +++ b/apps/browser/store/locales/si/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/sk/copy.resx b/apps/browser/store/locales/sk/copy.resx index ba2a2a5a07..de7fa7dee3 100644 --- a/apps/browser/store/locales/sk/copy.resx +++ b/apps/browser/store/locales/sk/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Bezplatný správca hesiel + Bitwarden Password Manager - Bezpečný a bezplatný správca hesiel pre všetky vaše zariadenia + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. je materská spoločnosť spoločnosti 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -OHODNOTENÝ AKO NAJLEPŠÍ SPRÁVCA HESIEL V THE VERGE, U.S. NEWS & WORLD REPORT, CNET A ĎALŠÍMI. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Spravujte, ukladajte, zabezpečte a zdieľajte neobmedzený počet hesiel naprieč neobmedzeným počtom zariadení odkiaľkoľvek. Bitwarden ponúka open source riešenie na správu hesiel komukoľvek, kdekoľvek doma, v práci alebo na ceste. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Vygenerujte si silné, unikátne a náhodné heslá podľa bezpečnostných požiadaviek na každej stránke, ktorú navštevujete. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send rýchlo prenesie šifrované informácie -- súbory a text -- priamo komukoľvek. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden ponúka Teams a Enterprise paušály pre firmy, aby ste mohli bezpečne zdieľať hesla s kolegami. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Prečo si vybrať Bitwarden: -Svetová trieda v šifrovaní -Heslá sú chránené pokročilým end-to-end šifrovaním (AES-256 bit, salted hash a PBKDF2 SHA-256), takže Vaše dáta zostanú bezpečné a súkromné. +More reasons to choose Bitwarden: -Vstavaný generátor hesiel -Vygenerujte si silné, unikátne a náhodné heslá podľa bezpečnostných požiadaviek na každej stránke, ktorú navštevujete. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Svetová lokalizácia -Vďaka našej globálnej komunite má Bitwarden neustále rastúcu lokalizáciu už do 40 jazykov. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Aplikácie pre rôzne platformy -Zabezpečte a zdieľajte súkromné dáta prostredníctvom Bitwarden trezora z ktoréhokoľvek prehliadača, mobilného zariadenia, alebo stolného počítača a ďalších. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Bezpečný a bezplatný správca hesiel pre všetky vaše zariadenia + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Synchronizujte a pristupujte k vášmu trezoru z viacerých zariadení diff --git a/apps/browser/store/locales/sl/copy.resx b/apps/browser/store/locales/sl/copy.resx index 83288e3872..80886de48a 100644 --- a/apps/browser/store/locales/sl/copy.resx +++ b/apps/browser/store/locales/sl/copy.resx @@ -1,17 +1,17 @@  - @@ -118,40 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - brezplačni upravljalnik gesel + Bitwarden Password Manager - Varen in brezplačen upravljalnik gesel za vse vaše naprave + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. je matično podjetje podjetja 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAJBOŠJI UPRAVLJALNIK GESEL PO MNEJU THE VERGE, U.S. NEWS & WORLD REPORT, CNET IN DRUGIH. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Upravljajte, shranjujte, varujte in delite neomejeno število gesel na neomejenem številu naprav, kjerkoli. Bitwarden ponuja odprtokodne rešitve za upravljanje gesel vsem, tako doma kot v službi ali na poti. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Ustvarite močna, edinstvena in naključna gesla, skladna z varnostnimi zahtevami za vsako spletno mesto, ki ga obiščete. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Z Bitwarden Send hitro prenesite šifrirane informacije --- datoteke in navadno besedilo -- neposredno komurkoli. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden ponuja storitvi za organizacije Teams in Enterprise, s katerima lahko gesla varno delite s sodelavci. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Zakaj izbrati Bitwarden: -Vrhunsko šifriranje -Gesla so zaščitena z naprednim šifriranjem (AES-256, soljene hash-vrednosti in PBKDF2 SHA-256), tako da vaši podatki ostanejo varni in zasebni. +More reasons to choose Bitwarden: -Vgrajeni generator gesel -Ustvarite močna, edinstvena in naključna gesla v skladu z varnostnimi zahtevami za vsako spletno mesto, ki ga obiščete. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Prevodi za ves svet -Bitwarden je preveden že v 40 jezikov, naša globalna skupnost pa ves čas posodabljan in ustvarja nove prevede. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Deluje na vseh platformah -Varujte in delite svoje občutljive podatke znotraj vašega Bitwarden trezorja v katerem koli brskalniku, mobilni napravi, namiznem računalniku in drugje. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - Varen in brezplačen upravljalnik gesel za vse vaše naprave + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sinhronizirajte svoj trezor gesel in dostopajte do njega z več naprav diff --git a/apps/browser/store/locales/sr/copy.resx b/apps/browser/store/locales/sr/copy.resx index 9bfe799035..9c34d5812a 100644 --- a/apps/browser/store/locales/sr/copy.resx +++ b/apps/browser/store/locales/sr/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Бесплатни Менаџер Лозинке + Bitwarden Password Manager - Сигурни и бесплатни менаџер лозинке за сва Ваша уређаја + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. је матична компанија фирме 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -Именован као најбољи управљач лозинкама од стране новинских сајтова као што су THE VERGE, U.S. NEWS & WORLD REPORT, CNET, и других. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Управљајте, чувајте, обезбедите, и поделите неограничен број лозинки са неограниченог броја уређаја где год да се налазите. Bitwarden свима доноси решења за управљање лозинкама која су отвореног кода, било да сте код куће, на послу, или на путу. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Генеришите јаке, јединствене, и насумичне лозинке у зависности од безбедносних захтева за сваки сајт који често посећујете. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send брзо преноси шифроване информације--- датотеке и обичан текст-- директно и свима. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden нуди планове за компаније и предузећа како бисте могли безбедно да делите лозинке са вашим колегама. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Зашто изабрати Bitwarden: -Шифровање светске класе -Лозинке су заштићене напредним шифровањем од једног до другог краја (AES-256 bit, salted hashing, и PBKDF2 SHA-256) како би ваши подаци остали безбедни и приватни. +More reasons to choose Bitwarden: -Уграђен генератор лозинки -Генеришите јаке, јединствене, и насумичне лозинке у зависности од безбедносних захтева за сваки сајт који често посећујете. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Глобално преведен -Bitwarden преводи постоје за 40 језика и стално се унапређују, захваљујући нашој глобалној заједници. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Вишеплатформне апликације -Обезбедите и поделите осетљиве податке у вашем Bitwarden сефу из било ког претраживача, мобилног уређаја, или desktop оперативног система, и других. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Сигурни и бесплатни менаџер лозинке за сва Ваша уређаја + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Синхронизујте и приступите сефу са више уређаја diff --git a/apps/browser/store/locales/sv/copy.resx b/apps/browser/store/locales/sv/copy.resx index 8b3cb2a402..6406ab013e 100644 --- a/apps/browser/store/locales/sv/copy.resx +++ b/apps/browser/store/locales/sv/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Gratis lösenordshanterare + Bitwarden Password Manager - En säker och gratis lösenordshanterare för alla dina enheter + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. är moderbolag till 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -UTNÄMND TILL DEN BÄSTA LÖSENORDSHANTERAREN AV THE VERGE, U.S. NEWS & WORLD REPORT, CNET MED FLERA. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Hantera, lagra, säkra och dela ett obegränsat antal lösenord mellan ett obegränsat antal enheter var som helst ifrån. Bitwarden levererar lösningar för lösenordshantering med öppen källkod till alla, vare sig det är hemma, på jobbet eller på språng. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generera starka, unika och slumpmässiga lösenord baserat på säkerhetskrav för varje webbplats du besöker. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send överför snabbt krypterad information --- filer och klartext -- direkt till vem som helst. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden erbjuder abonnemang för team och företag så att du säkert kan dela lösenord med kollegor. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Varför välja Bitwarden: -Kryptering i världsklass -Lösenord skyddas med avancerad end-to-end-kryptering (AES-256 bitar, saltad hashtag och PBKDF2 SHA-256) så att dina data förblir säkra och privata. +More reasons to choose Bitwarden: -Inbyggd lösenordsgenerator -Generera starka, unika och slumpmässiga lösenord baserat på säkerhetskrav för varje webbplats du besöker. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Globala översättningar -Översättningar av Bitwarden finns på 40 språk och antalet växer tack vare vår globala gemenskap. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Plattformsoberoende program -Säkra och dela känsliga data i ditt Bitwardenvalv från alla webbläsare, mobiler och datorer. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - En säker och gratis lösenordshanterare för alla dina enheter + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Synkronisera och kom åt ditt valv från flera enheter diff --git a/apps/browser/store/locales/te/copy.resx b/apps/browser/store/locales/te/copy.resx index 191198691d..82e4eb1d88 100644 --- a/apps/browser/store/locales/te/copy.resx +++ b/apps/browser/store/locales/te/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden Password Manager - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Why Choose Bitwarden: + +More reasons to choose Bitwarden: World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. -Cross-Platform Applications +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - A secure and free password manager for all of your devices + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Sync and access your vault from multiple devices diff --git a/apps/browser/store/locales/th/copy.resx b/apps/browser/store/locales/th/copy.resx index 9c8965b01f..f784b1884b 100644 --- a/apps/browser/store/locales/th/copy.resx +++ b/apps/browser/store/locales/th/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – โปรแกรมจัดการรหัสผ่านฟรี + Bitwarden Password Manager - โปรแกรมจัดการรหัสผ่านที่ปลอดภัยและฟรี สำหรับอุปกรณ์ทั้งหมดของคุณ + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. เป็นบริษัทแม่ของ 8bit Solutions LLC + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -ได้รับการระบุชื่อเป็น โปรแกรมจัดการรหัสผ่านที่ดีที่สุด โดย The Verge, U.S. News & World Report, CNET, และที่อื่นๆ +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -สามารถจัดการ จัดเก็บ ปกป้อง และแชร์รหัสผ่านไม่จำกัดจำนวนระหว่างอุปกรณ์ต่างๆ โดยไม่จำกัดจำนวนจากที่ไหนก็ได้ Bitwarden เสนอโซลูชันจัดการรหัสผ่านโอเพนซอร์สให้กับทุกคน ไม่ว่าจะอยู่ที่บ้าน ที่ทำงาน หรือนอกสถานที่ +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -สามารถส่มสร้างรหัสผ่านที่ปลอดภัยและไม่ซ้ำกัน ตามเงื่อนไขความปลอดภัยที่กำหนดได้ สำหรับเว็บไซต์ทุกแห่งที่คุณใช้งานบ่อย +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send สามารถส่งข้อมูลที่ถูกเข้ารหัส --- ไฟล์ หรือ ข้อความ -- ตรงไปยังใครก็ได้ได้อย่างรวดเร็ว +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden มีแผนแบบ Teams และ Enterprise สำหรับบริษัทต่างๆ ซึางคุณสามารถแชร์รหัสผ่านกับเพื่อนร่วมงานได้อย่างปลอดภัย +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -ทำไมควรเลือก Bitwarden: -การเข้ารหัสมาตรฐานโลก -รหัสผ่านจะได้รับการปกป้องด้วยการเข้ารหัสชั้นสูง (AES-256 บิต, salted hashtag, และ PBKDF2 SHA-256) แบบต้นทางถึงปลายทาง เพื่อให้ข้อมูลของคุณปลอดภัยและเป็นส่วนตัว +More reasons to choose Bitwarden: -มีตัวช่วยส่มสร้างรหัสผ่าน -สามารถสุ่มสร้างรหัสผ่านที่ปลอดภัยและไม่ซ้ำกัน ตามเงื่อนไขความปลอดภัยที่กำหนดได้ สำหรับเว็บไซต์ทุกแห่งที่คุณใช้งานบ่อย +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -แปลเป็นภาษาต่างๆ ทั่วโลก -Bitwarden ได้รับการแปลเป็นภาษาต่างๆ กว่า 40 ภาษา และกำลังเพิ่มขึ้นเรื่อยๆ ด้วยความสนับสนุนจากชุมชนผู้ใช้งานทั่วโลก +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -แอปพลิเคชันข้ามแพลตฟอร์ม -ปกป้องและแชร์ข้อมูลอ่อนไหวในตู้เซฟ Bitwarden จากเว็บเบราว์เซอร์ อุปกรณ์มือถือ หรือเดสท็อป หรือช่องทางอื่นๆ +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - โปรแกรมจัดการรหัสผ่านที่ปลอดภัยและฟรี สำหรับอุปกรณ์ทั้งหมดของคุณ + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. ซิงค์และเข้าถึงตู้นิรภัยของคุณจากหลายอุปกรณ์ diff --git a/apps/browser/store/locales/tr/copy.resx b/apps/browser/store/locales/tr/copy.resx index 1fc3e2a34b..539aad3aee 100644 --- a/apps/browser/store/locales/tr/copy.resx +++ b/apps/browser/store/locales/tr/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Ücretsiz Parola Yöneticisi + Bitwarden Password Manager - Tüm aygıtlarınız için güvenli ve ücretsiz bir parola yöneticisi + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc., 8bit Solutions LLC’nin ana şirketidir. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -THE VERGE, U.S. NEWS & WORLD REPORT, CNET VE BİRÇOK MEDYA KURULUŞUNA GÖRE EN İYİ PAROLA YÖNETİCİSİ. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Sınırsız sayıda parolayı istediğiniz kadar cihazda yönetin, saklayın, koruyun ve paylaşın. Bitwarden; herkesin evde, işte veya yolda kullanabileceği açık kaynaklı parola yönetim çözümleri sunuyor. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Sık kullandığınız web siteleri için güvenlik gereksinimlerinize uygun, güçlü, benzersiz ve rastgele parolalar oluşturabilirsiniz. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send, şifrelenmiş bilgileri (dosyalar ve düz metinler) herkese hızlı bir şekilde iletmenizi sağlıyor. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden, parolaları iş arkadaşlarınızla güvenli bir şekilde paylaşabilmeniz için şirketlere yönelik Teams ve Enterprise paketleri de sunuyor. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Neden Bitwarden? -Üst düzey şifreleme -Parolalarınız gelişmiş uçtan uca şifreleme (AES-256 bit, salted hashing ve PBKDF2 SHA-256) ile korunuyor, böylece verileriniz güvende ve gizli kalıyor. +More reasons to choose Bitwarden: -Dahili parola oluşturucu -Sık kullandığınız web siteleri için güvenlik gereksinimlerinize uygun, güçlü, benzersiz ve rastgele parolalar oluşturabilirsiniz. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Çeviriler -Bitwarden 40 dilde kullanılabiliyor ve gönüllü topluluğumuz sayesinde çeviri sayısı giderek artıyor. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Her platformla uyumlu uygulamalar -Bitwarden kasanızdaki hassas verilere her tarayıcıdan, mobil cihazdan veya masaüstü işletim sisteminden ulaşabilir ve onları paylaşabilirsiniz. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Tüm cihazarınız için güvenli ve ücretsiz bir parola yöneticisi + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Hesabınızı senkronize ederek kasanıza tüm cihazlarınızdan ulaşın diff --git a/apps/browser/store/locales/uk/copy.resx b/apps/browser/store/locales/uk/copy.resx index d59cd7f103..5a7de18363 100644 --- a/apps/browser/store/locales/uk/copy.resx +++ b/apps/browser/store/locales/uk/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Безплатний менеджер паролів + Bitwarden Password Manager - Захищений, безплатний менеджер паролів для всіх ваших пристроїв + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - 8bit Solutions LLC є дочірньою компанією Bitwarden, Inc. + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -НАЙКРАЩИЙ МЕНЕДЖЕР ПАРОЛІВ ЗА ВЕРСІЄЮ THE VERGE, U.S. NEWS & WORLD REPORT, CNET, А ТАКОЖ ІНШИХ ВИДАНЬ. +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Зберігайте, захищайте, керуйте і надавайте доступ до паролів на різних пристроях де завгодно. Bitwarden пропонує рішення для керування паролями на основі відкритого програмного коду особистим та корпоративним користувачам на всіх пристроях. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Генеруйте надійні, випадкові та унікальні паролі, які відповідають вимогам безпеки, для кожного вебсайту та сервісу. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Швидко відправляйте будь-кому зашифровану інформацію, як-от файли чи звичайний текст, за допомогою функції Bitwarden Send. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden пропонує командні та корпоративні тарифні плани для компаній, щоб ви могли безпечно обмінюватися паролями з колегами. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Чому варто обрати Bitwarden: -Всесвітньо визнані стандарти шифрування -Паролі захищаються з використанням розширеного наскрізного шифрування (AES-256 bit, хешування з сіллю та PBKDF2 SHA-256), тому ваші дані завжди захищені та приватні. +More reasons to choose Bitwarden: -Вбудований генератор паролів -Генеруйте надійні, випадкові та унікальні паролі, які відповідають вимогам безпеки, для кожного вебсайту та сервісу. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Переклад багатьма мовами -Завдяки нашій глобальній спільноті, Bitwarden перекладено 40 мовами, і їх кількість зростає. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Програми для різних платформ -Зберігайте і діліться важливими даними, а також користуйтеся іншими можливостями у вашому сховищі Bitwarden в будь-якому браузері, мобільному пристрої, чи комп'ютерній операційній системі. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Захищений, безплатний менеджер паролів для всіх ваших пристроїв + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Синхронізуйте й отримуйте доступ до свого сховища на різних пристроях diff --git a/apps/browser/store/locales/vi/copy.resx b/apps/browser/store/locales/vi/copy.resx index 220d50bdfa..e0403d1f32 100644 --- a/apps/browser/store/locales/vi/copy.resx +++ b/apps/browser/store/locales/vi/copy.resx @@ -1,17 +1,17 @@  - @@ -118,41 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Trình quản lý mật khẩu miễn phí + Bitwarden Password Manager - Một trình quản lý mật khẩu an toàn và miễn phí cho mọi thiết bị của bạn + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc là công ty mẹ của 8bit Solutions LLC + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -ĐƯỢC ĐÁNH GIÁ LÀ TRÌNH QUẢN LÝ MẬT KHẨU TỐT NHẤT BỞI NHÀ BÁO LỚN NHƯ THE VERGE, CNET, U.S. NEWS & WORLD REPORT VÀ HƠN NỮA +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -Quản lý, lưu trữ, bảo mật và chia sẻ mật khẩu không giới hạn trên các thiết bị không giới hạn mọi lúc, mọi nơi. Bitwarden cung cấp các giải pháp quản lý mật khẩu mã nguồn mở cho tất cả mọi người, cho dù ở nhà, tại cơ quan hay khi đang di chuyển. +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -Tạo mật khẩu mạnh, không bị trùng và ngẫu nhiên dựa trên các yêu cầu bảo mật cho mọi trang web bạn thường xuyên sử dụng. +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Tính năng 'Bitwarden Send' nhanh chóng truyền thông tin được mã hóa --- tệp và văn bản - trực tiếp đến bất kỳ ai. +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden cung cấp các gói 'Nhóm' và 'Doanh nghiệp' cho các công ty để bạn có thể chia sẻ mật khẩu với đồng nghiệp một cách an toàn. +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -Tại sao bạn nên chọn Bitwarden: -Mã hóa tốt nhất thế giới -Mật khẩu được bảo vệ bằng mã hóa đầu cuối (end-to-end encryption) tiên tiến như AES-256 bit, salted hashtag, và PBKDF2 SHA-256 nên dữ liệu của bạn luôn an toàn và riêng tư. +More reasons to choose Bitwarden: -Trình tạo mật khẩu tích hợp -Tạo mật khẩu mạnh, không bị trùng lặp, và ngẫu nhiên dựa trên các yêu cầu bảo mật cho mọi trang web mà bạn thường xuyên sử dụng. +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -Bản dịch ngôn ngữ từ cộng đồng -Bitwarden đã có bản dịch 40 ngôn ngữ và đang phát triển nhờ vào cộng đồng toàn cầu của chúng tôi. +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -Ứng dụng đa nền tảng -Bảo mật và chia sẻ dữ liệu nhạy cảm trong kho lưu trữ Bitwarden của bạn từ bất kỳ trình duyệt, điện thoại thông minh hoặc hệ điều hành máy tính nào, và hơn thế nữa. +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - Một trình quản lý mật khẩu an toàn và miễn phí cho mọi thiết bị của bạn + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. Đồng bộ hóa và truy cập vào kho lưu trữ của bạn từ nhiều thiết bị diff --git a/apps/browser/store/locales/zh_CN/copy.resx b/apps/browser/store/locales/zh_CN/copy.resx index e424ef743a..94543f8f6f 100644 --- a/apps/browser/store/locales/zh_CN/copy.resx +++ b/apps/browser/store/locales/zh_CN/copy.resx @@ -1,17 +1,17 @@  - @@ -118,40 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – 免费密码管理器 + Bitwarden Password Manager - 安全免费的跨平台密码管理器 + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. 是 8bit Solutions LLC 的母公司。 + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -被 THE VERGE、U.S. NEWS & WORLD REPORT、CNET 等评为最佳的密码管理器。 +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -从任何地方,不限制设备,管理、存储、保护和共享无限的密码。Bitwarden 为每个人提供开源的密码管理解决方案,无论是在家里,在工作中,还是在旅途中。 +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -基于安全要求,为您经常访问的每个网站生成强大、唯一和随机的密码。 +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send 快速传输加密的信息---文件和文本---直接给任何人。 +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden 为公司提供团队和企业计划,因此您可以安全地与同事共享密码。 +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -为何选择 Bitwarden: -世界级的加密技术 -密码受到先进的端到端加密(AES-256 位、盐化标签和 PBKDF2 SHA-256)的保护,为您的数据保持安全和隐密。 +More reasons to choose Bitwarden: -内置密码生成器 -基于安全要求,为您经常访问的每个网站生成强大、唯一和随机的密码。 +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -全球翻译 -Bitwarden 的翻译有 40 种语言,而且还在不断增加,感谢我们的全球社区。 +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -跨平台应用程序 -从任何浏览器、移动设备或桌面操作系统,以及更多的地方,在您的 Bitwarden 密码库中保护和分享敏感数据。 +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - 安全免费的跨平台密码管理器 + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. 从多台设备同步和访问密码库 diff --git a/apps/browser/store/locales/zh_TW/copy.resx b/apps/browser/store/locales/zh_TW/copy.resx index be39fdca06..ab37ed5f7b 100644 --- a/apps/browser/store/locales/zh_TW/copy.resx +++ b/apps/browser/store/locales/zh_TW/copy.resx @@ -1,17 +1,17 @@  - @@ -118,40 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – 免費密碼管理工具 + Bitwarden Password Manager - 安全、免費、跨平台的密碼管理工具 + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. - Bitwarden, Inc. 是 8bit Solutions LLC 的母公司。 + Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! -被 THE VERGE、U.S. NEWS & WORLD REPORT、CNET 等評為最佳的密碼管理器。 +SECURE YOUR DIGITAL LIFE +Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. -從任何地方,不限制設備,管理、存儲、保護和共享無限的密碼。Bitwarden 為每個人提供開源的密碼管理解決方案,無論是在家裡,在工作中,還是在旅途中。 +ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE +Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. -基於安全要求,為您經常訪問的每個網站生成強大、唯一和隨機的密碼。 +EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE +Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. -Bitwarden Send 快速傳輸加密的信息---文檔和文本---直接給任何人。 +EMPOWER YOUR TEAMS WITH BITWARDEN +Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. -Bitwarden 為公司提供團隊和企業計劃,因此您可以安全地與同事共享密碼。 +Use Bitwarden to secure your workforce and share sensitive information with colleagues. -為何選擇 Bitwarden: -世界級的加密技術 -密碼受到先進的端到端加密(AES-256 位、鹽化標籤和 PBKDF2 SHA-256)的保護,為您的資料保持安全和隱密。 +More reasons to choose Bitwarden: -內置密碼生成器 -基於安全要求,為您經常訪問的每個網站生成強大、唯一和隨機的密碼。 +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. -全球翻譯 -Bitwarden 的翻譯有 40 種語言,而且還在不斷增加,感謝我們的全球社區。 +3rd-party Audits +Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. -跨平台應用程式 -從任何瀏覽器、行動裝置或桌面作業系統,以及更多的地方,在您的 Bitwarden 密碼庫中保護和分享敏感資料。 +Advanced 2FA +Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. + +Bitwarden Send +Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + +Built-in Generator +Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. + +Global Translations +Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + +Bitwarden secures more than just passwords +End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! + - 安全、免費、跨平台的密碼管理工具 + At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. 在多部裝置上同步和存取密碼庫 From 14cb4bc5aae3adf91d31423528aa6e164c32c806 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Fri, 19 Apr 2024 14:55:34 -0500 Subject: [PATCH 53/86] [PM-7581] Validate cache state from external contexts within LocalBackedSessionStorage (#8842) * [PM-7581] Validate cache state from external contexts within LocalBackedSessionStorage * [PM-7581] Continuing with exploring refining the LocalBackedSessionStorage * [PM-7558] Fix Vault Load Times * [PM-7558] Committing before reworking LocalBackedSessionStorage to function without extending the MemoryStorageService * [PM-7558] Working through refinement of LocalBackedSessionStorage * [PM-7558] Reverting some changes * [PM-7558] Refining implementation and removing unnecessary params from localBackedSessionStorage * [PM-7558] Fixing logic for getting the local session state * [PM-7558] Adding a method to avoid calling bypass cache when a key is known to be a null value * [PM-7558] Fixing tests in a temporary manner * [PM-7558] Removing unnecessary chagnes that affect mv2 * [PM-7558] Removing unnecessary chagnes that affect mv2 * [PM-7558] Adding partition for LocalBackedSessionStorageService * [PM-7558] Wrapping duplicate cache save early return within isDev call * [PM-7558] Wrapping duplicate cache save early return within isDev call * [PM-7558] Wrapping duplicate cache save early return within isDev call --- .../browser/src/background/main.background.ts | 20 +- apps/browser/src/platform/background.ts | 13 +- .../storage-service.factory.ts | 11 +- .../platform/listeners/on-install-listener.ts | 5 + ...cal-backed-session-storage.service.spec.ts | 109 ++++++++-- .../local-backed-session-storage.service.ts | 196 ++++++++++++++---- .../foreground-memory-storage.service.ts | 8 +- 7 files changed, 277 insertions(+), 85 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index c627c0032b..622a115067 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -226,6 +226,7 @@ import { BackgroundPlatformUtilsService } from "../platform/services/platform-ut import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider"; import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service"; +import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service"; import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; import FilelessImporterBackground from "../tools/background/fileless-importer.background"; @@ -394,13 +395,26 @@ export default class MainBackground { ), ); + this.platformUtilsService = new BackgroundPlatformUtilsService( + this.messagingService, + (clipboardValue, clearMs) => this.clearClipboard(clipboardValue, clearMs), + async () => this.biometricUnlock(), + self, + ); + const mv3MemoryStorageCreator = (partitionName: string) => { + if (this.popupOnlyContext) { + return new ForegroundMemoryStorageService(partitionName); + } + // TODO: Consider using multithreaded encrypt service in popup only context return new LocalBackedSessionStorageService( + this.logService, new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false), this.keyGenerationService, new BrowserLocalStorageService(), new BrowserMemoryStorageService(), + this.platformUtilsService, partitionName, ); }; @@ -469,12 +483,6 @@ export default class MainBackground { this.biometricStateService = new DefaultBiometricStateService(this.stateProvider); this.userNotificationSettingsService = new UserNotificationSettingsService(this.stateProvider); - this.platformUtilsService = new BackgroundPlatformUtilsService( - this.messagingService, - (clipboardValue, clearMs) => this.clearClipboard(clipboardValue, clearMs), - async () => this.biometricUnlock(), - self, - ); this.tokenService = new TokenService( this.singleUserStateProvider, diff --git a/apps/browser/src/platform/background.ts b/apps/browser/src/platform/background.ts index 9c3510178c..a48c420e77 100644 --- a/apps/browser/src/platform/background.ts +++ b/apps/browser/src/platform/background.ts @@ -5,16 +5,11 @@ import MainBackground from "../background/main.background"; import { BrowserApi } from "./browser/browser-api"; const logService = new ConsoleLogService(false); +if (BrowserApi.isManifestVersion(3)) { + startHeartbeat().catch((error) => logService.error(error)); +} const bitwardenMain = ((self as any).bitwardenMain = new MainBackground()); -bitwardenMain - .bootstrap() - .then(() => { - // Finished bootstrapping - if (BrowserApi.isManifestVersion(3)) { - startHeartbeat().catch((error) => logService.error(error)); - } - }) - .catch((error) => logService.error(error)); +bitwardenMain.bootstrap().catch((error) => logService.error(error)); /** * Tracks when a service worker was last alive and extends the service worker diff --git a/apps/browser/src/platform/background/service-factories/storage-service.factory.ts b/apps/browser/src/platform/background/service-factories/storage-service.factory.ts index 19d5a9c140..83e8a780a6 100644 --- a/apps/browser/src/platform/background/service-factories/storage-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/storage-service.factory.ts @@ -17,6 +17,11 @@ import { KeyGenerationServiceInitOptions, keyGenerationServiceFactory, } from "./key-generation-service.factory"; +import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; +import { + platformUtilsServiceFactory, + PlatformUtilsServiceInitOptions, +} from "./platform-utils-service.factory"; export type DiskStorageServiceInitOptions = FactoryOptions; export type SecureStorageServiceInitOptions = FactoryOptions; @@ -25,7 +30,9 @@ export type MemoryStorageServiceInitOptions = FactoryOptions & EncryptServiceInitOptions & KeyGenerationServiceInitOptions & DiskStorageServiceInitOptions & - SessionStorageServiceInitOptions; + SessionStorageServiceInitOptions & + LogServiceInitOptions & + PlatformUtilsServiceInitOptions; export function diskStorageServiceFactory( cache: { diskStorageService?: AbstractStorageService } & CachedServices, @@ -63,10 +70,12 @@ export function memoryStorageServiceFactory( return factory(cache, "memoryStorageService", opts, async () => { if (BrowserApi.isManifestVersion(3)) { return new LocalBackedSessionStorageService( + await logServiceFactory(cache, opts), await encryptServiceFactory(cache, opts), await keyGenerationServiceFactory(cache, opts), await diskStorageServiceFactory(cache, opts), await sessionStorageServiceFactory(cache, opts), + await platformUtilsServiceFactory(cache, opts), "serviceFactories", ); } diff --git a/apps/browser/src/platform/listeners/on-install-listener.ts b/apps/browser/src/platform/listeners/on-install-listener.ts index ef206301e3..adf575a17a 100644 --- a/apps/browser/src/platform/listeners/on-install-listener.ts +++ b/apps/browser/src/platform/listeners/on-install-listener.ts @@ -23,6 +23,11 @@ export async function onInstallListener(details: chrome.runtime.InstalledDetails stateServiceOptions: { stateFactory: new StateFactory(GlobalState, Account), }, + platformUtilsServiceOptions: { + win: self, + biometricCallback: async () => false, + clipboardWriteCallback: async () => {}, + }, }; const environmentService = await environmentServiceFactory(cache, opts); diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts index 7740a22071..a4581e6ac1 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts @@ -2,6 +2,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { AbstractMemoryStorageService, AbstractStorageService, @@ -11,16 +13,26 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { BrowserApi } from "../browser/browser-api"; + import { LocalBackedSessionStorageService } from "./local-backed-session-storage.service"; -describe("LocalBackedSessionStorage", () => { +describe.skip("LocalBackedSessionStorage", () => { + const sendMessageWithResponseSpy: jest.SpyInstance = jest.spyOn( + BrowserApi, + "sendMessageWithResponse", + ); + let encryptService: MockProxy; let keyGenerationService: MockProxy; let localStorageService: MockProxy; let sessionStorageService: MockProxy; + let logService: MockProxy; + let platformUtilsService: MockProxy; - let cache: Map; + let cache: Record; const testObj = { a: 1, b: 2 }; + const stringifiedTestObj = JSON.stringify(testObj); const key = new SymmetricCryptoKey(Utils.fromUtf8ToArray("00000000000000000000000000000000")); let getSessionKeySpy: jest.SpyInstance; @@ -40,20 +52,24 @@ describe("LocalBackedSessionStorage", () => { }; beforeEach(() => { + sendMessageWithResponseSpy.mockResolvedValue(null); + logService = mock(); encryptService = mock(); keyGenerationService = mock(); localStorageService = mock(); sessionStorageService = mock(); sut = new LocalBackedSessionStorageService( + logService, encryptService, keyGenerationService, localStorageService, sessionStorageService, + platformUtilsService, "test", ); - cache = sut["cache"]; + cache = sut["cachedSession"]; keyGenerationService.createKeyWithPurpose.mockResolvedValue({ derivedKey: key, @@ -64,19 +80,27 @@ describe("LocalBackedSessionStorage", () => { getSessionKeySpy = jest.spyOn(sut, "getSessionEncKey"); getSessionKeySpy.mockResolvedValue(key); - sendUpdateSpy = jest.spyOn(sut, "sendUpdate"); - sendUpdateSpy.mockReturnValue(); + // sendUpdateSpy = jest.spyOn(sut, "sendUpdate"); + // sendUpdateSpy.mockReturnValue(); }); describe("get", () => { - it("should return from cache", async () => { - cache.set("test", testObj); - const result = await sut.get("test"); - expect(result).toStrictEqual(testObj); + describe("in local cache or external context cache", () => { + it("should return from local cache", async () => { + cache["test"] = stringifiedTestObj; + const result = await sut.get("test"); + expect(result).toStrictEqual(testObj); + }); + + it("should return from external context cache when local cache is not available", async () => { + sendMessageWithResponseSpy.mockResolvedValue(stringifiedTestObj); + const result = await sut.get("test"); + expect(result).toStrictEqual(testObj); + }); }); describe("not in cache", () => { - const session = { test: testObj }; + const session = { test: stringifiedTestObj }; beforeEach(() => { mockExistingSessionKey(key); @@ -117,8 +141,8 @@ describe("LocalBackedSessionStorage", () => { it("should set retrieved values in cache", async () => { await sut.get("test"); - expect(cache.has("test")).toBe(true); - expect(cache.get("test")).toEqual(session.test); + expect(cache["test"]).toBeTruthy(); + expect(cache["test"]).toEqual(session.test); }); it("should use a deserializer if provided", async () => { @@ -148,13 +172,56 @@ describe("LocalBackedSessionStorage", () => { }); describe("remove", () => { + describe("existing cache value is null", () => { + it("should not save null if the local cached value is already null", async () => { + cache["test"] = null; + await sut.remove("test"); + expect(sendUpdateSpy).not.toHaveBeenCalled(); + }); + + it("should not save null if the externally cached value is already null", async () => { + sendMessageWithResponseSpy.mockResolvedValue(null); + await sut.remove("test"); + expect(sendUpdateSpy).not.toHaveBeenCalled(); + }); + }); + it("should save null", async () => { + cache["test"] = stringifiedTestObj; + await sut.remove("test"); expect(sendUpdateSpy).toHaveBeenCalledWith({ key: "test", updateType: "remove" }); }); }); describe("save", () => { + describe("currently cached", () => { + it("does not save the value a local cached value exists which is an exact match", async () => { + cache["test"] = stringifiedTestObj; + await sut.save("test", testObj); + expect(sendUpdateSpy).not.toHaveBeenCalled(); + }); + + it("does not save the value if a local cached value exists, even if the keys not in the same order", async () => { + cache["test"] = JSON.stringify({ b: 2, a: 1 }); + await sut.save("test", testObj); + expect(sendUpdateSpy).not.toHaveBeenCalled(); + }); + + it("does not save the value a externally cached value exists which is an exact match", async () => { + sendMessageWithResponseSpy.mockResolvedValue(stringifiedTestObj); + await sut.save("test", testObj); + expect(sendUpdateSpy).not.toHaveBeenCalled(); + expect(cache["test"]).toBe(stringifiedTestObj); + }); + + it("saves the value if the currently cached string value evaluates to a falsy value", async () => { + cache["test"] = "null"; + await sut.save("test", testObj); + expect(sendUpdateSpy).toHaveBeenCalledWith({ key: "test", updateType: "save" }); + }); + }); + describe("caching", () => { beforeEach(() => { localStorageService.get.mockResolvedValue(null); @@ -167,21 +234,21 @@ describe("LocalBackedSessionStorage", () => { }); it("should remove key from cache if value is null", async () => { - cache.set("test", {}); - const cacheSetSpy = jest.spyOn(cache, "set"); - expect(cache.has("test")).toBe(true); + cache["test"] = {}; + // const cacheSetSpy = jest.spyOn(cache, "set"); + expect(cache["test"]).toBe(true); await sut.save("test", null); // Don't remove from cache, just replace with null - expect(cache.get("test")).toBe(null); - expect(cacheSetSpy).toHaveBeenCalledWith("test", null); + expect(cache["test"]).toBe(null); + // expect(cacheSetSpy).toHaveBeenCalledWith("test", null); }); it("should set cache if value is non-null", async () => { - expect(cache.has("test")).toBe(false); - const setSpy = jest.spyOn(cache, "set"); + expect(cache["test"]).toBe(false); + // const setSpy = jest.spyOn(cache, "set"); await sut.save("test", testObj); - expect(cache.get("test")).toBe(testObj); - expect(setSpy).toHaveBeenCalledWith("test", testObj); + expect(cache["test"]).toBe(stringifiedTestObj); + // expect(setSpy).toHaveBeenCalledWith("test", stringifiedTestObj); }); }); diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.ts index 3f01e4169e..146eb11b2b 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.ts @@ -1,8 +1,10 @@ -import { Observable, Subject, filter, map, merge, share, tap } from "rxjs"; +import { Subject } from "rxjs"; import { Jsonify } from "type-fest"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { AbstractMemoryStorageService, AbstractStorageService, @@ -13,57 +15,77 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { MemoryStorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { fromChromeEvent } from "../browser/from-chrome-event"; +import { BrowserApi } from "../browser/browser-api"; import { devFlag } from "../decorators/dev-flag.decorator"; import { devFlagEnabled } from "../flags"; +import { MemoryStoragePortMessage } from "../storage/port-messages"; +import { portName } from "../storage/port-name"; export class LocalBackedSessionStorageService extends AbstractMemoryStorageService implements ObservableStorageService { - private cache = new Map(); private updatesSubject = new Subject(); - - private commandName = `localBackedSessionStorage_${this.name}`; - private encKey = `localEncryptionKey_${this.name}`; - private sessionKey = `session_${this.name}`; - - updates$: Observable; + private commandName = `localBackedSessionStorage_${this.partitionName}`; + private encKey = `localEncryptionKey_${this.partitionName}`; + private sessionKey = `session_${this.partitionName}`; + private cachedSession: Record = {}; + private _ports: Set = new Set([]); + private knownNullishCacheKeys: Set = new Set([]); constructor( + private logService: LogService, private encryptService: EncryptService, private keyGenerationService: KeyGenerationService, private localStorage: AbstractStorageService, private sessionStorage: AbstractStorageService, - private name: string, + private platformUtilsService: PlatformUtilsService, + private partitionName: string, ) { super(); - const remoteObservable = fromChromeEvent(chrome.runtime.onMessage).pipe( - filter(([msg]) => msg.command === this.commandName), - map(([msg]) => msg.update as StorageUpdate), - tap((update) => { - if (update.updateType === "remove") { - this.cache.set(update.key, null); - } else { - this.cache.delete(update.key); - } - }), - share(), - ); + BrowserApi.addListener(chrome.runtime.onConnect, (port) => { + if (port.name !== `${portName(chrome.storage.session)}_${partitionName}`) { + return; + } - remoteObservable.subscribe(); + this._ports.add(port); - this.updates$ = merge(this.updatesSubject.asObservable(), remoteObservable); + const listenerCallback = this.onMessageFromForeground.bind(this); + port.onDisconnect.addListener(() => { + this._ports.delete(port); + port.onMessage.removeListener(listenerCallback); + }); + port.onMessage.addListener(listenerCallback); + // Initialize the new memory storage service with existing data + this.sendMessageTo(port, { + action: "initialization", + data: Array.from(Object.keys(this.cachedSession)), + }); + }); + this.updates$.subscribe((update) => { + this.broadcastMessage({ + action: "subject_update", + data: update, + }); + }); } get valuesRequireDeserialization(): boolean { return true; } + get updates$() { + return this.updatesSubject.asObservable(); + } + async get(key: string, options?: MemoryStorageOptions): Promise { - if (this.cache.has(key)) { - return this.cache.get(key) as T; + if (this.cachedSession[key] != null) { + return this.cachedSession[key] as T; + } + + if (this.knownNullishCacheKeys.has(key)) { + return null; } return await this.getBypassCache(key, options); @@ -71,7 +93,8 @@ export class LocalBackedSessionStorageService async getBypassCache(key: string, options?: MemoryStorageOptions): Promise { const session = await this.getLocalSession(await this.getSessionEncKey()); - if (session == null || !Object.keys(session).includes(key)) { + if (session[key] == null) { + this.knownNullishCacheKeys.add(key); return null; } @@ -80,8 +103,8 @@ export class LocalBackedSessionStorageService value = options.deserializer(value as Jsonify); } - this.cache.set(key, value); - return this.cache.get(key) as T; + void this.save(key, value); + return value as T; } async has(key: string): Promise { @@ -89,41 +112,48 @@ export class LocalBackedSessionStorageService } async save(key: string, obj: T): Promise { + // This is for observation purposes only. At some point, we don't want to write to local session storage if the value is the same. + if (this.platformUtilsService.isDev()) { + const existingValue = this.cachedSession[key] as T; + if (this.compareValues(existingValue, obj)) { + this.logService.warning(`Possible unnecessary write to local session storage. Key: ${key}`); + this.logService.warning(obj as any); + } + } + if (obj == null) { return await this.remove(key); } - this.cache.set(key, obj); + this.knownNullishCacheKeys.delete(key); + this.cachedSession[key] = obj; await this.updateLocalSessionValue(key, obj); - this.sendUpdate({ key, updateType: "save" }); + this.updatesSubject.next({ key, updateType: "save" }); } async remove(key: string): Promise { - this.cache.set(key, null); + this.knownNullishCacheKeys.add(key); + delete this.cachedSession[key]; await this.updateLocalSessionValue(key, null); - this.sendUpdate({ key, updateType: "remove" }); - } - - sendUpdate(storageUpdate: StorageUpdate) { - this.updatesSubject.next(storageUpdate); - void chrome.runtime.sendMessage({ - command: this.commandName, - update: storageUpdate, - }); + this.updatesSubject.next({ key, updateType: "remove" }); } private async updateLocalSessionValue(key: string, obj: T) { const sessionEncKey = await this.getSessionEncKey(); const localSession = (await this.getLocalSession(sessionEncKey)) ?? {}; localSession[key] = obj; - await this.setLocalSession(localSession, sessionEncKey); + void this.setLocalSession(localSession, sessionEncKey); } async getLocalSession(encKey: SymmetricCryptoKey): Promise> { - const local = await this.localStorage.get(this.sessionKey); + if (Object.keys(this.cachedSession).length > 0) { + return this.cachedSession; + } + this.cachedSession = {}; + const local = await this.localStorage.get(this.sessionKey); if (local == null) { - return null; + return this.cachedSession; } if (devFlagEnabled("storeSessionDecrypted")) { @@ -135,9 +165,11 @@ export class LocalBackedSessionStorageService // Error with decryption -- session is lost, delete state and key and start over await this.setSessionEncKey(null); await this.localStorage.remove(this.sessionKey); - return null; + return this.cachedSession; } - return JSON.parse(sessionJson); + + this.cachedSession = JSON.parse(sessionJson); + return this.cachedSession; } async setLocalSession(session: Record, key: SymmetricCryptoKey) { @@ -192,4 +224,76 @@ export class LocalBackedSessionStorageService await this.sessionStorage.save(this.encKey, input); } } + + private compareValues(value1: T, value2: T): boolean { + if (value1 == null && value2 == null) { + return true; + } + + if (value1 && value2 == null) { + return false; + } + + if (value1 == null && value2) { + return false; + } + + if (typeof value1 !== "object" || typeof value2 !== "object") { + return value1 === value2; + } + + if (JSON.stringify(value1) === JSON.stringify(value2)) { + return true; + } + + return Object.entries(value1).sort().toString() === Object.entries(value2).sort().toString(); + } + + private async onMessageFromForeground( + message: MemoryStoragePortMessage, + port: chrome.runtime.Port, + ) { + if (message.originator === "background") { + return; + } + + let result: unknown = null; + + switch (message.action) { + case "get": + case "getBypassCache": + case "has": { + result = await this[message.action](message.key); + break; + } + case "save": + await this.save(message.key, JSON.parse((message.data as string) ?? null) as unknown); + break; + case "remove": + await this.remove(message.key); + break; + } + + this.sendMessageTo(port, { + id: message.id, + key: message.key, + data: JSON.stringify(result), + }); + } + + protected broadcastMessage(data: Omit) { + this._ports.forEach((port) => { + this.sendMessageTo(port, data); + }); + } + + private sendMessageTo( + port: chrome.runtime.Port, + data: Omit, + ) { + port.postMessage({ + ...data, + originator: "background", + }); + } } diff --git a/apps/browser/src/platform/storage/foreground-memory-storage.service.ts b/apps/browser/src/platform/storage/foreground-memory-storage.service.ts index 1e5220002a..b3ac8de55e 100644 --- a/apps/browser/src/platform/storage/foreground-memory-storage.service.ts +++ b/apps/browser/src/platform/storage/foreground-memory-storage.service.ts @@ -21,12 +21,16 @@ export class ForegroundMemoryStorageService extends AbstractMemoryStorageService } updates$; - constructor() { + constructor(private partitionName?: string) { super(); this.updates$ = this.updatesSubject.asObservable(); - this._port = chrome.runtime.connect({ name: portName(chrome.storage.session) }); + let name = portName(chrome.storage.session); + if (this.partitionName) { + name = `${name}_${this.partitionName}`; + } + this._port = chrome.runtime.connect({ name }); this._backgroundResponses$ = fromChromeEvent(this._port.onMessage).pipe( map(([message]) => message), filter((message) => message.originator === "background"), From 36ea3301ec6d4292830a9bbeddbe606290a732ae Mon Sep 17 00:00:00 2001 From: Victoria League Date: Fri, 19 Apr 2024 16:11:12 -0400 Subject: [PATCH 54/86] [CL-218] Add new icons for extension refresh (#8805) --- .../src/scss/bwicons/fonts/bwi-font.svg | 21 +++++++++++++----- .../src/scss/bwicons/fonts/bwi-font.ttf | Bin 76108 -> 79272 bytes .../src/scss/bwicons/fonts/bwi-font.woff | Bin 76184 -> 79348 bytes .../src/scss/bwicons/fonts/bwi-font.woff2 | Bin 33420 -> 34628 bytes .../src/scss/bwicons/styles/style.scss | 11 +++++++++ libs/components/src/stories/icons.mdx | 11 +++++++++ 6 files changed, 38 insertions(+), 5 deletions(-) diff --git a/libs/angular/src/scss/bwicons/fonts/bwi-font.svg b/libs/angular/src/scss/bwicons/fonts/bwi-font.svg index e815389126..bc0a348fee 100644 --- a/libs/angular/src/scss/bwicons/fonts/bwi-font.svg +++ b/libs/angular/src/scss/bwicons/fonts/bwi-font.svg @@ -5,11 +5,11 @@ - + @@ -154,7 +154,7 @@ - + @@ -187,6 +187,17 @@ + + + + + + + + + + + diff --git a/libs/angular/src/scss/bwicons/fonts/bwi-font.ttf b/libs/angular/src/scss/bwicons/fonts/bwi-font.ttf index f9b63283e0465ea0285ac022a2aa1540746d3d60..f70eea7af77f4f057b184abb7f6f85337734d3d6 100644 GIT binary patch delta 7209 zcmb_B3wTpin&-dIjPJ2be!V2qg}1zGKx6927KVEIOw_#pQ|(Y-1UV_lkR`+O?0zN8Pn6HZGfg$TADzJ_5y4JFjV9n*U?=&j^%I`if3m;12N~gug{NzjNif z4KK|fd4|B`EJB!LD^@R=(Vle7eV0J#$H=5}W&4J1@)Bzy5SxnxxvSb&UX$NcmO^+1 z5_r2;uU&_VB5L_|x=&b+z4P$xa4<0!vf)Ug6TG#fJ%q45gdwD|nx2gw@PgKm?`Y4^ z5!eYVP7?Fj7wI_U;l5+kiA~%M&@*&E_gfWVs6*NU?$I8ON^_GKxs+T^R+5e6RyB2GmxY3Pg43bc&zZ-{6E_*J;JZfp*uF%Q*%co-VKiF| z2D904-pB|X$4NXdaV&?AI^(#%%S^`?S?)o^)C;_XYltfGf*wc4oLtB}GRI($yf`y^ zB?EqN&OD;dQ>W~*Y_kPr*`7LL!#4#Vu$gd8Hrc32(Id2j5ABm*VubX(!*Ufh(6iQd zUdDa)Fx!V$G?%PSG#+#f&2;4fuUc!UE9)-Jgp8+0vu&?;G_$$MA7_xu+1%e~TTcZ+ zQ0@Or(Vw-IhbKJ=A{Fp8+cVTr;-W$~O@3ZuSzCG;g?B}-Q@NC#pu=00vPO|aichMV z$5gJL@;1ZK!z4Vl3lMQl?ei)kz0{$ax=)c+BCq~&RsOs6FHp|+Tyz_&!l8yQG?B{e zKWdY+bEZ&f6^+MrDu%RRCZ%XxsPd^;^h5gm(W}1fxK z{ekUCY+G`l|hmPqbRfH5I z{%xr@@#FICGPQpnS&UwK7a@tQ4Xs0;cTJtsnOPsemee!4Tyei{G z{zMC?Udm3SR@~c$ik{lLQa#8#^geace0PxE*QR@{+DUian^f0#-M3iN=F9urwaL5( zC}yJf!FN=p9zvz&9XKVXwMoZxcKK*A+SjRJ;W)K?>&C<$mc%{Opxy(<&@`^_SybrW~D*%LcU6s-tJLc<4Gt&;)_-j{D)sqNL`-t4z-(VPweGK1Vi1?If%9QyZvTCO4OL!^#( zG(TW>p(M`<&6X!{I2gx<%*N&S}svpy&u2R8k4nx5`N|Ap7=@C*XAe@vr2 zhrpUsH-eggeuKJ%$SiVeBGNE(X!^%h8mq?~EzbwSPY3&?%s03oit&}3lCDk^Is7N}5iKs15N52cE_5>=cg2%w=Di*D%R13&B z!@Q6f$fE+B_JW9`bShjEC^6_6hs_@G!11Y3Nw2r_GOH5{jY_erz7^W)JQ2l^$Ll%0 zRnGt?FoNV-H@{$lD3}GExhh;(ZPMvYf|wuIPq^3zK~uvl$(XSG}*207M+yk2v=2w&2Gu)v)S@W?3X#WoSaRLvQmNb@~43%S!;z2w8ISW|0Nj z7ii^v@Qdd%v9{`~7|nzSzFw*M)#vC8$~OVcF=^qb{eGGjpj&=;O>+^tI`7A{haX0N zIdb+#)pb9@jU#_~O-nO-M)%M}M>FT}1ONl8h2TI92S=f2Sm5DGP2y*4&p?L=UupZp z%{n-dj{f1O0ph6L@DoO)!EuWTHmb@DoHYX`w}C|#7{+y*6)qjqZFre2!y1(vKH>l| zec0jGoj{AvaE%MrkEN4=r`*t!$t?pV9$2pedr_@H4?Ls-ClP4QhQ%uI2LztUhO3Z~ z;)Nbnq2YcXyasU^z+2g^q=j^nb=Y3sPkxCxL2Y_mVhIL_AQqkz2Gplr=)M>$+Ahv; z4a20cQuBCiS6dsWPVdri;`2gdv15;)0|eA?!jQ!6LiJ4}H;RE<1x^gIh=OknxFDQE z#O8*pIBuuEsU&|!_DVsl3WqDJ!g{N2wab(1tI=5{>XYFrL73vM&UF<#nnlDZtgbH9 zTXdHf#EUJaJ0+KqE+nU`EtK6JOONnPdh*T8*4dRhi!M~BaIC}zqNroR9wAVqkIm}#6= zyqTszxJya2WR}!rDQ^ z_2>twIe;WpOh$dz)urf{j7%nBk(S8BM~YFsvWoZ=)DeAZBKlMzbwDes8$cDUWflCG z0n_F}VX9^DRR^T0ySr(Wz5$2wIdjT^zBAg}f7RWT?4vjgzU$_cC;KQ4gKw%1MqLUO zF~$it>Us$3A)tpCzA^M;5CrCGxN%YH$<&$Do3K08vj`T03nHnG8&doFnJu@czJNF2 z@zi$MkbF5+13Ob|nZ}*)myw%b8$GGkQDM~MC0tAHC2s%=GBOCMSK^8|Vi->96L%?& zba_&X7md5TvLJ?Y1g|MniPf_r>Ovd2q7fQ{+JiKBn;HSWBcUK-M^qZ5r}Z!D;xTrs zAXbxkwBViq!LcWj7mUR7H0pHS#GyP=hIO%Adr&;mDGsL>w5QY|l;S+kFf+3K0_SoFaJ776dkYR1R&*$YP+27)HCB2M zLWQi6hB~keXB136gCGc2!QeBA29CvL?ZW^s@H$Anf`u7?`zutxEE;%*#r5F{_0~MWuD1#-uc2bO1`egNAiz@$81*_MWx%kkLEw3~4L7M)yg`&v zD=W4xsFh&MXr(dX@#`kbJX||TmgS&a>`RWwva!I(@U_V|b8?wRe{)U_& z_&FRUC{dGZ&gI>aQJ=!D~qk z;*TCav$~KP)R{%m$O}4jDtvUjU=&5O4w}bt;JDFX@S4rp27{a$FnbLKqq+dgF+_9% zFJtR0>P(_2Q#KgO@%C|~)C1{`Q!Qj3GLy6sV#iiLT^lB<4M@5`Oo4LM1=LCwt(b-t zS8Z+5t$KbY}U)E z-7`L9R`X*4MK&QkXQjqO%#8#JkF|ZPr3){%?^2;JRfw= z$O>X2ewh=4Su@;0fpdC8*(1-fA%W}QM2lgD(LC~;1A2wLb zmt0K1QRd5V)0FSzcu5ox+nG(DIW2;$IU0uXZ z2xTX)a|L-2ILRFhx-l-WF4399#RcGlF6OT6(8!1CGJdTy=yVzioiq2_+%}WX(eCi# zkE&irGmbX5+XhL4$fa0LL^~xpF|`v6$fhl?^AiSsiIYj$!V@KZ!d5~uJ;QVfz) zk2GFR7I+TV7}la74Og9dza~si9j>ANsb=)yMLCfED)>Bp&v`unoofHXT!~O^9(3i# zi(3ZT%u~N32JBJsbE=`MBQuV?I@&YvN*=`GL)xZ_p>~WErhk0;FEh`q|NoA0P1yNN zsqpjHE(9K@8y&q6eEbhXM$l;Lf(XX&Gj#vAF+MKz0yX|C1xCNq=to{1=kq^ODE_}P z^Mwjy_l_&W{^R(3oc_PK|8a!>jOf2#Ljw^a5eM;;Phd9l61$6A#g_=2*roGG#|*2C zQ{`S$ulZ@K;>dEwcPU-U8Q00IZSJSD^RplIp7b~P*X16~Jk zvSaGrX{&1YOy5$stZrn+!kNGR$-}cwHFz3cxn$ENk6!ZeoMm%2T*|~R-PRaw{P?o( zn>wy&zT&;+Cl*8&j4XU_QNvZeEh`pZ-MXgr)YV-3$qr^o$&$@WK41E~rC(puea(H# zwqM(N?Gv4?%k!6??t1CEvK7ZxwygYq)my9I>t54S)^ldfp|xAqb*&p*zqfbY^_C3{ z8$Z~z<%VUO{hRl1*&d(QcXDg}wpH8ybaV49muB6Py7kblpZ~Psr(fUZzwPJ!Ew|r& z``bHgJ34pVv*Y(WZ{D?Hchl}Wc7Jq7{vB7``N5v0dv@&kHnCyvnSGIco%{CF&W8-K zCi-;_Kg>XB5!kbB@!%IymH>w(7q82lo&)|9 l@7DwOAgbJ-UXn-|xp-h}7>WmCVYp%7jWA>lJQjxi{{cEen>qjh delta 4106 zcmahMYgkle`g_lJ&YZblxC}55a#2WBlzU){gO({ND2Wg#f+!*Z0wR*D%!sDRDxt@m zt#w`Uc~Z;Pee9HM)GFG>W|kY#f0I{4-uHKADtA`RkSvc*Qu32+_z{DRbNrJGPWz#OCTPDauE${);43Js5)^n z8z+?KclZ4Qx}|v#4n0yeL}a6!{WwB0GFfeEfeimY86`1Fn|ua#0ZmZar2_gEbt{`? z2Y;PI!Yv1gPXV~)gW5lU$}iWqK`4ph2{%j}B#kT}i%28cM0Sv;36Uy_!Xa2%INdS! z76NV2wh2<>*>Fd!ker^D8W$U*2@bLc-bH6fZsQ`2u;!+lx}|xRP}j{XMvKL0uviTL zgW(#byp|WVh3 zbE@<;i@{*E8e$92Df?Bw|$JEbhd~}($lr^Z=Rv}O)HY_q^76^Sj zp-#Cf^l6~wDD#Fjaw^djq4)<|B3&HXeINB2N25{aL?bDu-*Vk@Sz-ta+A{U@*sNqFP3dmXP7{wN;kbl%jL`W zEyk45Dd(26r(fWB&?!44dv*~j)@PpvkylP*#2IqB^9yt(Mb11<0T;r+%r}|meGlxQ zrVdQXjY-9kJn|AIRUo&`*$q^$bjSg@pECmMd3ZXDot*cfR^>HCvjE!V++x*ha>)?K+NDpz?wGB%o1946K<5%v|y|d_sp& zFw`jHbW(-RkoK(If;RSMtkx}+tot`Ii(BSmTS*kX!3|4?*8hN_1Fa^GcBAF74aaeA zFfH8Ja@EG1v9e!m`v`N5Vw+3e=02+ilMC8?QQNk>DgsWYg43C`^(c@2MxGp{tT^W7 zqn%B+7iHd4SSXv0-rc4C1?P~KfEI{yh)$x9N^N_WVWOYzty33m9xhasDEMV82c2@u zvo@eXN{7_(t6Qp{j{QqPO?>p%6@UxFE$96PHQiFvbAw{3(nX)A4RYSW%c6!1WBO52 z`iowFC0R#*t2&oPkFmCl{LTZyWlT9zZamC7dG|*iVsLL%ohyQF`Q*7l5KT%S z{TI!UpFRIPur^6EKRAruM?WY8^BHA?W>W{rAzKIul%5(0mX{B#2GJ*%2mbs7h&ALn zx{q5Xmkr{yH!0U>CpAmss6c7`XqpDiV zU(tS&YBNtx{7m4)hcJ9EjgzxJN42@i`}7pelIt&T0#m+nn!ZNUaktJU+i=dBBTYTre|aWJ|U41V%Lafz=zT5Gd6^8 zyba=2113qgk7+U;WK*&sCE4z?@w*T;%soD?By_3qS;XQ(Wtu6;DOv25oSuP>6Ip!b z^}l0|+2b4UAq$w9v-mhQ+eDV6=ua{@$d~r*m_D_nY=}>F#l^>IL~dy?b%d|X&8=`G z1qKEI<*0@at63BlEl@E;YvOs1<8-1JV9w{?R5#9*=QVRtIW+^ z;RvSznOcBA(I|uk7-ECPAiV+O1?zQYf#(4mCEE1*5JTYj01%^tLxXsoRZ9iX=NFp7 zjCc=F4GnYXw7L+BCE6AcW3@&Y^k%#+HA0|CCxFRRGm}D4U_eqh_kf`11(CXfd7BVs zGsXsK0}X}{gCSU_H}e7uCTcL4-H5?>&^m$wLUn=hX7iMBQTYk>K=et&!gN|~sM#M% zZ%{*NFcc3)^UQpEphFv^e}7`Z|T(0f)Y+prfpr#}!xU#&d@LBKBIo@_g;<3x^ZqF!!` zr#BQDZ!^hZuuiO1o}qi`D$lVncwUSrZaTyr^^`bZsTfBJ=_j1qbJ78&(AVP#heq|B z?CFev_kfy|9X&0R09!c&CehO{=oy^?)mk{JoZ<>;j;ACMYNB4;ow0tI<^Jo$z!UZx z-t#mbEo1oRS*6W$B@t3wN5%%(fcnptARmK}AQ0kaz{dTU(RYEj{ z=##sB*snv!_yo}T26!I^hB06VV&;GWGtzsUQz6{NSY|SKCnrTgz7XN=7!}Az`Q*ch z$sJIPm>nhEh+-<`l&!83|9w82a*)tth z&9jKW9Q#TyesgNh>B7PrfjvD{=PJ#A&(HZYJHE zRGf4yxhmyAYFuhtT0wee#=RLQ@0*;d$;{2XnpHF1I=%D$eLri+-aEtXtaf@`IVH1R ze(=cbD{~^|yfb&>++%Y`9bTWcYu2EI z7uF0lG&iO<-e@|x*4w9~$`U}x9PkzJv?=5%tMqg^pwC0$+EQuf7qgqleJ z>o4IvyVD@hvnB=1CP^J*8u<0)=}3WxCWQJ#di?%;5YI#4b!Zex4*F=oPj#*5dsH>W Y`$T$@N|t#V(je56n+iuf`ZPH3AAw-ljsO4v diff --git a/libs/angular/src/scss/bwicons/fonts/bwi-font.woff b/libs/angular/src/scss/bwicons/fonts/bwi-font.woff index 1e57b1aab32302c3438d584d5f9056158fb1c5b9..52cecc3ead80bbe917f34b4e12d74f4d9583444f 100644 GIT binary patch delta 6946 zcmb6;3wTpymjAiWrOAFLe9=1FxC_@S= zBbA2`Gz>a*WLI?1Va4uH$7K{z83jdgeGR(mu&$2iAiFA}HtC%I-n1}$`|Zy5yZ3*b z|D5xm|NQ55(_R0@efuS++;(kU9RWfJlzvMLYX3Na`1*WMwvV$#*Uewr+J%c9xH6Z% zXU2W0yQOsng}sDxUFx;mvSdv=!d@d#Fp0i14q56t7Pc-RFrMO+(KqLZ%zx}aAi}C~ zom5bx?(x^gwb77)U`M&F||hMrw=>wLs(h=jGxp(A0R{_fJ&)m;RNcOhZ6`W|I1 z%UYK%MA$P(=%#N@V@a~>))g!9KyQ!Yu{dHmHfDr-qfdNsZa6VKj1c7V1^MEvI)mZ= zaB|8K!m7$p7u5y`Ag-B0hr5wUH(>}VucR9LyJ5Kda5wuAyMa`Z<^hGd8(0Ly`q`Ih zKhVy7$Ebs5aT`GQz**hzRfK{4(mF^V?&j#O8Kjt8L#`uB$r`ee>>wm|V5SSi*dr4& zw3fG7eb(IAb}`chk+RZ~V8Aap?27G@adBzv4dV^b&Y@nmcc@o(>h+SO*E?nQ;&|vi zquF9Gn9YXEdPd+lPU3lqV>x`(5y$;%WOxzCa*rUUUf?B!A*#d+dh8iMxbEgA4na+{gQ)p7E)O_tNrWZ9NHLR&g}IL$;srZlQ-v?5Iv1E=MqjF1}dx6Gj` zlhRswRfWr(wZN+y8#tJLzoy0pSGT6$Bktjm#iQORgLHMi-)T!s4hSj(VaykdtnJi8 z^2oJhXKdcg{DItp4Im@RfEO^=R$LEE6O=vq2I6h*!f9;Eu^JiCVVpXAf*&Y%k z4dh7_>iU5b_1iT8+OGbSHmI9Ek?y^r;iOKbZ<<+4>37dkxo(*KeM&d$=B%Lb|DL;; zcFoOi(5`S^siutfwpLB_@7eCyiTT&52S@2*!38%NsBX}HNG2(dy*WNT*09(gduhQU zSu;})sUxjqt-4axDGuDUuomcD0iJD7`y!3MX^{*js?1(AY&WSOBsbRh3wvyG=Y1%~ ziO$EUG(AgJY1n6%6={d_cjc>tSVQ+8)zghxK7jYh4x?nJSKNs-?JMbVWBXS9gp<8J zS81pC5!cV(_C12mu9h{P$=eTH)cL`+Q#I(1>)us`r5EvT@2lFbdp4wYJ#pu6RYl-- z1ATWr!6Co0O=s8QNM`f6r+#B~5JOzT}8t zeNX&GWgeUG&r~gCPsK+>dg}nypK5n~1E*B!={!@~GLz%3g^h{KP9Cr*?e$)n8vUvbN(h z5NtrjJbV0t<{SrKqt0P^{T&VKTjYJ_#G{&8cD(r*u1z?3qh>}`Xhu`XorKt8AHU@o zc>1kH8V$k+{BPf_O&-9>{&%!%XSd=`?eA%-dk+_C-p^;%Bhbn64=|)s^Q4m<9~NAk z9R2qcJzZJ%F|Qr_S)BC!DTVetPM$mcGobFu{tg8TqDO2*cdd>6>N;Uy`==3&-2ucq z_Mhv)Lh%Mi@bC!nLIyGhR(?K5!+R0&-u?4QZ4ySY_n)~fswNaQEzBejkv)Vs{lW3T zmXwuOL9wq)$pX;?PA?QGDkP#fOc3w~^9n%>3!4hg}72yj64wC3r=JiEupONL8|gH%Bnb0R<358iwyli(dVZdIBdCLd(k9V=qohn z8GD*7?1Gb%BA9t>yv*vvJfl+JtZ9apYFAh>_<232x9S<-1V)gYD`(`65e2iLGgpN2 zDor}QNf2|Q`Y~5}AYiJmlkCkkHO*!u;cI4?8Tu#L44fmwUGB2_jd>MN5z_fgnUlS? zbU|h;I)^zKk~1W|#4vNKjYb{Q?+T}-*}-73DEVzu(j+EbXU=G@xngd*ET)0U8EMG{ zp3!GGIIG@fHH#*jHDj($O1Fn9DnjNA$>>S5;VJY`!jL+4-*fPvMVGQN?6MxMozg!Kc$kSfg-*CI^VA!w$L>pxJ0}qZ3w*#*;YX zg6@&v62ILAtJKL(6zjYT9#to&aIz;8ZdNCM#EIaBImoEZ4c)3hgYSCab%;_=ThFe? zI<|wXB)#O9)NaG;Zg6>QPr0&octn;oj)xGmns!knp@O9ina z6e_O>>8-k3ovv)p1f5l)HW{iAgozoI+0FuclZZHZm6dsVi|)GIXo1DFU2+;JATdR) zuxz&CR1e>z2H(^+UtO-V=z`S>#|kV+QcEqYPc5?56(Px{U+wWb?7xsQWMqZ=vFvWo zm7#)$tm!OE_pb=a=~AQFqSvoCWSHo7h{LRjdS{npRiv-w1qzsg1s*N(Y>DcT(MbRM zmf8ukjKKIZIlw{_z#6pIw!n)c>zbda;m=2MNj_d8MjD3|ugB-ms@`IsdQoGcln$&V zK?syai*tQeAA5MH*A)!9pgNoG57o5IhH8W*+k1Nf3uCsIIUIB)i7PlNT6NIH;5<=X zU5({GQ8Sb(KqeVW#$#ztAfk8xFU%jD45HxA3zkfwAs6p=A|8N>7S$vPpsY@x!^dN} z&U~WjV4vP4Nz%R*baptA9dxm(j8xKUTB_3*4&xd{Sg{XrUHW}g9Y7K)CZo31)k*qw zG7?Exq$M)(kOCC1q%1lSWkj1Ai#C-<4bY0>`cOn`Nf|$?z?8ahGgUL#R0C=3>S`RO zEnrtVZCWYNc0_vHZ@L;2y%dMRwt8A=qL<<@*iv!O>yjvlF-owG*F!)LK0Op;i>_Y` z0bu6A&t@loo4k;G6SgJ0XT#0lgmAL$j^qP<%({D%e}OmPK=K|~o%lm?0&GdHU>df- zPeXUY26|Ghq(ZO9OSpwRNZtSzWMmLjuf!R)7o$6=Pt>W{Q|U=fUexdWB>{BjFkVxz z9J6Ox#ECj`M#9twwFhbNHZ=ll!@&SzhgBM+r};19B5bt>_g2q@$D$7gu&_{s z8Qw1hOn~P@D=ZbA_cy3sSv2qri}2JRqKW`ZG5Z1VtYDFB`Ie{-^R0P| zO>Y%gUPHxn4IJ*uf&d2@FzR(i%79^6gTV8!2^XnUyg`&vDl3*QD3xFwkxFC2!r9onB-s|nV| zhc<>&Z5pFiZW$`uG|{E zh?IOdRdA|#%tdNR3x0iIrJqU-W7Ya2l^-TTscHdgB8!ww#)PX@HmU5YnUH1>)?5r@ z7_w;cD+e_bV!+YRozWv95A!dcP{{MMkS8%+9kl9YSCszMTRW=#R%T$vM7q>I6KyzpYKj|ctUIFfZ2tEmyqrey~(7XOG%Mw3!Nk@ zRJOi4r}$DY+(A$3}P=O;Ct%4F0geVz}gr1?*+M1Q$&?4vv>Z z0kIu96uU%_5lbjW3#GppB1r_XSy_ls>|BN8Eg`Hl^D8U!*)hS)#A0WF2Z56^0)Y(l z3rtINBymvz_<)nSKQlP=kqXAII)hH9q0lk&%e0I%lgHj_cf%HN+ncaY%g9KBghAwz zEGMF#5}cUaf+y1nO@a=iboWUdw&IY1I~Zsc^?I?A6M$1JEW>zMju&Lk&FDD2LuxT7 z5~o9;j7s9X48xio90>eGQIf=oJQ6v~=*k`rl_;|wB$5<^72fN>Q%?_{|bOBJ{$n!|K2c!{~u6!BW0#B9v+UL z3qa;58<81K{w?JH*n&q!moqU)!{2G_hhF`EF&h=bXeI2<5g9QU{wpDW2csw+3;!fy zCtmUyT+JM1w{pw)LV*)IbuQ^O!!qL}xyRIFe#WZU(;ZutPUV90RQiUDXEJj#pLCz{ z)_YfGHDyoF-s`(3(2!G?TM=v;b9ijqxTbM$6>KfMV*I#pefYSDu;BG0`+}^Q4`VmsRbUvaY(ldT8pb+Nb_uZ{6v7SN$tj z-G0@RSA9CIefsKam}@pPL>fN5_IzX84NW(^-}Lm%@XVoE@6WEE(=&I;&GVXRiJO8T%FD*E?uxsH%?f2Z$e9O}v&5Lpto#{NfxMay|OXn^8=9bt^kp?pw99XXR~{)%9yWynWpr?Q6Yj``6tQy}tL<`kD>PHvIXnrn}Sc zPHsH3@vEQL|NPu0@1}q1n|trBd*9xiwz*^T?#;)x+_iPdw#IGuZTsZDocnIr{^5=V zJ2vn5Hnw`_g$Ke9bUe^clPnovP4vqF|I}}r_}%$nj<@82Nmi3E!U4Ym9*^Ii2Uq6a zp_0(+ap7kV{xgTe$c`6fPK|+B$W!sp5LNC=fh1Bwu8hAPf`WK71b4*e#}M@Y E6*9^cGynhq delta 4085 zcma)9dstOf7T;^{bMCqC2agLE2zewVD#|<5@T&P@RI(8@9|%5B2@n;POyP=#$taOW zy^YgH=zOU`Iq4~xs6o{7OH(VyM>&q6rdv@Iwp zuRz@xLTDq$_DLawixtE|Q}y;YpTzSIst?*<`bs{)a**V}f1~^+*K9^dbt3 zy(mvy9u)B+W6Yo}A;XspNTBV0J9WF|QhR8hPMgVOx0_71t_R$Dzlt}FpBQaR$|cKD zs`K$p_UU}>RAtOL+7YK7J`$M+^%ST;y*kY(h7Lb_Q$nLXzB{6d{7L6gON2wirh6-R%H^{TIrl4VK)*+At^$^A% zs#Lqb#!zz6@O=#MAxDll!XsaKasy+vSXSwne*xB4tK?;0<_zv(++d!qa>D0&3(6Q| zIf)|^$U=F-xQX&xQ?5g`vTdpsv~t={;=j?rrmUR4l!v`N<5^Co<$cOYLVg)Fd|15HDjXVMZ0;vCB@4qD?pQ*rAfe|kclko1Kdf@Ui>BNKtBP0uf$TQQx0!;01HxU z<#T_yuf;j{>P*lIPj4syeDoe=`)g?Fk=u5hm8Prp>~&VAY~OiB(vc1hkZT zrui)`xSakc-j?LIy&zt}k|UK<`>=m#&!UtXD z5F61jU)lCSFWT)Rx3+z$O_Xg1hf(ZI2kXK>mk#Y_SXa$QIhgC*NBmq!EKEM|$=l?PPV#D$hQD zR_dd^!OpWOO4b*B7z+DP7_XH6%>@ru>Mr&NDM4-4%ntt@{rhS@Le@R#d+=OUcx($?0C(AYto(FmfQx#lq^U|cKUq$AtW6S zk5^R^hSd2yV(~n3%ar7lLHw4So`HcAd43-C*EzfW@s1x08(4X;_&BZFL|&xiFETjD zSN4OPKC`4Oh)=DGi;vSubY?IM3!j&rT@aQO7#IYMGM(Tmn&gTWnG~Y82m+-%szhovn|nFtWM|I}3ughDdVoO5EV=?rvB6T1(S-SejRvbI z2!M@}97bb^De%z%kfMV_g9L+J&qOfhL{aq^9Q-21wrrhHFfgCt{RMXQ71ZZYoU^q8j|!(j49Nlk8pq_;-sJ=b)`oHL}OPC zMiWq-Bn5YO)$Vx2tTzG}nA3sN7B4<~Zyof{#J1ZheAIV-TSBEE^+R@x4N(d^qwzTr z9lK9kmAX4m0Q*8+r=0rnBp=vusJ)=<{COcrZX62j%q<7p{g0H0uTTfRLIJ;=?4F}n zx;FD~4bE!v?n*E*ZRQYfErUQQfnd&ibwheH2dz);~0&7}k+hIKalJCGTxx3|ngNJrw=yuxP z5)cHEZ?7$p!N5PKEwxTqMkPvCvo5;Ut8>BP2STL_mPy6x@7WeM-+R{uuS@a7!!FQf z@2)VIDaDao_9gXrjo~mI+FJ_4VX-z2TP{SvNnjRrL(Ad5fUO(~ec2&6>rGFD1$t;! z57JyV%)2WQilUlc&R8+aHslU53HXzVkdDn;akO;Pd&ksD@4x|&QnjahkuTt{Uj)iA zhy;-k4+mfOQ_lB*+sEf46eOQyKqMVE9nWF&ZD4)IqDR~IB3weLKM&x-d{xm!ehgJqszn^bC5Bv%@ zE^=@=2*C{+5q`(%$n!N0cMl$Atzd2^_RxEf2foLUD%_uXM<9UD;odP+TM+&bhmq$f zLM%b_>45~~`yxgo#v*cj{4k`Mh!cn;1eWjsymY?*FMJg96p=&}{0aTW!Km}|gl*Ay z9+a^6HJ%6Q=|fTGC`QC1;t<@Q+wcty#SJ!l2PWfwbyBfD!g$J5X3nxySSxI;j?kd6 z;Cg4V^KQt+&~>iX@VM~35f>w~BbWD@8Z{wmPjqeUQ;&}6Jt%H!!r{Ji`%Ue4rhk3n zr~!!svj^TwdMs&b(%$64l!nx})XKD{)9W%G%V>MNU#2cIJM-3{qQUmTbwjp2QI@r3 zsK>p)-8F1l)rkEg_l&waCSuI-v8%@J9eeFJ3&yP+FN|L|A$7vFoS&bXJ89~q3zOfO zl02nr>V;|9(<^3_&YYcBo_950S8#E*FlXSL$~m{^9-Z4cZ^^ud!rJ-y^WRyJzc6m$ zt)h>gO)oxGl2`I`>6t|r%E}j~FTT5^ZK7FiRx3jZg0&!xcV-l0B~5&r{@#Bm3L0qrBnP9G1%PW0a4 isW93S?^78`Dw*YdDGfrsjx^}+J(3E~dnczs!+!yd1mw2> diff --git a/libs/angular/src/scss/bwicons/fonts/bwi-font.woff2 b/libs/angular/src/scss/bwicons/fonts/bwi-font.woff2 index 88036b7b3e8eda7d55afaceea4beb95b02821c88..4c8cfd6e047aeb1b7f7c6430b7252e34e995f1b6 100644 GIT binary patch literal 34628 zcmV(?K-a%_Pew8T0RR910Ea{X3jhEB0X3We0EYDd0RR9100000000000000000000 z00006U;tbZ2nvMSFoVf#0X7081BFZrf@lB)AO(aR2Ot|U>!YG>^JbVRf{g>?F#Oc$ z2+J4;20Yw)_MZ-@$XNT43>0lJYZ(I65;PX#St5ahltnSQYY&WM`0T{d)1PMcZ320Z zKkL8bz2*#T8~Kx*yvOr%dh*`?5#&n*?@xkO10+y9XrYFdiUBnR+KEBinNXb=GILlb zMrYbF^UjQ}6K8mSZoREx8*5_Z1|y`l{f#i-Z~Ge{y%8z75e6|}ASk0sNr_Vg1p^CE z1i`Zs75l=-^SnFp($hPYA+pV8>lN-`%N^WNWXl}~{VH%EvR5hX3SCf+D%dE^PE%2% zL9tc*W~)* zSeL|+6-?^-`B7(m$(6hNyJPr>P&7v%wNfin>^K892L|i_4g}#zYR@!T*i36HonayH zuy7!KM^L9fcwq3Kj(O96q)SRo%(4b4u#I%YqZhjW-&A|s2MFMyes&J@aZ`$psocs< zIdkuO^Jd<=0}yY95I6uBhNM0PN*h2bVH4hfd(I39X+ixQo#m7zNSpXGC8u1vY1Zyi zTYS6Pb@h8Ujhn*ruWGt}+{re0l~fv)R;83SY^_b%oGrta@dN)uLiw@*5Ryrdmq${v zAH{1RrTBroRvGs`rS+`=piTjK4J2JiUX$o5rONI~EmP@Rwzhf8%an}XavrA4S;m}_ zzQt3=%$8*>|1&_;yjCsX)(R3f{p+0Pw*SeT&r8I)_#RM*Awq;OYUzZ^n1AeJ zOta+m8-I2UlkLUQ-dBJh7x%)%*I}sVq8(e524F^qP#&JgwM%p}%&$8&?!L;xl)Qy8 z0<^y(fb^F!*uG7G1P+8{1Z1fT6dB;4Z3m4+ZBPOceiZ{MVg(h4Bst@O*@cTp<0o`Y zD{Lj3lDv={*klPqAxtLCdU zEXiQHf%D6&+cxv1zT;d-`f<3hsvwV# zk1lY-_ax#31EhRPXNsL{XCQ~cVl#BT*_w-ABH5>0`-vzEc}kZ1MJ381Ru&a{e718AI=Tuj>~oLn zu5eKx5ZGMKy9J;ypbT(aK}dU*j<2>sb`CTpNJJ)Wm9#crNK=zXDJBp^K?&CHrNKHX zmsNGV95w`R-gbTmS_u68xB=5kI?N|C#bi zL_@b)){neyI7TEIM|vcJ6v#KRk;xT9)inJsR>Df3?%E{DoauCYONjO8f zC*0-I*0QK$>j;q^g3VsbA_NhyBaW4i;pq z2xmk(s1HS&W@y3S0l|2)f{@4dI1w5^8GT*kEwPJtG%qo#Wq=@U05Z4jjs0FSNEkV9 z7|QLUA^Rx;(+vk)z|*t4GR={$Pij3LS9z~UXO=Yv5{@G`YDe9iRX(bNag9g_=t!j}#E3_r7l#3P5k4X@7Zf%?ao73K6ft1S za3TOsK+wFJ9t7j(V9g{(=$0y=uT-IqB0FZwU*!8zBU8(xy1K1}FnQ-28gq+p7bs-& zgK<1m#@2hsHd}Q9GdC%tW@zqHh$5BQ4*7ZtFju6D>>{3af+goa$-4brOZr-=nea&m zed%p}<#9?YZe7e#FTMNhn`kJIejAf5p8&&tx7XG(DjbH{y{ZJ1_`j?)RmHqwK$nDE z3k`CaXl94pD6EBOnL+T7gryhdu`HBB13SyoX7&Ir-{=eRxR0BWt0Qs@>yR;C5-f%% zMa#t;w!DA%?WkJ|>9FzcWLiNU{MyE;+D(W83SqJo6uP>~p=19-r{(BJ)X+ocQ#KokG>+IZWmFV zu8idOdAu)VRQ@#g+N<<$jq%+hnK)>ktoxn+6Hc1>xj(vSKZZ0yvU`68#)T^+;XB^y_DR3QD}t*ygHWBnTsRQ_Lg$(vz$z=g;VwwJ>0%y=1UV-ay1ef1fXEkG6!;pGBOr zuM%Q{YU<(_L*~#@2tJ~xrT$&4d~9?CRti-tU|&W zNZiclt*+r zWsdy8b{yy;l{F!aeuVNOjfAlCr)UGbsZ6eX&Yi?O%cCj}-Z`mDQ7Ar%spfXD0=q** zsdb7gmJqqBx1-V`M@uv=II>s+zfTMTj5pMDn+{TNqhyVZVsl~io)~Ja`}{kVuG)=K zn#)Lr{LFc$Z4M8~lSiSZ#0-ZAksV{lOSO6-Zh~?~Z6`V;vK}+zX0b^5O3IC?*LF|f znx`xs%o$Gj8EV?(G9kK;8So~KC+hx9rM#}U6C#xDVYwU{yd6tZj7?-l*QTNf9+r{D zL!b#LAR8@;hjpmc=29pqa@!$!Lo<(GxG8J9r_7i&(wJz0g~(uXGG?gKjLEIBPqOj^nK^$3f#=Yjr%LG=m)e&>8$hzAl1m41wHR)o;zLrzf6d3 z?nWo9+$~*!Ig&Kugsq{yPgYIvK*38n$g@5R2_Dhj;*a zp}xiMU3+-fRiKsw)mhbE&LcB~&x>ccAuBL0`Ui!uw1Qi-x#T!xFc^=b4*i@L@639w zaU_-jdy7ODY}#yVZ3|3J=gZ6tb0IU^g%56m7Tw2j&`J>21nqw+dI8T`Q5mkqyP6qo zfsF!OvZ|hu&lRPP|HrZsUzRa;@WQ@8Sk&4Up2+e8;1^}|J=r-%y0Dy=ML{kKo{ZQ$ z6_r>R#bE;9Fp%OYuMv&2z7<;hB@84C5l#` z@C4!lFL)&++MFO~swf#w*kq7=Lt{vYJm7CpR{MAbdV)D2(q5ycTOkVntFg4(`J%w1 zv7$>eq2$D9l-qKsTSnLUZy9ob6Xp63SgauNsQpaPmU^g(-UwnccIP85K83dfB3+P- zNUY^=BN^axIE*&fjlTMR8LIo=qH}sG6WHY=nn+%hJoY=C-ECQ7thp z=x@Eh0&WKI&4`aDcd6b}U9=C#7{$bI>T_> zhvG?rE6tO7;&Qd91>MNl+$0-1*T>IjXb|+izUSB;`1S$Vj6=)hl9{|# zK-H#c z$!sViH%5e8IU zM!6|=S1UcNZc#e*MwRpM(ZL#P&^uD)Up}ewF>&EIQ=qYBopF*4@1*|*yO7c^N5g)y zx|Tunhs0ozODULS?bfXpF$$p7W}&O`He0El?q9$`Q=(;5M^_z?5Z8=tkh~(PxO&k$ z?dl-sVnk@F32uQ3PAcTYD9=}$Cxccj|Y^p@1r#aYzby@hWgxn3Auu~BYn{2uDZC&pajgsca=v(h=nyf zqNJ2*b=7yNd=NE;%JURKu4!q{!dX*&XavfP78^ZI*qjO^yeSB4RD9xmt*u=D-W_4q zzo_Q&WTUN}SoGKy9=pIGN#!aV)3fX^ipCs1rZEfndqN3bszo5yx^+Cfm7tz6HstH* z=+wkVhv>1AGR#ZI%bUzjr#iPYB^OZ_!UO3$`=gXD;Tg+sD{1hLBYD znV++(VVwG=+^mMqSz!wNh;l0Xvd-gHYqX+>Nxx}h>%LV4MEG6=s868H*7=cp+QF56 zNY0h@LCij1<2ThxJ_4-}Jmq!?Bsh24DSz9Qfj#WgeYUkTVQW)+?*I zp9?C01$~NIBkWxu)ix3v(+o}q< zL72UfSdNhP_hG`hP4eqFeeH|S2t$I*9vKX4B6#VqkOdp$EuRHEnGKtsBil*@8^>*Y zw$M8SiG9rczJC#gf!ROCOGo<;{cnV*HF{o=#Ufmo)GJvu!sQziZ~EG$iZ2q38-Y;JiL{j`69bM@7Jz@K@8%$B!^mcohH-Lo zh%a}5%zl-Rk9NnrY3hwJp(m*Qm6?^^*%jwbyLt#XeHxfEum201aiQfm;$W;QZY8Yf z&Bm;Q~-lT)U^ z%63}{g|Uzc;l!@Pj`kQNhDl)l9t5(h_A+u7&MM|bCROq}Dy&S&7zPsJ|5LacE@3#2 zbqS=3*`=|TEB^O*Yd%L|&OX}RmJOc2|2^}kGjF~YupaBOcA-P)b8s1f=KRN9|9@na z-^Br)_gG1R5jfcPP!Wh>U4pZlvv!|(ldAF%DfD}->%=dRwBF;sQW z{zR;*BF5VyqI2I@mtL7i+V=^0Wch6tsO6oiF}}d|z6YUJG!Uh?eWh9D%e06#tmqkm zRlqNZ*ob8E3bco2F;j*xCqI9O+J06Pu9uCe{ zopOyLj##Vae3+uEk`e(GDvRz?4_aZ6PTe$PGIBR@a=ItBF?9v0x{2S_mNS2~b?}(x zETipa&IW!csVfC^LK-G;g5{MF9I#*Ag-oRI=RNxvrs=)a;9pzhBcNq>dxF$fp$YV( zafa+ju&X$a^0?6TfoaYtN$gJzqmKf}-|Vp$x;~<}3evE%bf_utP^}ggE(SAa2v^U= z90rM>55jtVXw?Os55T5*=Vdf^pVO-DD+(fg`L=S*Kln-S4o^tPI)R1g0Ec z`g%9jO$U$mA~je_goHx1%N2`}#-_+pbS;r32Ww!q%_@L+5261nNT+ zL^o)fDk=`)^oV~OrqiyR`OVxTe1lm>1k>SwxTbh!rL&ydaI5oC8*?|y$3Xq9Z7_2Rt6XH`qM(B4xn8_A(e;jyTSo@P=d+tqNLs8-J;mB(7#m4_+wy_Q z9+I4<=C&Lo>%tmBtRZuKTB@>j41|x03qh4d2njN!kTJSGxYn3cRB0FgY7?>G5Lg{{qoT;wKpynJv`&%!g?Zo+$> z?1M8-i?jF({%&&-b{vFp&k=eL9fIG5TPUaB=!n|f{*L-Wf)cE}MBO8)2&hCheD2k3 zcLlWlDF(yw!O;%=J!zS$`glEqJaj5UPG4zN{37K0Nxvkm$(SklY#=)ZSaF<1&85zL z6l0lU>scs^++}b-H9dVr1+>q;fYM~`XE4p)=CUZMAV#Q0`7L6$-ZB0*)NGD?bB1g)e97+l;N4lQ;?3r)Pl0G4mlfFWsuovP zT^$H1L8fTyxrFdNx-EKy$WiKrm9?8GTAl2M(L7&AwH`jVS>L+}FlgZUkLItO54$pC zB0+qA>o+E6*A?+d1OinFgw9uPQJ;`LvhL+w-CamrI56D5cvOUlgrO;=`uo!)$dFQn zC@KaP!n4I5-(*7Pww2D{*2mcLY?Upf!&oJPOu9hw4FxAOT9x_$%et@QbE5ZHhBHJ# z$qQ_8PIJyR?D6t)B-+bw>%$gZ48^$KhP=Z+N z`OYm=1(`U?DCmuCZ>{zk+~Ble&7yJmy^#?Ov#wVKD4z@kjKeskTk9L5mZ5UO5V1{I zTj)xaHNYFNGn3F|y!loI2gWGMh+(dXiMbF;%sH+Fh)O=PS>c$J+;C# zwOi*^3Lur*TkarpS^+W%o6U^!Q#$h-jR!84bdDdJ36r&RF>eU990P1h{OqKsISuG2 zJN;V&Jta>2@9v(wuyTP*i;9a`B486ug166pJf{=77_IiR))z#m2c@{@=8nCRHK|c~ zmGWT|grjfps=o~1^&)$9ErdHxXghyM(8sINik2;J(Cgd*)i z6g55&aXJTw*Sb5*0!&e#KZpQ30Q_RCr=%(N9>VigwM!lHWE(~`ZN|V)5pGBJIj1$Y z%p!EMBQQ=3{%Ctl`^raeM^PFAF=PXi9|e=BcIj4gDmuXhMCELx6l^qBFBR)0K2)i~ z%2D$l;Grm*1NF_-DSFu>rU2=d1uS^fmjMy}kj27)-c{OkOSh-l=1HTebIQW;&zrQT z+X!!>3(xT`(Z=R-b3`}#Ebuzy^+=_dFVZ-+F}F@Vr81ufqsR1_YLB|A07c~LRQ(V5 z_-IcEfF-8BS|NRBwh#s>+(9wb?UU47&#(~nvgn?=>p*u;Rw(;ZSCJA$J4On?wI6i~ z@7?71XJeS135QWk+Y~%whmtAocE$@ev3f5p$p<}SRM3{ZM(E-M?Wpo*vU7vYmxES4 zSM%_1BgVY+T=1^p7-i`x#{|D5?*3?cL9|5j^|=ZXug^T<25pauTc#Od7Urnv#*### z2qDFKA{6r~XbHZ=px!IeJu9+`&~cZ>U{h9k50if;B}B(K`0OSy7XB7NHjQcjYVS7b zrMo;amyJQQ-Rmo>}r-@u3TKzO4)!^pVwhOqcLj4(LlffhuFc%Ppo z0-b@+rjK*(5ygi&7*1@mE{_Ui!^>?jLUf0J>L0+^pBBDxcVTW>-~Wz5obNx_20kT4 zh>~qyqv%BPz)e_1CsE19QbdtduMgcvOiP;LGP^Wkt%?|H+l{<;rsnP$xf%LG%aEf~EV4rxX_ee;@ zh8MHMeTOLaW$N4_4z2a24jI()W`1Aj7;r(~9BcJU(SorffWSocn_Rcey!B{iw1C?^ zU{u#{mQJz;7hPxgJ>lN!16A%7uL@j$9rJicVbE@Er6r6CZ>8ZxNT1|{h_yk|oie8Y zSlifZp1c&%_-iY4oZ_>hX>DRR z=xX8#8&Mr;z{DU!vnsKs^gRX`E7OIE5v+7>Ullw41T4K92#%un1(KKgd^o|4qrH~X zd3VrXyh0&S+m1qe-HI*;1L!y9##BVtNZ;DCo%)+)@Hk14iy0?_e!OGdckh)poci- zh9KwfT%_XA5*d#aoMf0GvBIk{O^aC+3!|a9oXTze(%!K{)YYaAX3f}H48z-gl}kr^ zBE{hf>v=){VJ_au|2q9fC1_uds?Yp>z5J6O_ebq|;QecfYtMdF^mInL%?3a8ZdFjS zIMUajOXqggqw089{r@N#xe!mJ5U$pom5|T>oUonWAFt+QBBLscO|NfhamPMfGpbxZb;|vpd4lI=K}UL<_=`q z5>c0KPD~7!K@NrkD=CVd^C4)0?AJvUuT@zV*Gw^qa0|cvKYhSm{QU+kbPVyjMdkVH zg}49rH{!l4pe_sigS-ZW7iQZsDBs5`7#e0ri29!hYlta9()b$JB;z$04A*mOhJr{t z;!zE{qPzBMc2>VqfJ&X$W|SV;P4(`>4Uzv#lkhL;ss2%_$Zqrp+NtDbV%H=42qAah zUiXn5nl~`_Hq(8qohOiRkMKxO1TvGa#Q%z;!i~;0uDzIBlT{ZJwd~y}ZO|Q$=|9f6oMe?}u`R;rpblYNO{W zTIu85!WLqITsXCY7a{%kuRFvWNZ#HL6-eXvNu|Zg&Rumo8Lr@xxSAJ3F4~ll#aS!1 z{fx|507kqc|7e;zl%z+Yw`|p7{(n_@Sqf(DV~kZA_7JZ0;QNHm`g|vGQ#1Hm=$c8j zHfKDRTU`VbkaMdloHdiWsyS1j{<_oWFRN}e2&}Pd$n_L-`BOfNq0YBcp5Q*lJ*yDLaJYF`sr~frd%A~`3 zyt{o7*U;+?3nc09nju%@;pam&0!bA;(*S4$hsIEL+;6o@21*6s?z^uni}q7(7SqER z+7FsJvC*_Iz-74lV{fQMf8f-y7W5}FUcEGm2QJl)$uU)P>&5cM=hkCwIZFRdJwVuu zNQJb`(_x`V0Pm??{jOb&&+tufIMcvaUs>J6%IlWXRa2TVHQRXNLK}wz_6p=|A=zDt@ zDL5my=hA`j^6?>PEyccOr`-GYwEc}zAtL<~&vaKx)tI#qWF7DSaZsOm{~^e-!m5>K z%K(-BXEvA6G{j1Z^GtuART$kYsH)YbbTv=>sf_jamJ%xf+?Vn6UYBWHK&Khs`Qs)! za$G{F>ghHoy53OUiF>AXv&Okhm-I`K^$bn>9VaId5%Kp3#u&<}@Bsr9k-rW_9Yj?S z8SlvEWQ@n`%t>#UqDklTZaVlpYYWaPt}(Is*04AV&CCNHJBUpXR0~UV44zcLOElKE z4-XX5QHbhjO!;EwPHc@4D1E4RB`-E>_q6;C1IEsPXKQ=nt+u>TAsb;6 z&xYj{@<#fY4mHk^;z_Vs$UP-<*?KrJoxNs9**O0pQ}rco0>k4m_zqU~ErxbX)ndq?ItJ$n z#_x7ioco@~d~5UiQ?ntYcOa4uk9qfV)+lrn769>TDpJ|^;^}w#k}IcV`Y7;rwmr_h z7*-5_)RbI>v?gu7?PEU@!v8=WqDw5=3C2i+wzprrZf@&?%bd5NRPZlrN&8zib__7* zAiA?p6s3%{hGTb`y!z`&6fK|4iaxtd;H1D`^{Vai>TOd+a=Wd&7u5sKx9iZp~Ng8!u>VzCR*cN8pF^Q8Bc#h1mmNk1=j zWA2;VAM-{n?06gbDhG*2wu7je10t$`sc>d+FN4?L`~HUCZ?A#S-q;hh10R4Ma7@6i zI}&iQCib6(COP7sy$7lkD@-u=yf@7j`3_!wqPLU*WUJJm-B7`LY6d+1vY4n~_ftBe zH4Ziq^9}=U{~(c7Kt+dXBNf1wy)x;2bN+J}2w&|kFKBe49Y@4H{%0t!qW9U1cjtNR zoT7V6!XSX)fVuv&D%d=R_Y|Rrzk;tjjVkj@cU{P}gm~&f<7%l#?6`{avx_%8^;#?h zZyG0jlNnUVSb^7I^7Iv`ca5`>pSpll(8xz?yM)7ZoMj8&936}>Sd2zXEWTWBYZ~$C zlt9z->0?$1KIgmGfS@1{CTakt9G-LA`?6!TZ-I1VougZf+FK0j{TVSEjg|D5CFLf* zyab+Kb|m=rPu0mq&{R&Y7MqDoBuF=p;}JJPW7D+vibNt^rS$<${tm0c6dT2>)wy;) zMPNyXz)d{21LsHIH7rdtyE8%IAU2Nl59km2k*>-z1}^n;iS|KQxSJkHr7?Z=J%4!F1(j;AjKw4<6iDw5AXcfyS12K(|m+^Cu9dc4{m+D`(4*1d=w0- zb|t8&-#4L~H{pGi;bV>cKFnc&CU~212Nev0A-sg1;Irl@bd2YG0&Cs{_@|@xHshrU zjz7^NA~2?1wneB-9EI6n06b4snbU5j4n8-wIf%k^_35uQ#b&o3SwUbJ1}8pu)OCEt ze7wt*ApPNp@W9h2qG{+QYCn?qo4K-zlGI8@ff5?}Ide&~In&oTwC8|$*ps8gmeh^f zhCwbLM%rT4mIG%q4FNyz;0`7SvfGD>$eZ3)Pl zzG(07c<&MCuMX(N$OZYL7cVUTquQ~cWoXRiDNH3=$IuTbMb~^csQiq$Img$T7P(7O zjh`+__~E^$>Q&F>-G2z>FY`1j} zr>M5uB?EO7Xx=AldZ+dd*MF0@@8n^_@;$k`u;yYJ_~PQN ztzY`s#8KEXNdq^AaBgJEqQD%rp^_8Q0t8`=5In;O!-S$ygQT`euUt0q=VV6JCB}nB z^X}XH%EQlp_!De9@5b~E9#HE()mzvz5qo3|p@4fDChx#3fwDrU`p91x2^j*+eeyRF z)F5)>vsMGlDH-i8q$|;=)x9Y$>&i#BpT%5!?OvDU6(_dGF%6eSCi8(&l%t?pwkMaP*nMw?LqZ zd|qs3ebHwymft$q`bFLgaUpI<_aJq`7JS+W3;n{5S-{Hqkk;I%RG zaU8T*h|O@HILRT}KWq6EghcHcrMg|K5}pMambgGZfxX&Ev(jeA7x)mAEGvOI=tZX~ z4!s+6hUeB-kHSVYt4b#Fx+t>(gA~W7J_5v%xmTq9Q4#X(DFrlXHux+ZHT%9#iiRdn zw}?Np8XVgCQ+Z^LF~tcTNV9|_QisxqtuqV9dYH%vfzklAz29mVAAWrCi?jNr^`C`q@gxynx=b6MHK^L@@MK{7qKYV+mkAw*S$QV)8dpJCBa| zXRjD_byY=x!wc-@gomp?ee~hCe+IRhw|OZk-`*S?9=^jSltkZs$1O<&8!ofYG#CbP zUyEHrVI$AypooKGY6{BAE9yN1bL%}TRt1-txDhFfSv-J}HqniOjwcSUMb6c)RECG+ z%cf6?LJNcAtiSN6j4CXEil$HEYQm)@?!L3@ki(~a3vO7^#ypP2V>RTDqSmx|{F5FD zpd!GQnJ&-&Xvcgye;q2j;ha0UH>wXUB1+?0mdaP-$w`dJMDkd!!JrohKjjO%ASfSm zqG=r0B{<+Wh~@c&YD7U&UifJ0h&a2AZbul*GLNwAE?>Vca^LXmwZWc!H^4M}7F*Y# z;|UX}EjihrEYy-ktu1tWZLQIyt>q{FbV|#`B5e{usO9A@j{Ye>0FU1Y$@2*|ES)oT zVG8_4IaGjGBQX@~M>=Sj2`mHyh)A7}=9`xHK)EPtcY&zOO?#UV!=$HJG*?7>&8>n$>%c9@pSD`vPuPMC*F`l4Mw35{Liqt_4<5B6)B=%`#=fa7z_j-!McW!;@8AKZz`-=4fn zWhZ|y)r$&D&eM^jX;>6IgBOihW#Si0k}V5%>O*bxINcI9DP?YDzfW~W71_X7pF%4U zR)|tb%E3IYR-K8AY;@pffp&6oprTUyGX8MG@~n(S6QJZ+@NdiHtP;5?%E7W;J)&BS zk9D-+6T?D5mYr1MG`=gz)+2fkI(=9RMJdrK^q+(gL>@xG=Q@#Ngu7ECe#bvug#;5s zbDYQp{EwNlc%sGpTqGJsbx00XV{wu=-9NZ#X3L|gCsOi*`?XF`zWZJR+Q^YHNUq|y$BQZK&FqxBi~qQX*#9g( z@izN%5##-I(W_NR2w|8ujc~!v+6XbC0l=pLL51IR-HpW$?(eW^6zfCdEyU*N!b60@ zUBkm5Y~TTOkG*{yCbM{M7|Ubb9B;*B0nc}#2lyAn1DtfQ03D0Z=Xjdue9Fx4aI49r zVV@6}(@SaF-1H4~lKN0nb1r3PCE9L7J{BE)oaFp$NgQ*O$T0E?$$1Wc=Dgk^F&F<_ z#C!&uS6y>2(ZdZ3gTH@ktW%P2-wTdNVF>!f%ibAZEI7N+AnD4}n0jr}U3iHyD3z8# znmh&O6xF)-Zu_Q#+N+E$-Rp0qrz_Oz?OLtH_6})H&F9!k8A*O+UsFE?>`5}J&S~`U zc-LJ0u1E4{fMPrPAg+Pqbg0PSxCZPXwu`LQg?{_?8$mt)x)}X_fbY}~J;&29F>?_* zd9d3bi%M5+7z<9SHBeed8@Eve`R>Qrip;h&^V3#`AsYX}3 zxjA`N+S)$64h1JHXuI?Pq;Tp~j`s(bJs}OkcIsxJw}*Rc##2y54Spc6NDJg9PHIr~ z9jWw#_$gKQQdeS%>jsHo(U>a(Mprsrz0sU`m)m1%)y`@~=9MM~QdDY&=)?%{Ft zq0C8m7yK7%=J%UbtFgIhr;xE*K{Acv)~jev+OLHGrsYV2# zu^fE5Z5o=Am-o5_1mM(PdWTT!g!X{Pk4@-^KG6hR>NZ+0r5iB`JvdO$3lXqE;Ua zu+QN^U=jb+%JGiZecate8D`^-Il*|sX7O5$A`GTDEt*dwX>{F-Z1)Sn3@wai48jT_ z`Pf&7P$W=~s{N52Ri@gBk4bqo-eAsw=^JTjrUmccUyesBFq30f1T#y`XKd;8HH=A~(w6FP(6c9{sG{8*6k`Kp7hGC7YD_Mr?w z(;;L+(oP)(*@Qxa`~Zmn3XPLdgx3_30|_5{W!{5cN+*|?IM2NE*mE^Aklh^8eTr^A z;^SwJN-X9HRm_z(I^q!m3`YpY2y_!7=2qW+iQ`-mm`kEyi-2_k(qSZu7J;VNv|3a7 z#XTpkB>DZeFU_8?VS@!R5#&E7ufNNX=u_9{zR-8jzsy~qqD$;nnr>^f=KDe&S`7Zl zssp1vKP5*$TT~2+YGLQ4Yt3>n8fHWt0v~D&d_G2i2^$IdDTvu+-GWQwxgkL(TF($v z4Wb}>CxcKn!HD}9vWs-g4xxR%*02R@ENJ7xD7omu==@phuV^uKcYd#lZgcWxBrWX= zKFgMcAm<^3kvO1+d2bz|Qfi_{A#vOm@ zlw+TbI0Xt{4@^5JQ*XrGDqHmZ!vfFOE|!=T6{_NTQ5M0KPVohr_-HrEZkR(PXJ}=+6n+cLqtFg+*+2Jo?dQmleJF;;D>!SEDPR9 zbGZ)|W`^}4plmAJe(Sk+iG<~J9z+0bjjF<4kPV!1<42lbuB$iFU5JR;O;S#h z@##Gm)ao45PvPu`Z}L5_AJ>}|6{+I3p)4Mk%EagG=cA=y$%cld?D`G#`qNnIQh|v| zHM}{OKGR>gD-=8MYV_^Gz^>73_nt93V=EAO@_;mC4WI!KARqvaJ2VSYlHHbtRRjeL z&5C9lY;0?FV~hl;fk+U$ATmkPP7od#i4fbdJTygc_ckOmf59$_DakhYpFk@hoo7fJ zj7U!YzjRDWvy)IxOM%(Vie5o#Y-me-1|X6bm~M1TB}gN|2nZZu#_S-dNN_jwCNqEe zJ3c16#NdAdS7Eeohv7O0do}iUVPGfqkIW53o$w*j8u()m$L)JYy_7G8H88NSwcH;5=edqH!^ESENW!C@WNr)wsj-Y2Bt$i=5&Ot>4!?PjK{km<8@NU zaF=mk^MsE}ZD6upi>>Bk5wl*l8;_xTomcx1+}UYf+W0xrZ};siC5xoGK9>glb66Z(!QdX0%Rk@N;A;g?LbB zY;IQ>m%2E`Z_F%wap_V`#vcWibWMhGI0hw!DV?WEnUTw`bxBE0TCSJ8uFKeNicy$7 z(p*8gSk|xCi;bb49l!MYGhvvnIY!$R8C<_NXY-^Hw2G+&X?-{PxIm&H*bxcdq&FOl1sT6z_lcUuV$?LmM?Re+^<-@ zx}698IWn5r+~4`rZ@*ewx9@F|K_g(Ia&Eip+fOTdtXz?<;N2$q!Flew=;%6S&Rz0#M0*I*%a?2*R2x$85%~9&FE9e)YUee(rA0^0A>4Cv0a=oICkm zJ1>`==Y1!`)xEwsRgSv#Bgt{u&@}56D7K|0DV|@YZEcHpsMHK4#Kb_e1U1lHUXu{V zuhO)xcfGzW{<3T0cca@s{*@Je^Q`7Cyt=^PbL9?p{@uk7?w#xpYM_!`BMzjHb)gcM z`BiY%cM@uB4YJRjX@Wr&0}fN1pJD4zw2fu_9A$yYgII-|OJ#!{TW}plLm5Zw5NR{h zH~EZbb$2f=XDb&r!7J4McXT!;9qsU-dEnsLsE5@%*zq2&1WvT)ni?7?uy040UoqWc zH15Cwh}v&}*P~z`0TFG4ATS}&f9@XPJ-Q?1UGuOh4~OL{31PSe7$${q6AYuq!Vy3i z>qwtQkaEY+B+({OvNC3s3z<$&zfFifdGtGKubv`1dGC+(#!^br!nBzQ+8BJYzyAPr zDFjL?(hAW5p|FwLB*A(S)6H>>27L!wgboTAmviUJQizXWRj`@EaShL7wWB?A+NGk? z$hm36i?-uJ#Aq$HY~^Y+s*!qlmN*Q|t*40u{P8)o1T7ME2o3aXcvia^RO&5TVg5LxVyd z8a^CFA0p(lvSN&fW$YZOpnk9PTi6K{!R&gVP@OQ z{1v*$n44&YC`2CH(Ra+%Rh^l6y|*vt>}y-b%aRVb`SNP|?$Htoh&s1t52_lF0bNM| zGeFG0vR)>ax{%Y?d#^I3PDnV@cW<_%JHR9<{ian|LH^5ay`^W))Q;Y0wF@9fJ2Rcr z9Yq3K^8hx$vQDb`a^ON;z4y>-2JK)u3(N|0n0$1wWYWb&86NXFMm69t%hsdlcJ}R@ zZiEz6wev(Vd}|%_uCH6j)m~>fSvG(I zD8<|H_y~mL6Ob{2vj#J8?$pjnX8{hI4l>SGpr^4W0FOX?`T&v+hVdeqGc;)U^Rev6^tUSl|CoEIRu=?-Hl*+4G*u5e zWNPL}6Q|EfRqbGiC}3a!l;UQg*eLQi992!YhNE=GM)1ckX^?5WhSb|Kd@xR zJm;=%Y@sX0JVzhvSeqQoETSJ~DPo?E&oUDL7EuRap~iq!`SzzH;cX6R7lM*871oXH zvs&3WY-cA}o@#vxt$^oL-7bE+PV8H_ms~!A&hr zN$R5P@9m$6xj6?|0jERDaYyb5v_N+y#-` zo)(dsrx!oCmA@?lD`s3yJTT`LHaioRc=p=-xo?79?=HF(9-jkC&fT+SsPmIvno9t4 z`-0v6TXZcr;f1<*p7S$?Is|PqFn;c-3Az|Yh1msDE_rM06hp)kUIb|%7(4m&1d>b? zEq4O&`I<3OWxu|Tbdo$$s*fX`Am{cq7LiYqdN!5@l8^ZB9Nx)#)}rfCe7F)MPe#gy z@pt)3Yv-@m%Y(yOW%)nT4aISC_ozNn`~>c4nY0Ijsx-=QoFwB&;D+k-;gQ3+nX-qU zBK2%ckt+N2b)F+GLZPWLnRZL16F9;T>HcfkS7M~~dir_}5P|tCU;lhRIiktOkJ<*hP%s()HA^qkLUsufE98oQgnq)QjbPb+b91Gb@C zXamFgsLASv@li38=Z0}uw~ zHMoZ-iUk?4=A8q;;$AEI|4tbvf==ffg8gE20`O0ZDVlrW$c5XEsfE#d8iri!dqb^L zEw@4P&*pEhOuSxK7pC-TouMAg%zPCWx9ZyYdGqL%P0_u-gVN`h2`knFTD&`X^Bqj( zgZVKdUH|adUw3!M7%DN2cTP<{&Exb19PJ0abO*N5DklciX=>F*hZz`@2fX!BDP#MY zl@D%#ipPLQuni(w9Awe*7O7E6+LH0EC10z=*>BnKxSi7-b4IE~dg!yUdoKs~?>A4b zO)|kY*;Zd8g6E^a$-M)-6^{zY8j74cmNCE3$smFWW5j6h*5xP0$bN ztO+CFGlD6(2?R&`|LqDa>A_&A&0M>B;y+I`ouW!vf)!AtfjE*jRP|40<6Hng9}Y!r zpca4A#}!jlD4K;xG&Jl>pJ~kUt?3nouU^?+=hmT29s6ZQ3`V-Ds-pTe?Pb5XI&J889_vn#4OxH^+*94`n%Qn*KiQOUM4HRq+G;bO<_ zLxRp-LW5M$%=d+cs5{Sl2O1^O4qt5g0ZkS!4dpo6MK=q=F*yBi-tUn+e{G4aa<)Kc z8H__%=t#l^K_dsJA@=UvxG&w$N7(J{qH1Zo6fU-}r{hpjYivI6EXC(<8E~>@0F3P)m@2Lg5U#q{u)o)6gLXlfLo6m3n1V#dm-!JRGSR3* zCkiFSp=9pY(0B+6AV+#{ z&$xvtYy#Z}?qE`xBW7K@ZGt`ugw)bqT7*tP$P`0_Xn>Y8OcT@vQI-F6)xe#FG?X+w zNYV&kZ*<38(WRrsuxBmtT1T#WMk1~aMbcR+fD8yy8NH8#1<69QrqAx?cr`V3AuY`g zw-^&86%J9kiO-%Zicu4_$~GrLSHLXsJ;TH>A|7d<+MeEvNalxzA|aCSBA}PbXN)9d z!0KF!<+v(XeV)*8CDN{r(L1Q`7#MRZ`@4!mY%K zk1v1cQ@g-3Ipx;a?5P*`kG;NM)7sWX^E5&Jf7@@BC>py@Ir)$()q^pbk5vYGweE*l zrPN4%u(TnEJoOhf+xeJDUA`3+htkotvqCJ@1SkuvG>l`hy`mKtdB!GMp+aRQ&o>!n z?Df_3bu8nfSWEciM5cx#{md>Y%?K5sNgpTlfwMM=P&OejNlBMp^KXBpk^~463V12J z5EOJ>Q3`C}XDiUeO0xYFj}MbV!VMc{vh-2};aQ|~Iwryh`b5d%`3MSe<3u4U5oU!Ez%z0Xd|_!PXgjA^m39JxcCz|1s$sokZ)&88 z@8f8@Iy8A1C%1lQpP-;snAq*Llsd6-pOY4FA2yUQ%<$UyzJ(~$vjqI9P>vmpgn?J^ za~#NS0zNF37vR9_K|hC%j$Stp8A&Cak<%?Odh%TzvS&?cAStM(uk5N1Q~cTEKV2H9 z7(3bDj7GH7sXmtKQ+GyA26C9xtgDkO)Uf+;;DbzY{Zs7+_;NXmxiK%suPq*6YG7Y!rO zd=Xycv&)gigU8{|9;BA{hdTZ9dNaukF#- zkb=7V0q{lW>A zYB*7eZ}#_9+&MRS?4oDw#*GyE%KRf60(|QceEIYqc5tCV`{S~**O{G3>~pxog4A+UpCyc1u~KXzYFOu8}ua`a;s(^Nn#39*J3!gL~Rgr-3u zL?WA^`d#L&oRkNZ%y_F`dvwE(1|iz~D&7J^9^rYR}v!wV_AUJCFd5LAU?2?|Z0 zZDRWV`_$OCo;ft7(z7(-m{@FbjN^-f@ucV!eD08v;{Ruvi!Yl}?uQUOVA+`na5F6v zIaz^-6q*y$2N>!_GsJ-}a@|@AMtk)HCQ0?^=lILqD%nY0LVe0gZ zpQS7mtO!=mX5&_7B?f2;*rHPb(^nT(?bXzy-@bp|o4XRVZjR`j~N7bCz?^ zAxxc~^>L&HHgOgTiU~NNuY8xqc>pD;Up8|MfqPUSVd#&|{aiC_D1QV}wGg6=7sWtd zpK{wlk8WXA7RC@1ha|(VrC{$^!otNTC$ z`-U(AA&AvC;WV8ge4i05iim~s*8pd-{wbw5PKfIT-jR?p!psXH^zZ$M6i$@Cd`F64 zzYmqPG5Q(3$RsK871O(Hm}jcAVkdrM;Rshd)!}B-$uxb`cFvBnwXr*kxblRCCwcSK z8p3tXrQd}*em4Y#_dcVHdRfi8XH?7QW@V~_o08a%Y|sAn1BSlzeiM?3CHQdKsXQO8qj3^_~uQ%qGkStmf9OL&?TTw+p<% zyD>L2U8;Xe(5!mwpWyjHCc&HHKRNdEgp$yqvwn-XPvY}77tq1h?QkA2yR@$7Vp2_& z&5Rt?y;pENrLu~Te5$&u-xRxksHUBKV~u0WZ*eiTlV(XZ>r99>Yt}2TIQvwGhty1m zcw*<)E+#lT5Geu5#a-QZB3D=2fSRhjxn|-c7bjlr?%HmKyQrpSk^*2(8NUp2jWFl) zPbPT0pB@I1@wJ2b7{PN|2DRbX6my(IU^rODtA}C#B5bGenIj;8$n>uwKMT?$6eQpr z;^2<*cZ@DQ*2x*t5aH!FqX>>91Q>RHGwUv8u)WR2y?`d(mb$F0FX-4Yq*#68h-jm& z3KbtdK*N=NWSp)hn7SZMWgL?QAX<20+>=M(=IF6w3>dh_qJbw7$@gr?(PLaW!+4(D zMlgPoU1tA50;P?egh%=OpfCV<6ooG0&HYM(MT_6$=ewiLFeNf1zx?6X*i!_&|1g53 z%ie>Ylc2fVaa^Zf??z44^T(x9U>_6U`DjjVyNyWjzz?(B*gBj@0UvPQxCrKzrB&X0 z;t7iTdjqyF#K)yS6cI7yt#p5-q6?DKlHGvZ97aV z6z6+<^UWa95Dwcf5>+S=C%Y<)FR#22CeOuTQ!HTS=)0mvv9Sf_Dq1aWtH`AtohqgT zAD%AyEr2|zDM?&i4j95rnqDcmqTjp&T^S?S}ourl)yXy))84kaoDBc5w-b<#~JXy`MsYnGJ%V z-Dq$lh|O%jBr+6}?E_&9oAFvcLy54|;arxnAppwuwtLU(GMSRCu#JRFcscR*+<{+7 z0_h^T7&Ka0&izPgh+A{BH;D+c0kbvl1NslM_F0;sh2%k4h{2tZybsG0CN&LFvZ^!N%oOTZQkr;(lje(h+*aPT(W zWMBueW71~$1{D(0e!;;o*+(Juy2_1AnDV%jU>&aN9giqdL4a9E;=f_o*jWIu+CD7-d5l9#KD3HM&3sSk9z%#Rrv{K_Eyu4E@|syll6> zWV4gGuSoWj+Zbfr({W+VWc)+@z|K z3Cj`GZbAWqG!SqQ(e%U?rECa}s1u7$=XEo|ycJYM?*NN~%39toKstIof zGO(Yx3>}StCuL!_W0p1xY`fryZm6DDgB93?)o~{GA{2z;35p$kY?Jx5fPMCcqtrnf z9+oyJOpq=tg7Z)nY)Y704QmBilPO!4HhC`rU#cDYmS8~u;GM8;{0Jn^!_9ZSwX51y z;wfggC`IK6Eq2jOdO>2C2vaQ(8eUcZT_rG=-%&~9T($js!n zw9vWC9S1O0hC(zr8`4BGG(rwJ=g~9?{5qkR(})@t#?EE}Nz8c#V_~@f3g*>fJ2@^D zWiQ41_2(gUkU)YY!2z97s3zScoD!id7+_*a&k3)IeR#WAe0J6$d8h-E9=fc7wKq~q z@!1;Ld|BFDI$(qt=c1jwF>s}fro5VE9qcAvp$j?~#?i~Yb5`lq)fsJi(aUG$*6sqy zJFI5acOuYKEH4cGqa51F(Y~bK1;J=NK^UNWXPHAiHk%I5hkjWtkffBSDo0hWN()#Y zNogpDa;XT}TkVki(yYYc)b=Fj@Nhk#qX0qQp-R)cI3ojIr+1Eaq`XF(KoS%hc~JSd z*i1#4(a?zjC}{d(nR5Am??1SIG7ed-Pi2*m#=GRk@zTmQPU>wLKcCes-Pk%X=5%We z#lmP2kPZRS02&7LK^j6}!S^44L_$P@Ejt^#RZMC8g=qgZYB`mlK%JHang!=BU;beO z$g^OJ1o(75k`F+*@%=;lu@YGxq5&{If$CN0SVdYqd`a&8V7)>p6PJvtTE4&sMl+@MU@ zOz)dvI$<)CQZ(B{+i{jfSiz}U#mn;}vKGIT!P)foW@%%3pq%XJoA zu!YoS;+vCF*H|7oL4;luQ25a(IA6Rk^mv{ZbPzn%WkNa8teNw%?cP_Xt)ZYLkY4q`hwlMXonx?ppFjW~&(|-sPSym8#SSPTzS$Cq&o5qOPusG6;X`%Ih7Fr-b4&sH+RJb;q?t?RWzhkXi4J7VGnWuh zz4`0AX-t#h!z*grMU{fl>XHn{x$#8`i+P1Z&MHYrP?R{3Qi-`p5g(P-*PHR{ulr9O zCKuLbTSh$&CZJA324GNp|I$ErQgJDEGPgX8LcT_g|E_@6$A8GdvqmvFOWjA1!lf%ix2{?=FsvkPlRMHn0V636y)Qs)xux z`@>(-R;K-6yj6m?Hbod^zXh_!x4yr3OC^QNzl?>4A9(T_hQ$v*Ek2dQ(v~lojZbHo z*zmuLi%a<)U^j+zZ#5^w!{@afzAmUeRde_K`^73#BcrSHQ*@K&=KcLFp~WPO3?m>C zbxqD{NqUMVlQ%9P*Y%g}7OB9CN-SrW=yH~+V~}?5pCaI{$wK}&4;N=;$h97qqzl-TSBt0zTaZK+dos#;vZd5sy#Z!nkri zy&<<%2RlAt;K9H@wkK_ubI#iO2lc*Bo!Ixs{sGbZ_`mu?;xTk1SzQ&y1>+Sk0|c2A z2oNJTvu329@&Ex4D-M~Col0P}I@~~s*hJ5`*7kx7f5mcJa7O3keQMRhSa}U$rO(A{&Q$OH}r6bYp?8b?17CUB& zqOR)%IV_uP?iP7$r90~%WA%&G0jr6!$~?3kY5bL?AfrBG*Fn?>IFtWm{JI(-nv0`DgGc3!R$jFXa&|Mgon6vJ8Pqu<$FS>dd#GOQ8~kktEV~S z-9qVO8l}ON7bEsVz_f=cpOCZO6n9<2nQg5BksL<@R(=IXi=>Oaz_AMYME4i{@WXPE z{?1_sW5xT@(e(~n1B14R`TI1P4-1ALXiOQHNGF+24+<5w7^)~wA)?7DRj_n4S0$w2 z)Ur2VNfx=juFhl>O~C$Ua&ONbs;nX>w%3pU!WpSt(x|wILqx30-Eqjq%b)NMIo2=N za@+xX3D|xa1H^ii!kmpk0XqVeYm_c}`KA}v2KNSCGE~w-PHOcRRpfcsb>mSRZa35R zNZcuJ9x(ymc`>3m=<%DhZ1LX4M`n0eYU(*SVpn=ZkK;B=t0twPC^9>{GMtps?$_4C~6*mcWR<{&Oij9p9CF2tk zo-ZpCe)Ec@5PjM~)LiQ3l@QH#Ldb+^jW4^Alh}}fWpqF_fn)5-Hd8U%FBR!xTyt=0Qn)vCJ;2t zfi#_&Fe91({G{pOHfdOMbAhq>3(+D-4{Iz02BM&;BM6jx70nv#BY~fgXdR|fC~}N^ zw@tVmXOORhVM2p|EP_DjsREOE+^wOQgjp2FuW_=Y4%R~fje^IT(#f0EXHcj{Fw)&c z+R>~vlfotA?2SR;2MTq8Vw3siJ2_+;cXX2Wk599$>?Ted3BF(0J3Y-#s&P$9db7X3 z?e^7)i?2qOmv1zoug>3arvVJDqih&G&dZ&jLrIlyn4imgZnT~vKeT6hV2ZJ}nt{q# zy_WIp^uY0MpTQ_@mq^&G;8j>27N?<`ZM;Do)@giUK&YiPDhvdJ$KGSqzhayx^56X8 z1K|I0ks&fTvHxtj4S&+>@Ib}e0_6e0V>{#F{(T78WsJ<#t%oFf2~V7`i2$Wz`_^xe@519ZK;k?cD>Ms2#*mBq5HtMo=;szJ7!JsDtJFGi+}p-MUwx$I|VND8E_BcoQBU z{CZtuFZsk!hz?&o`T5-3XS#9;M<>1g=q0m_{;GLtvP6+wq_5gaXz%T=h*}a%-M&6| zi;QPop|&jf$`WcT5`MYS@#3vz(XWkSn&&WN^4Vei3dxs`)&^p{P+4Wds;xomI6ppH z;P>FRJY8BMue6Esy)VNr5nad-J$fKFjfpj&ks|R*{v!OBv>0T9&Ts_t`u`wFRtu2_ z+1P6LZy^Gnb*(P%6c_WQj{x@b-t*M;b-@B-*RdF zp-h8NVqMwce7dfP$*Jv~cAi5EAoDDs)$C~_9GtBv7nge24k&oZk)Q}(OlUE%La#k7 z+|zy9CkDcr+VAPY%AweI$!|*Q8JS;#F}DVboY>7!8 zRehbHK3)CwGo!ST31~W3Q=dQ ztOqdeB018n2TLA>v0lKme;30`M}>n+j}eN~Na(|tR#9Og4@^(RQuY z&M#UJd*$Kxv}-^&m*Tc0)Y|n=m`v`SW+knnDO!xi!?>`9Sx1IMJcE@V@K_NRk+41| z+w*8o5&3zH=;;=Q3!y{;mV}f|3jm1FD<{v~r+mJc_)IIcFxn_rxlV0+yJ*Z3DriuPpSBRF(qyCwndrG~| z`26}wG6lTJSf-I1#uZ{U52R=13&Wwbc0n@-o&jdx$FA?~>KZfS9nd%Y!gGJ=`;PF8 zjIjUte_}>S{A!`U^nZKiuhM=CfeSm)xqBfY`Q?gMu6*3gL{*l#B$F!D9l}>d(*{!$ z#ZzlBPpZ?;5gsO7Y*OtLD{4&f7*EH|72oP_^Lr&o4L&r))6F^7wBASoOXI@7p;IYp zc|k$u^`^$KW5;SK^0SW}3u|n;P_QCTrjDk_Uo60yxv0~Jk6a4HnrIq&`4t*xu(a1z zeEMZMJ@%3!Q^LU)SBWzy@*57Pu-HCh34Pseo|eFdK2VGBlS0K8ZhN^e&&o{3?;?uu zoaoy9G-1%No@z_`WO1 z>o;IS-hSpm)j&)+uiGXV6T}sQfL5i=cN+1zZ%gp02A9qB1K;OyXXsmgk*VQVGzj$%>I_+9$oztTUEyjvXq{i4^^;PRqxOMlO zvr0T=w(UX1M#T?>I*i0-bas9pt!eXiX2kYjnr^?QG``_pMrK&hC*C!oVB`AOH~B&q zs#7;**N7!ivKF%=ZR$+ATHy~gBm&d{BHDJSvV|Kw zpbx`SQ%f2Zdt)>=$*CrGbF*_&vWbqZffB){@s2{wzG!8mA2g~@n|wj-O-b_LWR&e{P# z9TBWiTp~hE%{~(c#AmCApR}Z~`L@n%%B<@OC|nC9)k{1E4fEo- zVl8*A`y#hl>7$Y&S7SydIYRLB2}z>!@!KRDi?9$EL5C~|c>=;T;esIr+63PP5fcso z;H|(|NuOm?^&l%#(Wja$`x=d$_ztuYhTB||(I_Z5_WRfMs)@*mdOlr=kYq}wpi1Uw z%1oK#N*ywi*_*lTEk4^JK7t3zuo%k6gXh(nR!PHK`i`Bq!3n{2jk`zUd7sU{P2YEM zZN#Z0Wnx$_svi_`o9Kc1d`GA>!b)`wzM-veduys=rC4B(l!Z_46=yK># zpV8i?q5e~+UNo`OTPqwYHN%P7Sur;qrx_-*kp9Tv!zYjQTQpUEYFK{e%|_`jdl}Z<)C6M?Tx>zTXqw6^_WAh%;{paW8jTmv zQwO4g%3ku?2SkIAWlk`o=bjG!ldj zMx@3VNRo$i^kBQKfYFMbp#n zo=;CtL!%n-X{AYoieinr57JOt{Ysu+lag@GXX!KuA7eoCfs(2-^6v*Ki&{Apkpx`HpF2Am2Me~YVw49GRFO_V=BHEH0JWcC z%A)>ag9d|gB@l&=kwg84Vhx6bA(RSyflv(n8QBoEpxfmHtxFxtbao9<1Ti)R4>|bN4!r0aSjd8JFaNb%wWp z*6=G@mzftt?fcbYE}8S#UU$4RQj76wy_){jJg*AYraJg4yAlOoHW@RW^FOw zYEH2>$G5IfI{f)|E9hj?!6VMGuU+!1PS|5Nd8IblyYi2ZuwdZ^595`|UfYED?2PPO zUa=GuH$mWy`tu6N1!3`tq@*k5x5C3dgSykXLXhX6x{#Mut8amup~*N=iP0lnA}U;( zer4EnXti<4LRRr(dvjOjuQbg)U@Xa8wq{caZ1l-{SzfJeUO80p`=FuIq!&g35v5Zg zAW34D9CENL2!JbB9N7k-a4hS}{Pnz5LWmAJ%YrRGqgFJyIN}{d3H!evS38{;b1&#g zM$On1C5L=H#@K5ly0~s_J_bsDaLZuh+aG_nmixSeUmK3hT6j35BfgKk3#-*|x_7xE zPxIP`cRntbO7q+J15;i~4_(}&DV}2sJgvLU+pB$c61~@ zsAJfdvS@xr_zywMebR*O%%4#;G^1X!I9mAJ9yTCC#wqOsfBy&XTg!6*ZhA&P#e-|A z=CqP`Nn4iXp;j$+UOf%>$p%nHs_9*V?W+y;ZYpi7w{lHD+^(Qq(G#5T{x#8Z(BQa{ z{_xc0`@5W@?qp|sFF=2F3NBrmw5lll677%EE=YAE(^KZ|UTIxACu>gXnpBW?Mrt$! zn6}1NsOhBHK~J4@p>!(ju~I3i{uv#{sv@?nI0+>`66aphqV7#Ggzq;rA+of1x`4PX<2isZ)@Gs@K=O&y62P@ho9u-mn}CEUBM#S*{F^J6y80k!}y8LcR+Y zN~?9Mt}b9`NZvh}Nb?{_gn2r#hG`5%#|<+6YL`;0x^kt=3d+x-?zwm0`E|W}nmpLr zs?OA9Zsyw)U)UFJmy@%&Sfk^XtY~&qTA8IG|HbDjq6>{TH)|!M4U)_k_8NCIMg_)F zXO~$S#SbpmG%ep63O)_2*J$|?hqt_GtvC)-YE7nbJa$0j?*M2htQ`P^8%$ve?EfD# z7l#HMYr*sKP8@!~9Z$8nj`sZ^M`OSwi)k-hz zTD#k-&3574y7!CTxy5#qeQrwYQuf|b;NqxgSC$TM-KS|I{vxO*4{oHm%h_J8+(12(sd`z+}&cJQ%zI$OA|eE7MNgAyj=X*v>D5 zP@PfeKXmrM;*h%=C9e#ow&&E)SUG>}>+0Wu0m8lm#*Nsos?XXgr*EO(UI<UnS!J|)m<@@LuB8eP;bdCX#WJcy@1Wk@uj=uGKYlK&E?BZ|;4i-tsG#ikw&KF>ZcChRTLlJKO7@ArlOCE1r6(9`Pf4U(=CXDgk+O_lEWSTh z%!r_aX&21L+KVm%0&UAK$pCl%DP=O&S(1bjq} z)>?Zn=Uq`YC-~ahNCzr`x1Oq6(jW-AEvi4q@(Gfh*z_m0 z(Qj+2nMNT7GBe7%dwCR=dV@D!p!KdAW4&?fyK_u7LJ-_DFiQBzviEILY<_YZBq+5d z6c-3BOw!x)FBv4h%zU$Dko}Ya3dn(!rw>a^LdA9BtLoNGHij}yaMcy{xY}K7wa9XF zb09jj_2oiz!Vl^*@)vUVnCI_DB}bQ>N2ul!^F>ux25ADhoV9(T4-ZOAsdPoih}vD7pGzLk&&fSFO7c!K*|9WI?yi0GsFi^8-&0Ur#i`dRebq!C)a`0NCRC}CpZqQ22p}tc=p1=A1t&%_wmJ7IBTTm?@{OcM7_Nk zf-}Qts$PKgK<(o?y$zpX#fko!bjYBfnL|>Lg zdsXJf4)7MDqhLoFqALguAGn-waoN`@Wn$aD5X`-dUPd1iGxFDO=^21NG=NRL9axgBNeUs(o1HCt8y$%l20;i9~7K&-KY}7)}W1O z6DDy^$n3J&AW8Prq}$vD3cFqkqlG+rasu@m>fZHisD!S{Y|l|hoPirymWkVmE5~;7 zpg7FvRd?9Yh%iYpPTAEqEY`%2!!Z#x_E0RgO=<>39Py}jnoX~OhEPKXX>yp>1h&pH~Q&s3E4lI^Kk|(xQ>4W?s}{hweZbf`Iams8U;tqg@br(J5#L5My$>9J7oSU_Ef3^DWYoDYc4w1J&ztV(nE#rY z?4-ky$iUEB0wcQi!})|!LR~_>E%;zU0MK_ZHJd|eV?03+B4aR37tJU!jNvv~m>d|2Yf4oB9eF>)|_ zn$uaX6JIA97dbUxltC-p>`TIJ4Ob0~FUX-4<-Cwl`KI#h;xca~;JXz$Bv+5275b-4 zPF8AHIMtU63xz?^P2-W<o zUp%Fn=>~H7K86qww2@I#RRm{^_E9`T-jU%Bc>8v%9j)A|>P}nR9o1Fb)*V*C z_a(SvbX7xZ@xupiXvFsq9u~JYtQuvS_AgAy$)PS2afrTh{gL%_eX*kGv;%jvx$DTvrCOL4`v>9hAxdRI@T-Qs!sb<8 z>wXcuyJel%8<%IF;h)_)Gc8%S_Ou^e^+M`CR_BrejBM~F6emz z25?#Q&FO1nqN;aP8OvI?4mQA&{CvsOO;oHY_oe(doA@^Mh5|!D{Tk7(%dT7kDnD@w zqjP_rtUc9}l+(V>;+8|-f#2e1&8H)`v7D@mS7^6wrS(Od+vDAQ2G8=mh_{fC5f143 zyOF2IT-zRhz}5RI;P_7}vwi+;fM-sdOX$Rx zJw0jGZnwnJ!$)e@o-h5T5r&63PsaB3{dEI9D{7=ujfsg3Q`3n%{e#$&X^k5mHl7KP zl5OUdUv3j{eWCB)7wGNPQJFFN-Ov97>j`*XVu|)Qeo3MdPD!1TPQj_fZ`827R2~k2 zjFg3PPQFj#N#R!Axt;Aib6p4A0F&@iie4|h4nf-)dmo#`*^mcE;GvVmwb%=t+uond z`Q0;hfVU4&^F&qJ(iTB%dg=AsVPPuP`>%zDZ|`sLkZ<1$3t~yPfHWet#Wb7{L|!1B z*~KX-uf-Hb<%+?Q#+CgU^-m6^lGHMt8?jm}{q1L5}^jLf^ zpsl(0qu^q)TjKlYF1fL#T-kMMAEm1zdn7k#@1E|B#S_UT6S>`c_6Fsy9V)Fe8Kb_s zhF8kI?soCMRpQc%&xN8ck1HGDMfX5+rz1e{pPSK>v%4#&Cu17}1%Ri6zzxdubmhd= zT7%xaM{m~KXWBQpcjZ@3n46~DS17RC1t|dZlxICbzgvp7`PI)<$&xyY?>)6Vj}YNV z&ax%G;D#q8pF90}xkP43tr36Ogi+lybChMuxi-1Y&26-#E(x7gg-Joo_qJmlT-D6OqY0TW+E-azA*dGc-$lxOf-B{|KatA>W|2$+uJXlh@Gt{ zFMs)Rzx>~SXUoegDqd|{H4@Ql-l;a~FRz`sbK}+8(p}mI_M)C28(V^)*%M>s)&Oql z;x~wm3o13#F zwk7Mq!Q8+o(pSG^BiZ*~M3d5UPk}9)CAJo=8dxSFP1*X*s}m9s_ot5hquEc1QwX`S zjrJ~|`<>bzcFRRG8W6}5;^Z5#xHvw>Yj`kst1vBwkBh@L0{^`|fDiy;mn0S@7Ol^Z zvMY^VurSKDlv$teR+Lm2TnzvqqIZrc`mq_=9u*1z0K{LycF9kPa(=05)-HQPVcNJV zA(^dQx+o`x80?cr`+~LOcfWPjYUlm%pw|L))O^?o8DW-y`^nE9J|Rax_|1&Z<@AVG!ZY@SR|i<)+_~)<`j7$c8W* z1WmTbRXi+_oCw?mNE*2)xd`LOhA05f_iGaOvYDTW~OmS?R}qu$?d295;Tf%B3^m>Q^rqHz<*F56ef5e?U#WF{=V=VM zQP=P|Ew+T6obdosc>06;-2B+I8(KQuo$1*HiDoIoVFoo_O%rk3CeyM>rq2F z-zbp;%u9q}2?8)wVSNfV7!2P2gg}5R*Sg(sOW4w`i3Sinx)25?c~A&k1ON<(!n|^<)M+%?KvMNVg)7b`4M74ctl1- z>;=|ImJ*p0*W+QHgB_1m>Xh^pwdzJkRku<7xb-aq!e*ZU8h`7Qrk$a7H0XAKN!;Ye zM2B2gn2`>C=clE9{0Hc}Apto4)-i7azV5<7YKVCQLKkptmrg}h(>`tl<+Y;ih{-flhayIdI`f4HGrV{#z1=6R}Lsdq~(E^8A%HZH4G&M0TT z@nfv=tCH8T>SSm4TV^kq{px;k*x>={%OO?Q50pD@GH>)ZLS5Ey#A*A%mN3aqxOQio z%DL4ID_xhZ5tjjc+dRty2O3Rpm@04fH!%Z3@34=^q|yRJGB3;Jy8BXM`TDLxv1*>h zDHKHB4)^-G>D!Jq7;FG(GifN7W6nEhais_fz}OGprwHKc;^8_;vJF zJJ71VxKrz3spR0w5ME$%<95njX z-921;g&<*m=7zSwfaUMm<=7>FC6vl|s4R7PY-XlXK}q``0&CYzr&u?cop4V*u^Pt}QC*{d&~~OrJ?G0l+|&1_1W; zu;dH)GaJq^=7M4x?d3#=1G_{J>=MIVT2>PTqT|GA18k`D9{|1JlWUSu+1!16e-o#_ zpVPMyok1V{WXYlRcfy~y0zM#eHt1wE5;L6 z>j{1OZ9ZErRwuQZwhDHHZfwo@ui5G4rWQsO2p+kOY32YT_;vG3F^}f=N7Kv7K=f_% zpJ~U$T&_P~7BjoE3l?=gDG}u7q0N+W`VrPw--y(S;GgJY^HW=I6L| zC%L|>XTS@uYzQ~?@kOqc}#;MbiewDa?0(e$z=dT<^TGY6rys=>-b#6l17St7i# z@zL3N&EK5F>tNuqT1x-*^(W^A-!hh1v0G&b-+nE7 z05&Z7-yfZ+^6$?-IY2O!66IIc^WvjdND=3Wu{L~SzoNGU!AE0gX9va(P(yA*!%139 zEA=F4*F%gRTg!=Gccf1%qADDf$s*gd(3sWE=;`(!a4J(AZo^Gn%%ql)P;}!)nV?`|a8ODDx6h2CZb6raLv@Qx^NsZm)>^$o1V&Fc~1zfnXF`yAF`vNv$!1QtIjF5?BGyPKN zu0t)~gt_X4ze#7-kFyO1FTJU+fhcxM(?2>rjzy*xzc|B5_^{k&%zwj{`0}oJ@{cJM z`9We?l}Q%zc)kt~LzG;H+;M0)Mrd@%9A2i;Sat&;o0;T|rMzvY87y-yS(oAP1gX-x zvg*oDfA-l-ZyLS66GcEWx|eSG$q$~<`EhM4D=%%iLY1;Zs?dHkbVZ2DhjwyXeqQqL z-ri+F_y0I_Xo60R5l8^aO3m6P_eeWnEU39)2wDA+OQcj#?WXeg=j-xu_J&PjXGgke+ZDIWR;oLz zpG@-cOX@ujVg}>FoFLG+k7=P4-t{i`;P4Olir+A&g%{#PPzDJKU^nw`(w5ItsB?cM zIt7f+9Sh7&M-XpbBL?snha_OeU(j*|NXW2%>_rKthfxh&5Q<$zO7+zu;MtXSeu5^J zkhT2vzbJZpPb{%l{(1esSO5EfX+EI73LbIK0LJERBp8hGi1nHQAIEIQ>ozNPua;fWqE&Qy>;CAZT#U0znMM1stn> zyC6Wr=?fA(@KQ;s;({Gg;KBh1gkV-hPSH=08f!NEe6C+GsDK#g zP`4!+;PGp*Tfa(hlGl*;|Aw*wY9KEk6xHcHnkAO(aR2Ot}h-bDv;*f;?8AlyO}!Nvik z0K7305o{a)ko-fl|BnfD$dIy|0=rXlY$t<@@kx9^- z3>KLcFYS1Fl`zS{89`=!N z1I#2(!%lB_er>*MuHgO!k^f&1d3XPgQs7?_eRpIXp(s%%(IzS26hX1jsVEAfQ;eDz zy*Vr9oal5@w;9lXf6w-vy?qUnAe5k)#Dh?>!c_An369n8w|zS|SNRn@6f&ie+%idY zf2Nuq>P%@pbxhdAGK35q2HOSRx_w8o(%)y@=}b8r5ZFGF^r-4Gg81@X{{x0WomR45 z*Jn6l_-O!=R%w+nj#IEX4%K#miVzU2U;Y2Xbmg}tuaHm9k~`o+6!Pvej1{xB)6ikj zY3xt$X8)XWlUG94PW*AWPX$1nTY}IwuvJZ#9G#c1&c2I*IUw5s#>u& z>mUs*Pa5LT6wUvcsQI{{&K36l$erk0{ifszAAB|2di z^1WRUl1b-$*)dX%v@gf1@?DqO!lmyj_t!Kx|4S|DAD3jjRk|vtbmiG84WJFHD5#(d zV1a-BpRwl8e0w!xSu2g~oFwb^$+GXBWILO&wpH!*j?y}7qmg%`wz=`>isWs!w%V!A zraHBt(pgwz#{v_0EPDV2hoEcd5_X3ISpW-zVOiD`GJ&Y+vVf}*xZ&LlFW$_aV=W(Ea`f{iUDMT|4YMhPO1 zt{^cfi@8=IMTP9ps$A4OGo4(>1sr6{#i}LyDBQ6?Zjfw;6A39kcE~k$NbD#Y?wuD4wT#!!&oHm4@xGMEr?_(f3SY36DHszPrE`rq zzx;H~%(C+fGAr70{GOtm-5s*I`R@Z`|9C?*+ZI9to>5sfqAGcrU`WakT+FPkU@Mj# zvoO>8dp^YDHEsa~w&c*`D2Pjn16snuU}fvXSK-$WhVk_sV_e{medp5bOg1zy&5Y0z z*AOHm?qfWj=cGE_X_U-#17|A@8SpKki@0k);f1k?*>Ed2|NCJpyq)0-LAtWKvJ5KA z_%p4U@_)hdaw{uM*KzaRADMZ+?-y_$gFeR)Az@k>au`Ji3C1+%=Z0dCi|Q=3t*nFr zqy=D56-_z${f)an<4F(C=7KTbr;s$D-rg6K*rcbYBsa%n6D^aZxi$OPKJr-1hQae1WGd@PB9@3(9ofkmK;k<10W?$cX~7SnahN& zreMd80f5>OG&?K{A!t#1ShWnb5JX`fROP#}QY_$A)#Y^sdJ(gi7wh~OjA>0^w2J`I ztWS}c=?%?`T9SZ)6U{PqI}O-P1?2L}DJ3UH8=SnNq{bI;e9inxro8%{Lc9{%V7C(GRo&U8gAy@-%udeF z2^zFxn|0-7)oLDV42fp$)~wD0iei!&3?#^>pa3o{UsF)_At?b3$xMZmPYDddkS0eE z1D5onpaO+$C%!e&Aj5Dh0AX;@r4~VT(>OavlxR#AFflbz4yEm5CtpJWM%h)x|PS)8%Jk%2i1}1HaguL}>SKv0 zYivqiI+5j$g|acAv@gHVFgW{-Lm?e1Q8t1tT=M@zrU1O5-lNXUhn)*)q1|h+ytR%# zyu_Eb5Rs)fCeT(9(*Mh0_O*cdg%r!L@u+f$xo3>8N3wAuS(($TyM&)M_|65FgDXfO z#D~we>8!qoc+$1ZZG>VHPA4nM^=*g$aY5K3FEpC#6yb{58wkY%TMzC28qh-mqQ~&x zTrLU^9;Xt?HQ%lJ_M-&76Hmg(dXj$9mE0@w9X9OeYQM``O9%GoEr4C{XJ;k;{EhKR z2BpIZ=*ShF?G`Cmg?tARTBBYk0rQqA zJDp=}T3te4U|pt@xfcN&)(GfuZ*y~Y%asIf#A%WwVbfKOs^yQ!#|LqMy>{av5qSk3 zqxn8nbT6-SX(5X+m9Ok1A-TNxZCncYLrLAEuv7H(bPUBplR+X3LZ+onC>}asyx%CM z8^tqZq7ZzgEM;K8rdG5d<9RiAs+h86Evi5OJJEu$E=vNNVX~<2HrRt2omXtN*&`W^ zzM`!V{usy`WL85h_IUum#MWL?%r(R96m=3nRis*%@};0$U_DY$v~0 zU{JaNgEI?C>TilpAnpuh?4IS<>spbs4{^SVN(AWql}?^_QIrF*yTHRGjtPlh11F1& zwOCIhO3J>-VBIOtm(ZkBY{|pmvDZa1YecDS5oFvnd7tP33OoWEE0Zce@^guJg~c5f zUU1qIJXhR`v1ZL;g?2?Toz^(2SX|`m?q@`aDlXBi?#}#|Lz-}1mF&re}gvkm! z!SS^4l^CeeeQKxN4YP52YuWJ-pK)hk?y{1ohV>Z(8$EaJK zpjZ393&%Rn zXs(g3Rdjv2;#JVq`%Q_v)%|{KxzyYPLnLU55?e)8g>^y;v~zk^1lLKbDH_07XK4IW z4wk^ZOKa?KDfqz^mxBWe3_p@hFDJSiwVq~gR(d^Bbed0SA(bm?{{1+ZsKt-T)WD^m zj8?;qG(Z{>9%CGVLmXonQp?AK+^1GhlP>-k*VStCkO4L^EEp~adBOKqVQeCjbQ-l8 zu2g8syT66VE- zdzF*7LI?m%itr>_1y&4ILYfifLMpPumNnfoI?8{$;MGRumLtl2LCQhZreGTE*){Nb z1|!z($p`SvZAS=pK|P?h?B77(>#{NuOIn0d?vPV0%FDsuT~%S6Btm2p2WYA$7!MmW zM2xU>=i%ze^>})k&+6uVXWP^+1H>7z#rim%QwYJio>10+=-^Rt$K z4ETz2O5Ta>Khjo0xpr?fOI>JtR54yhe3sfzF0LS>BJ|s z88RIVTgE=iYwp|dtgL0`%P?6|CN$yHLI3~D!aR;K=MB|&f?GzgVKWKZvd?s z?i{1~@SCYaRHr2^i0NvaY~utELZaL-mLc6GsB-&4Prv$Yj44!P1H`Ksb(W@oW+aesNDPEL$p2J2sdt?b^D^}3 zw98p38*-e_L?bO{CM)_`yd$jwv(IV5HweDso!d@*T3PkDCaru%I&XZax1eM)VlsOV zNIjZS-o)OJ9ti|CiC0SySg0`%tv*9y;$q&Z%DzUuacf0AeoVdN75F-O0bxzBs(M@F zdelzi^Uug4*CbE@qZD9xJkm*4L?6g1hDiig^a(f2*=v21Ce`ABPV{6Wt`AG zy3x6?GB=>9F++@7hn-h2;sLE(@t4H{=Osk(p0%ed7rCUn zQ|hAg?bwTB1UdjD+Lm9U6blFT(3MJf?2V7ed>$1CInQ9%!kHq*+a&9K48W~=(dn_R z^Hdoe-lUCFEc?jW&9+kG6IZ}32 z`8C4%Z<;}*_uE){%RxP3Qp(p-Q5}ewqj*+v5oVnu@*EsN_qkr>3n9?kC&q*4gb+@1 z5C6zfkj#9-T})WKf>*CtM3OmYcFgX_;WYV>nWoNJX$oG;ln<|8)^WVv8Z9AX(rf0L zanLOSBCMhW^-R3ezK#U59bD-JW<72UM)@jAdfq7K5zvajQ}9cmM%8JB?Bh@dDyY)0 zl=g;%#HQZl4gpo4_50Q=k}MSiP3t&2|1*sM?4${>LEIVP4Q?ve2A2 zz1Wb*q(}=9IMOt8)KpKcE#VPd9F@xQx?2JZS-&j8poxBn#Y0Jsj5{EuiL$DTFoJTBq-f0Gc)}wz}>Umm;s!g z2R7KJH?hB%cXTWASygt+LD|;YnA@WcUcO=i>GNWk_x`O+BsUK$0&UUMhRD2vids@T zgJ-X+VVUEy>6JC5f3S7>B?>HWL0h447Q9xtwui8bWfq7fl9*pgAU!+Sj+_Vwx>TBZ zl5w3phY~C6T0@X&a8#Hcc2RG{x(KOqn)>}ps<`FTZSy>GJNMJPN&e#)aT$b0?qlK5550IqYO(+UEb*>dB$LGTc&5?$*Al>J^RvQC@gIYHRC&1 zJY9YB*L}dYL$4D2t>}e#IrUH>O8T7I(ZP9!!%eZLz$CHBBexvqckA-QaPv_ zQT(=Nh3_obqmIr^B;6+u4Bz1Z4)zf#Vu#pP1w2pkCUS(`=Xui8`eLRmwB!1!RAqYi zk#b6{vWL`}=N#j572JaV%QiA2L9T!$1lhHwzChBK!COBg%;3bT@M_scQJi2#c%omD z3r(^JzjSwr=I!pspXP=r1_K5SHutDe&|a6?<-Nk;V3tVpw%ITSpxSmK>w!Nd+x;3v z20gsvV!rPXi7N3x&@C0tlR7*&Tep;TC{$v#nz3O@ZI_e~R45F(OB||&VKQ~n3CYN% zQtdpT)G@IOsXCt^QkOHA?JjsoJIkoMnc1pKf(C*^HPSGFS>{+qkjH*#1QC%!4}0!( zj5BIA)2}Sr1`xP+%QBo%Xq0|5{)#*j%qk|N+{$-rS$8%m%lI3by@Uer7nXV3sx}2zFIZMUJQ?VeYkoq#VfSRH5G$!y3FPY+pSmLd@}b5KOk!hKOYVh*A&;T zv==kQ-eovsZx<$ghlaETdd{UKd;bUpQE%QTycCAJEpie)+B;W}K$IGZ#YJ ziyMp8SD#C4aq3W)^qQ|5qKfLA4f8sEx^ZtY0H-}!iS>-Pr`uaJ$3Zk_7Aw$^^jZCV z<5t)!nzW?sqW)zivB&Tq5+cXzthi{7fSH|J4HwB}s!Z%C*ty?!r6!ALN2RV+t4g?l z1N~fP%(@t{_TJ+%s!`7e*!II&BQ690Z!N_X+jOR}-SVqu6ild95cO4Mcj$@R3RBz4 z-RNJzrtWXT`@1xN$%c}!H%RrmU*pv?9d>Ol7PSLruEVkz*|;dEP;^}P9avYbE}L8C z2gRo{n_fs>sZT4x+PNPaNqyP;it!ebtfAJn90TiQ3?bH#xjv1mW|V>OQE|kpvIrp} zQwk}g>)N%#jIx65cyN`J@G7L_a0s8BAjq)`tDDi_1I>{Dwkj913LIInY*_$5>b99z zv3|F#xIFH@`WY7Qch4efU+f<&Q(^vlk&V@cRKryHaG4>Ly^Vch6(iDfGhO2+6~f`7 zmC%q975?tgzoY+7QH+FBmn#eKr?$%5!~Jzug}ex!-+?~#5YYzC6No5-A)rD@Vu7NN zNzru#uIi*>14` z05UE&@SSvWqi|1g9<2SK@ycvXq2Sv8r}HnR{~PHvhH#k^G&|6~wSU}gWdvP6?qo`Z;h|cB_Xq5u2-H<1@77F{FcKvdxgl?78+f zUfu{dn{}E3k(68(V84rQSXsDTAS4Bua?s5sgim#C(IqBMsT;Kib~&GRD#L_iF*~At z^_j_fZSiMzGCY{u>j@P zP{7cS6RNSkQ&BQouh2(qA=U=EFk}tz49v{LcMWg8t%5xhC1u1oHig8lfG{I=F-Tf4 zsECeMB3{Di7YTKZG_Hx)`leR zbtPLXLJ0Ms6nFRh%!`ak35tt^51SwuZG%+}RxCQObjl&w=j2s4KGS6{i>ke;wu9vL z<8*iUXH}1u6u_3ytsZkI(#|Qb@`}*mJnCQT?~sL%qyFq50_*^Ai}5yfnr_nG@J!TC zwa<}lXw|kv4GZ$OBX6G58XIO1dc^@4CwhPC(~$PTFCPq|xdOzH4NTq$B4KUQ&E~_% z3N9chXCtLxqB#w>Sc~{jZrLeCt$%=rBHtf~4_Bw?Cy$sqO}H#z!K2&>h_I_na|6O_ zxk*<)xKV7@8cm&37EWceadWx_a2H(!mbXhAo68Lm-RQEwW1rK*wPL=A7>&*9Hv&+JV&jAG&@@r*rAW~kQ~_jdV~XA81H&lu;m zC1(-YIAKQ=t|p87JhnO4i#sJL%6=bY&MDzWzm(+gA+m9NiLka+E= zL)>69qUPptMi_}XD7aCVFeV?8)+o-QPYTVwP^`qaN);e#?347ZT@cos>I2?@#6DBpw? z3Rz^$2OLUQ3&HW=&$VTd^7__tNm*oywR15EhnzX5G!9d}&9Y`0{u?Gq9TMJ1Wf&Oe z&JgBr#{kVW4t0VE5wA!|NT4_H+4OPFE|ETsLw{tGb$Yr$HYl~h(BK{Z(iiD0wa5ee zWPQtUKe~x=k)37=l#NLtQL@czq(;Q|+=SieBx>1M@FxdF&|-$gABGnScY4aXT~NUX4GOc(jm4~40rxxB^&{rO1M zBI=kM7G~{8n~4PXsj5YXw=TtD8tW-Rc(@pS;$in}v+cFd#hqK{)&q-dJh;&5dER#< z>E7NN9ybZ;HXD4+hf$$2gCl((#Mb4CBh_(P_5X*Wkqgmi3VPM%tcZN}al}e`f4rQL zi4GQ79B=6_PH#!TaDn3Q$2~`y(TvU1#V9I_s1olJ2hst4BWq6`*NU8v_6&9+jSSle zQlvdMP>ymx%SP;L#9f%}NJu@p88I>Z1adGum`PCR?eRhD72h6Eyf?;@I4s8~a_jJ| z|LFtv;)Bh&;K&ly1bO~k{pIj)>3x?$>=gKiH3JAQWD6o}o#7Sq4YLD8{bz>b2q{6* z1RICN<1DJ-=;k~<7d@dB@V^`AV`t~o&8%Lf0Hyj&oe^qaH#LVZ_C@~BPCWR}O7xLs zWH$a1&0Mm(G;WbS4ApOiK^s}*iH1QipTU(unn1#Q#v%Qws3_(uGg2bZ{r)KF-shDZ zt1f1`$-7b3r&)UUc`WXnV}7wdIq5KRVx}C-ej3A!W1=TM1?*s`ntde7g=%ZHQ1O1? zauOJyvG)j#Od{?>U*Z21+TLh{_hk_ww}m%Wi{QpV1MSg60s}Fl0a$?VDXp&*VRc0v zBZ~izgE_vEaU?bmg3D4Qu_Ydr&D;cX#*|GE^CI?f36#qS!mnZM+i(n#?0jJ0u_CPb zUok=Nd_pb~d{WY^v1+QK6+g}lY!VC9aseprNNMhjVtWYO(F>5|aQJ2}=F~iu#-+|D|rr)-C4W&|a2?nf(}rIt^P0X+8Kk zqO(5Vi`yy8^Y#XNR&{7kWZo zjOlpZq~opiN(JFM661z3` z@xfNRcaAij6X}@kaCd7V>7v&=T`H)5*9@s5r@TL}PCBZhYbFI6!J)B|eXVpmrA%uA zaQB>PiE?nG&SHF+gI0pTj5NWtFTiCu^m6~a)@O%igQ{M8(%K`%QT*Id9h>}LsG-fp za?ZEjm4TQp{%A}iY(}KP(o5}=Xxur1JOrm-o%BdJFT%(xQEQEXvTEgrkyjPwJe_gq zckoNI7a?|W0owI~*8OvyuJWSGqhwU(4(Q|Mtg$eT?9VR>pkeIayaR=Rk=K1uIe}_z z>K&>uwwQ?k%PZqh;OIN6guKWnEBtsTL5JK&SU4y=G+6L1D@b{TL4KyG+b~_I3IvYWA32Bgq-Y~ z#v7TxAj?yN9E-HB8cqzdv^*`0)Bf0V#wG3ultp7`SenW7>3YE%Dkm2;(RguJMp9X85@ z3iD?z4oVM!nOzv-*xAUC4nTh!VI_Mva1c}frbeakSA@N?+2bh`3d0gGy2)vJXIjE< zn9jM*wrXrbnF2N3LDW8J`lqKyTDCOKBg23`rn?<_3wCKR`;;N*scYwHH6xraVuV5B zC<;Mb&k~C8s10Gkl`;SkJ{W08FcC9T4US2_Yg)M8=z-VKK zb7O{|g4m>D+!VOe*B+s@0}>g6B^ni=5D0v^kHny>tI+gGw!~vN6nl=kYiJvFG3#VY zE!(#6q2h|Ln{Cy_^k}9YlGz4qqpDI^wnN}J1wrDnJh-0-nw6FziY`h}e~i7-hxmJC zI*s?&aM&gp@_3lyF(R2d8HQ%7`*)K%AEiJgXJGzUW2|L~lBb~p0g}LNSdNY-w@K2N zK@+@*DYCY=Z5`v7@DOPLG{Q>zz35*~OK2fT^|Q?)enUbgnlmt_;7)FL+I}q>JI=3xOC2hRCeCSp#&YPb zrI_u-j>nEIMznbY3TZMFEn#Qzm3W4j$EU|8b!> zFmWto?{FJETKzkOU80M9EkX}d|6>9M^Ct9v1Z#sa?8YZb^>Vlnih;|R-m8-j-?KU< z>@;R{|j5&db*;7H-X>Haq6^h;Hdu6Id14il5-A^!lx1r*!;slOsZJv8u91@meR7&Qi z(EFwVa?{2cfDfCBL{6Xr>V9|0v{SM%pfaEZFEXDJi{Vj6$#lrfdY=egH%dIv8>$yN zqj@XF5W0{;4~qAjTjtne6kT6x`RjY?)GiZShQKpa-rGltLdG57yGvqT_2#6EY0bHY z9q zo%vV6@~+8Ua$7i*rX+xDZ{=waRtaNFv5_DSnB3uLm;idT25{U^xNDLe!rL&VpbGav zvG`Gu(5n7+ZcRKaTnZD8g2iec^|mtkZt{~+KQDA_Zkt;l%R{}e=xxbYElNBxO-|7r z5K#xL3;I3o0rd04g(17!a!Vs`XijwwYykQ>J4CCg{{)+AWLr!y*@>r)oa+Q_Va&Rx zJn&?gKLl5bNp9f~w^=S7S5z>bn(2`LO=BM{{bC-H2Ef{?h{qUk^at7;15~t5l$4M% zdu7zy=KSZl5#$a*K$B2WIb8T`+&~PBpNM z-@(;G&8o57vYqO!#D_Czt+}oNwrs_T<>}YlIAa#4T)(FtMD4pO>)-v(h#g@zjdH&j z^;6`1)Y-U*vqxvq7VOTe9JK%d#ZpdSgbD@^IK7*EG^uPf!C9M(dI%II&!2&Kw|5ru zO9v2hB-!L|`^k`wvj}~e%H9iu!D!6b;LByErs2O134wn$BFzjzrz$Qrz^@365;X~Z zIo#)#_lsk+?*i$_TaI=ycHT6o_ohVbUQ|w}sVg`0h6voV=t$7+m+ECOQuWDYlYVtM}WU1ZhID|pL=naV@!l+*wOqI^|Kmx6@j$#v#YKRL}j)K$V`o9t9M!0uA z*==w1GIG^b?=G=wE(Q?x_SGW5)rqu3$tZy40iy`10uK(*pGqGA+p5@OqG@efUV|VW zCv+BPXzdLB*rZNVF*o_~iOV%}i~W-qk%eOY3q7&B5XuGTVs4#dd3NBdSW-!wBu%5a zv=?H3`*qXI9(guueFCv#>KB^r@t4!Ki$3BFig+c6pxrki5s49e=l)}rysUX#p$U_l z@fE@w1T{S9koQ?lg@}yjd;w$L2l&&|EAM%DY|_!Q;4te@)I5Gen`2R7d>J6w>#8P^ zH`8*f8=Lc_+;rpapVV~Im)@{4NHYvhIP>+TbRoVHQs&s>Czn;lPWd(lcf}c38>G zKI@nmB%{x*u#tD_TQYUsBw%*l;-d4rUMZ_%iciK&3dw8ujNrF)fbV)alCR2NSg9k_ zvW%-~#JMxaWdcX`7BJzMe44nK4|Gpd69LvmIpwx%G+Ep7KVMn?pa0nZ zrnkIa<-rKmkaa`QH}axtltC}l`Wb8#jkc`PTM@kdg4}P~$zP{^E*=n}7DRp8&{V-( zpxDh!0q%fDTS;BgJgAxYE}g6om_i1cuN)f%&ENVs8q`etn~veb1G|e(AKrXaOfU)F z6!=-AgdF>|wW?1E!$YtrEea)lL5^N@=L|n_6jSCL$4t@Y=cj)x`Zsf0{@=fs|F^yb z*W@X$xXuQuLD&p-1e0pUeoS~Ur|kHiV)eQW(8x$7<8wyk$VgDZ zqOERj-a@!H9kwqh@V$TU<3Ill8uMN)%*goZa{ch|b;NDD!|WR*+8r!iE69~dHj$nZ z>m`y!0zo~I*;!s)_@X6CTf9O-TD+Dn314JdN3b(qF90yMmGYrq>7G6F)F)f!YOSs1 zxid$lkrCmEc8@$0?IOa_$eE+0RBPJUy6O}2)q9TnmEN~a8}NKe9^)NGUX$m)=tTh4 z0%QhyappG|et+g78h770WXEv1d7H+8st?|W4#Ft6(SUMCjt+8G(+V0ufoJ5htMwE71@lw>hD7Wd7Te=bX~gJUoYgJ21NE z@ssN}Z)LAqy2_0E#hVA|T(F<|cW&-(Cm*nQ&y9H$AB#*~%xlY&VrggEcBK>YW zk1?4jpr~p1N4;)3HnvSDDuUq56rzy^L7ZT7tADf%nhs zgJ6rlQ=U?*BPd(a=A*PD;|gSmDu5w?Q{|7Gd4KjJ?*&n$8X00)TwG*ab2B9Z zO?nHED?bZ}oB4temqMnwg(#4E`20$VSNhFv(zf|e;g@h;!fXcJXRYGA%3Lai7F9~g ztA4`jFkdk1nT2g_>zEjG_i!17OkE%8f6thu(|=@G$k+OT#&j&(@+AgVEcw|ZT8wYV z2>9a10d-X+5sZMDtR-~;;QPEd)BH~0DMXX05v17*yeQuxg!U@DS z_Ja_g82*JxFYzpP{YOT+PgY3E{ybcJX(lv8o~A&J>}&Cr&4VTuLnP-YLT8mWyVa6; zStj14Iup*~@m9>=uJaH;hyeaA|N+Zf;AW zmTQJmzFW7QPmdgGL4T_nNu+h>4-kR{{Z?@pn@4-K;X~ufN)& zm#&5}r1FU!Nbj{TK2ZKBzntM+*#-b$Z5$q$7Ti@gwB-ZVfSqhUfmd)p{BZ{BmWa-) zFa0G+lwoZ@_{Nnh#gPyjX}>83O2+m2-NAxj0q~zD*f)1y=ksc0T}6&Xw_~BHTyT+W zo{;My@&z~|J^MjtS_XX28N zPjvpRTmEE+3{|WbY?@7V<9|y&p^NQP@$9#?FtcvT)eBXRP}bulAQVJKNaEH5Ktjj`nt((Aqtsgrl&AMeCJKFe zX~vVYlH(a-b9l6g|E9}vlhS>&wT2tiq^QL$XdP^@~ZTf(l;VDfK#Yrph8R zLYLTwlP5LV%uH6h2=C&|MJ%S6)o`m%?6fYnPZZe8<}of2#Z@H@OWKIiW{I?u;|G8s z5D`$oO>vpo*-Hcp#+vD1@)q9(3&Iz(G!+Pgm~LbUFn*}9=F*F9l1f@Qp*R5&1tkwa z7Tr<-#^{~alDBq$TEiAY8iIu}m!j4{a7G++La#40{|zg+^RmS2=Amkf$Vgq{I-I;g zqchFK4v6p)XA)BbhFUFdNv|Vls5b~?Yxl!j!uo2Tyn$5;1wQ_ke^&3-0*}fLn5MVc z?%Jh@NCzwc0t5uW0ih97(5n^sL}!ja(#bU{Fj}h69j*{c0ErWGB58_J&0;m+S(4RB z6kvH$+t-@L`J0&N7#$>O+Jon!;@6cFo=I%bPl^r&O9|n88gh(ua;q=}Ewar0;lwF^ z!uW_|KqwVLPZB5zcfcr)PHJ2G(l~$ro#YraU83JZN{}i$F|1iA@JVm^XZ5813DGvY z(H2Bjzym$}B&bpV{{wR?l=)ntU5aqKGq70MZC$5Vg>*_1hE0U=t(HxSE;6}Kxh>_O zqFNv*?dzuHR}K66#q2p!fj_W8QEsyKkg}UhKBz3Kly`P0 z8r0MslK7AuYpT;%x+x+5&!#w*GGD|k;1Sv9bS_plS=FiZAzhOAPO2=p)v8gsnT%~# zuA4uiXypjYq#S!JQFWjypVrrn(iYRwjoq|rMzc*SppMS+N-My^`$%2Wku(%!J*Rmxu9qnc2Qk5E* zmBa~xXEgJRBTUQl=FOikKi7yD*!*H*7RHO!NQ?J{ED43{ZAkgKGiHw_&sAD?pUqji zbcvabyBXC%{3Z#!hS*7L6-%1<&`6cJc}{+*T+Z2U>&kPJS09}{+nDoLsVZGjqy=Bh zNoG{+SDWFcbDH_7sYx0Z%g>hQY%nLtjJcJ5CR>9bO=7XtCbc8oXU{&uq={zh#@l+G zWtAD7%NmQD1{CNbEGBfp;%0;&XPVrBOFo12x509EzBA?KHtEZsYO?Hptfu<^tXNX| z!IhDd`xR{d@MiuyU^aV#FDGpSS1)kcu~QQk*1<>r8rhn=Vxa5A$G>evh3SC-<$BM7 zP2^-O^=Vb_EVT)mpKi6IPZJt8$mf4E(qmY(qVxHSCpN-@>i%{m)B@Sshz_?`|7KL$ zYHT&8yG_GPCu*DH;+nOgU+5D}YN{(UHVwLUM4%m=qLjmjQS|B`Qbbm^DD*399dmQ^ z#*M2OjSP&D3aFT!tq4#chtq!kSEJrr>sYJwSg{z1DQ|lFEyO1V*}lUA%-BLI<9DnE z0zyWNA^D-AhZv_;mg5veVF~Or8|uaJg@}+ICOra*iKr3dNPZl9EyUMFgdubcjM|T0 z80skWzkW)oI(ehezhmg|VTY;1=Qe&fPN|fqM87HNLcd>OlB21AQ(9sHI>SANCgeA! zCW(?ng+)oiL}5d6d_4MHnE5;78$KBj#$?ji*5)%Rgoi7L*=E1<1 z+Adzn?CeKg5AUPyujQqh97|#4LN!UfV+ChzCnZ(Ypk39ON$6ZwDXyALFw#ootq9!2 zXUB=$Obvz}wR}hPGeW z{ngCe4gJ4yT5Fh9i?b%DX_@IQ0|UFvig7Tu+9!h8E0HYXx66qhrq%KJizMPM;#A@$ zaU##rlW`d)*AXpAtLO6i&!O`_oW_f|7yX>PHM)@Pd&DmG$1jc@5VJU5vNIE!e@fUN- z3AT%=+@A88V6LD;s%RkbEXVHt*KeE>$baG3-q+vX&h&EZ?eCvt5H)24YlGn?h#ezU zW?WJ!t|+CS=vD%h26i-^$mI!6e@8iOP^XaVP7*1MjWo?>v80XIP3V${oB8ZIJhSk` z6XaBb`m_nybCe(!x+&DWA}dK%rhhYu)Q$41q?Hxn$N5oI_#s^t<3(C4{T1D>gpXFz zgT_Mi1LAXhj}_(AAVMSx%S`gTU2%D4>$Wm_%w& zF{%-`fzbY1YBZdDgdUN&5Ekz^o&f&NFui|r-P5o+PDRBh@noWya%5xwxSN}A^yoMG z`U_7ycYqraD2?yD7zVA|TE&Fi^Lw}9k^v{-RS?n$m*OW1`}=M-#(m}G$NH~5)ImZb ziHc9!T1*amx~{Kgdb(-rr9wE1z}n!M!X5)1L{l9x3qfkw#)~0Wn_GN_p2Ng;gdZm+ zGZt5_AFSH%;^HbO{)j+PDQ+0;M7{>ORM?}Y=hXCEDnv>Myc>aP@g(!mNo!td;_j-g zgOHp^W)wG#cET6%kWWkV)ez%Fwi-d&hydg87Bcx5st%(xjZv~}cnLO6mxk2gBx$lr z&?v_06tI$w*0qfTRl`UtVGQgdugUyIEl3kfRy{)um|!CTQsu9zq*&&FCEGD^qX3H0 zQb7{s5(7xXoD*)>SeYtrnXHl>IB;_xza&!65MyCD4aq11EoemR@F=>RK8R2nh8qc^ z2h!jpA2+?KIpGeY>;JfGIMzG_0CA|g-@3hFP?#&6gYDM+Uc7!ITdD#FF%XC=){0Fv zJ&xoSjq3R9L&hc=Aiu5jXkR0}Du2k&FKTaw#4?S++U+2xFl5!TLfK&0X>JCcW05AS z&~Zw-JFxk1DO{+qqA@ZWSCpnZkDr&UL5!p!BuJ`Ex6zxb@>c6p6ov7l^TEiF_Hen$*HM^@?hE3tHwM#T}ldR=IE4D)aC<9zuYR`{0m_( zkNtzqMUol1kfI{JIVPq@*tj70`xMSbuG*1(VEJ;sy0?cIp^3K)7CWbxr-gGOIUBed z$FZb*3k(ndDL~f05fuO(g$@gSlw+~jIw9W4P+}M$rNX$!sN9FaN*3Z*m+r%JX_u_+L;o;uNuGqWFgyx(VmABK*n>^kBe z^?hKP`;nhhj{FerHaqJcvGWK>p7-VT&MeAf>PRv;vOe7X+pJsos5kDC8NMEtv=Vr$ z1a9K*8-rwoGGk7nsB&ub7+Y$HMgnVK!PU^%m^#hGGEWV_XX&H##G2}6`YuLOO?4vu zW=2SLTLt45dSz=(2;)%Drr}N8M?GE~B3rxa$)mB#VX~)|nx=)#)!xBjqjH1#Y-?qh zQPEZ{c&DA~bR(fS?oSs-+-5i*bY@C5vD7ssr z7$bo=Wc)t|E$E{+SEsLo!T`&+y!db@!(bUVHMfFR8DQm*uVP=paB?t^hd?N_kWj+~ zTqq3Aq0xPU2TwT5e};3&)&XZ$CCr=F+Dyy22d*AaTj|t4tXw9_HRy9!Y0#j%5>Wo& zO?kkV9&CQ$^SOGBRX8bn zxQGTN$-n|g1G*Ze{3=(azhVmx4iIvqXDH?3)^N4#w5yRXEde3e@~#1(vtA?m|4ymD zgdQlV3lDS*B|%WIc;W?o$1JJ5XI6uL$<%g6RV z47IM#a#Rj^mv36jsvTIb&(iA_3*TX2i6A!HkTJTGQ@eHzTG;^{g6%M4`7VT(*U6GA z2Um1=e^1yB&3=Jcm+qYCJVLcxBF24k{pQobojWbl(o#(&nysEB;N1d#CFcd{m2ZSj z#p|$+IrHafCf?8$h~zRq-28x#)eDv@yeJNfN#in6F?13EJKI!sSZAKY{I@HZT}zNq z4E%iI@PA&!VpVNMSVoe_8dzJhhU)*x%{>w<3d6g(6)a;u??+^+>gJ_PLN#DK_=N?p zUY)(P?AbGin@A>6Z$%#G#1rfbla@9-=fon9E=(T&9O%LVs*;AYB|})&v%j9-s#qqU zpK9;->azXDePWk!~j99=ARr&gJ8S%+?c<__gP8 z6R)E~+MwuAV3mSwjtl1#q{idEzG+&iz1c71YBMBMP-1!tQ4&}KT=w1s9K&0E9~t?4 zU5g^vcQJ&!x1xzySg`QM&4*UVwZ`i6e7;)x%^&kJ7s@0$9H04%&56qLy@P|B*;YdV zAGi872+y#UUb((yvo+p{SRt0PV4|?-dR!7SqLj7?yKqF3?Q+Src`N~<#tMlcs$#ZiO@9Ef(Ed>_dME zL9OhYjB1FD$S98pQHAEE=5D^pc8OoQ8?ml37Li04ii7}Lqc;A0c{bh(Tb7%=o?WD$ zl*{Is6N(MBjnFI3gvA*?o|7~x7TBP4SDntxyqc8-lXX3++PR&l+eN%FmOM*g(6vmh zhPcEee3y;tx`iF?uLRLL?$F26|^u0KVIxfTm&!s0ah z1)IcJKy6wq;tjJDT8G7pFEX&$fQ;rqVxX`{4U-m=#k=Qz@l8wj@*Ht$bn4iX_fMYR zYFxE?HOtWiCI4-|iOOA6am>kA@p>97tqo1Cq}AA zGAYMYketx{w%enuTUHZ7 |kbio&%cJKasuFxvxt?VBqV40)Vk7xR3j_cJa6n*_? zqVD|p{=Fy|jQuRVzxecl&DUxk{$S@@f_y-!)y3KO<(1Wqbb3z>07jDI`N5?p9E|hi(mhYk zXl>ky``0-uJPh8|5tcIN&|Vb#BjbW9lP}o)z6n$|tYcgK`Ep>*r?ax5!j|ZDz)f^G(IyMIu~1hCSOp zuNhB9GpFL>L3vat7P5MisL)x9X&1QN z0ZLGJzavj)?t84~czihX^zQe{qx~MSoSgf}n4eCQL}(a{f@BK_)NXKIzmf4|Jh@=a z8}}{+av(-4{*q3CA&(0vYNk6$uuOTBr1O;W#H9Ob9XJNE#6RGd7D_xEUi=dYyzEMu ztyD!4(j4XnVl529IFXJzr6^?sdCCbMl5p}N6rDws3Y>urim{1Xx6I7mJoDp0T{Tx& zAn{qKs+%0?jYlb`lgC_B8YO6Hl!vCt2T?U1Ff<+mE@u^@8#IWdqB*_dEcoQ57{tME z6fSEJ`unOWl_Y21CqfAvYV@E)z!Fr14NBPHu20AqXoT*&&t8V$H1)NH7|!j_m8e0qnIz%OFA<=7KNuwH#Xi@Na{(XE=la| z#-@jJ!rMJEI;FZIbMjs5M) zHn_jzqcJ{)c7kZ7IcG}wYZZz*lBu*^qNtGaXL30{{s^tynD0#AO~@1@89o#zO=et1 zF@Xuh_IFvG0=OdIBNlG?aHl0GIP}-@8bn5_>kvrSLI}Z)L=5x|*;*y&b-Egq$s1khxfT8ep)Wj%A4I^4Q)Pz6-zr>DN%2>Co+K--Fc|HKI3|H6og1J$37|Aj^ zR;>lerLUkKFb9KA>Kk0>6M&4lZ(1l6Wm7#8LpiEiR$1Lcr!T4ot;kEtII2dheuNry zobuAi!ICsd#yGr{_#w5MKm`1XzkAkzs6Fa3;KJPW zB?$cQdc>B7bL*~5>-^`A9qr4@cp&fC>&(_Nf41>Ilb@<}H_hd%Ousw0Xk`y_L&*Neh>kLSR)1wh&lc*4%r>BQ?qXoeI^xTS)KI z*}4|p+0Z>;PF&K^*ul7;=a~O7(J^g@MQZANQ*q9+V(ocno#o~sJx67-chlN#4%nI% zD+lr=-96WBmnPeTy0Wa*zDu&5=YGAXdxHh(;>O1POaPOE(nF|QVQkXv``x0%jH7H8 z`b;Q0LWwz`M2pB$d8{;zu~i7%D8@ry0kK=Ue=iUqGUAH-J}As6ET}^4-?-!ryIf4_ z;!7$S_xLZuu}noI@s#s*tGj{Wwjvw%YTEm1uS4UvhmMaU#p<5Ds*R}%m3(vsU$5bN z_d$9V-0G#NaJsPo#63^1dB4LnKex4nS+z&N6U;f80nvfw5N^|SvZ z!MMgU%Jm{qP8gt%7gFagAH>Rk;-L|Ot#0(2+Lf|$6uvQjQ%~qw5Eu&*6Siy5lhuoOAbMMV( z&mKLxbNhDbF73uyp}?k8Ro;Ng7u6jl3j=zjoc_p zs2P62VWZuKn)>B10OJpSfMmMS2oq#oZ$us>?_Js)6z_Fu!>Y=2`~G|W(O|u5x0k0r ze)xy0*y^3-^EU5ymR_@PU9q?SQ>Qkp`6X}F#-l+w$c7KEw$gw6=l`KQd8;?zjH0EP zAD3Sal{Sz_eNyR6E+L0GXG(vXdnrm)M3TzmP}TpY;$A{Rsimdnv$iG_u|^J7GV|`8 zsQ5FOtF%&W-qv6+L_!1WX|SipvNSm1UInca6-bW-XDYAeMK&S$KeBU^W{h=Pw|W%? zmza1pAJEw9U8%ZUY+)J2vB zv23KWc?3o(51J;*vFWt!2BU%2i>$1htx3L& zwN<;-Eh4vCEwUzAvCdfSnCmo+<10}FLk*PEakE}|a1oNKw9vF6ZEh$C)h0u~*O(lq zQjeQZv&Ks@_1Osq{O{~My=-})E;S+(-Z^K^eYpDnsQMNgy{$nPQBxBh{^b2tkm%ts zc&Y4QT?OKvI@I4sFGta9ub!x^@>TuvdwfG#j-Tqk|G~H&_+qQ{xRc7?*ePPTTo>x< zm6CV@w`u4XN<>hYiLkg>s~o~&nq|_X3wt!Vn)c-{!XF;%m1 z)*@LO3L79c%~^yqxB9g_g)|FQZ6Q}249l50IOg!(hj(}GP&Z}gP!>YH2V~7$b`AIa z3$|~C&GYB?w+-eX|26}u|Np&1!)HF=@E|QnE+h`90`Hx`uz0e0?b@B6O%fs_+~xzv zOVzx6e++hXu6xSjBD#c%Bp2OM!Gb`55};%bO1b!W$2A zbKgXY#p6%@|E2GfM|G**L+U~s`B_Tl4MSWWNtRcH(*KDhMQxh1MJm}$0`^n9lG$(H zbSzJ|dXJ$&NzK_z%Im7UJXwHjSz-A4t#1@Um9XHuM?8{UZt<&&#K)v8Uf)zWpU;wy-w)4KUm2E3;2+}e=R7AeHka59$Thp9^8TR75{WzWtLS~*Dq5L6y@u&k`TNe7j@dWX4_e!!5GRVRUie$ZYMr_Q z_;fmw0T933igrU1SrNG(2C8gTxU47+{NVs0Arwxv5;_+XUOkzseUy~(<`5e zCBhM5w7EcCh48^rKEA_R2J<~@0RN>0G)6b#WE(B1?nw`-wWWz#EQ7>5(Fx|w+d}K=W!+2EF1(1Q_*c6wf~07ppr#{zG(6VWrY6 z=J{<7d+>M!0|X}s;ui#Z9;gbd80mJ;}KaJkh%!XY^t7 zPF}}}zJu&ouA&JAH`$0A_Q!U8`1CA&ccg!_AG@AC^y8nhyKLpca;tKDTnUbrS9o}z zBOg3$f_pQ-;Kw8JSmln$LkqoeyTGY#Q+5q;?7*pn4xbAXb{1pv?SogvNO9u8nuRAa zD*kP(=?^+@12gf0H;mkk7ObW{7KB@3p5Ny#FJCnj3BUzO z_*WC4K6_X0=d8C|cIS>he!+s}4#8*^=f=}$8BWBe2L@HPlz;znZ*x*fUS_iJbP}0;h8jPtff4>edk)#!*3l}`#54V`U6U+RyFnSA zC{|6q;X_JFqUjONfn$6A=Dt@EoS*MGwuQZonV6D7xRNSM@VB?38 zk+-v^N_=IgGchE`D~D+1+Y3F~7P%Dp3-VI4vm+ynO$}YGJn{2l-k$n~AqEhC1W{Y# zj2~$WsB%kQ40ZhTAYfqa+qYMA3T(;O(dg)156%z>{JYuNhbm05@@sDH!$}S?{@?8E z_uuBgrfx2Om3K5cddAr4=fZ7)zz=WV&eoe#)t<6{os-xU=Cx59)r=bs_qswl4fKZ8}~<{GY-WS-T`&Q{l3 z;q4gTi9DAdqwP~d)U2FWMrVxjyGZm|HE-S;mQv*5wE$Ect&4Zh%xoi*hi(C~yp4C} zzG?ys%6o6u{o}C9;i=)FtU)=wZ@mY0{Izqv^sW3ze?$^NTLdL184xhA6t4p(O$9$O zWKD>S?Pso{0AjLTDwfV*ktR5iRZfhlMwCGXkUUTq58|nrp1Cxkey@AYyCa%0#wh-{4^2 zNj6}|vX^J3vzbc+>pVu4ea%(%`!DR_cO>pJmVX}bh#K%i4&MIP+6P-JaYvHfJr znJQI}M6o?jTj$0vwwefxws2GfD(-n%`3(|hSz&OjwJL|`*kf%ZR9QV>Ux<4T(nsFC z>rYW$+9RaNo(B&U3s;APu91nZ%FDe|I(%D*j*@t>qIbvOIJf3eb$o^iALRAsN%p3s zHooFm;ZVhXm`d=yOIzjl_LWyh zXi~L+J)SGRpESa(_5q9Ycj~gbie(I1*$@>s8E&*1Foei z0IEKHWNWegdzPwwU_s4(@32^@ULI?irFA$m<)C#W6e~uCa5qs0AweD-T(fJv8qpKF z2s_AD$Vo~D)~nzIe1$~>m!W!y_Y^W3Qaid!1M9@GYzV$OR}h1W+6aS6xIQ1xfJ}r4 z0jHzZXrOxzo*`-gCW$b$(mb8UQd;a|X#p-w!az_FISt*aMnkUS34#m-OhTf?B(Kw1 zFBPm+NIFRp$6`DYsseQ(7(MBS#`@eN#hhq}-LJ7BslDo41%=u&=BmhzoN+ENkV`)ke|IpSJCUf_;iS-BB^O6%# zCnwKiADmc!XxMAePpX$nI=KR`G*a>gv`z)ToQme>OreAW@ZiMqoG8HqI)S^E`~`XqgAe$(x`nPZb@W4g4JmuQUy zdUDM1?yUP`Vd!A`JWF^XzGM@YYUO=F0kg^qr|4@UO$ttr4)$U?Dk~lTg?JrBMI;;l zDv$#&lUeH^)_T@S*zI6}aj)$tUZC1cCLcy+6Uox3SQ5ik^%@8WLSQLvnz79u5SHvY z!t23mwJe+@PeRedl$b55@`^S?e)TpGa4@=ff$G2>8zwE2T1v`Bz5>0$Zim?LMw7pT zOg{Ly^0q#PZ7>S$Mw#-_kt1Iq-U>cv*o*g`a*8-l;!Cv^T+ficR7B8@@4+3k!c_h4 z;*l$mt8o-s1j#q8phY3!Z+1wTbyZy4bE;{ReSuAu6E~W;)GzLV1tw)EjirR8qKMX= z1(p+o-@}VAT{?drTt=A`ILY%VL;jS9cNN)~SOAT;cs>Th3dlFlE6E&?N9_e;{U1yS z2_m`Kvcg@hQoy~q(B*PvrHEz^o>zIy$-|7 zE`=}2C>_S?uWocc-ds@#wkD1{AHidp2eUv)vn>@w9%^W)WY#eoUj<(w6iJi?k^u%x z<5OENzOLf9Z#+(lwI9(#YQdGfyx9?4*TjA&Z0~C&6=r+-cwJk|jk?cdKKLigHB#V8 zl-`wJ?b_8H+bMDX>GG&4vvaSrZ`5XwR+Zdg1mC_I|7&bOsSwcQ_F5W(m_Wr@d4)6^ zW#-o2q)?vHeEY&C8vzH}Ay)Dn(ZiU;9SPwcN5=Yf+n$DnS=tOeR@0U;Hda4A;#pLf z&#>ynp=;G~zp%&Zb{6@NR9VC2*(E*+RrO%8V+uu!P*x^e=BwjwnE)8y8uI^VS@`dt z=Gun!zj}BMof=^=F}c&MQ)hA}=jBV=sN%aOmUowzk0N1>7QaT>bi8Kopz5gkS1nnY zb@TM~C)Wo@mp4EeZp+xyW_`$^hwkQ9-i3w5a5{SE4sq7*vlUTi5 zs~Cq`06o%b_Vp06<~G7};xCJ}2c~>)%aVG9Oy0&zg8q zWO~B+JHN7S0H-Nz{>8~n@W7bqj_niN)FmwLVyW~X$s#iA!6AjLOQ}tsb3BFR=M@%s zjr3M9?jum1GO#rS)xu%6jMtG4KobAx(dqa2|HmIIt-K$J7I;LD^!f(>v}Xvz_g^jc z{rGVq8lM1##l|>wd=0}*NUshz?RZ}iP!y}i*;2|VURPs}C^ja>V%t{tu2P>8pMm|t zg`x$-RnN820Aw$wK$Vg5>j;{c%cBE<3zQ*aZai4=ZmA%|CoA&>LZ4(^kQeunDk?or zER`TFcy686VsMyop$#=!HRxsLwu0nWCK3>2}C8spjN7ch`eC&sR_Q?Q(T0*`4oR%U3O1b;%3Nv9$giVvq1S z5|iiLY;TJiA8%qZw~miTwY8rPpA*XP4N;X}tw2Wv?&zMqXU$0&4hK(MV(}yKHJMBQ z{llNL{V5w7Q2?J`F!55A&#g-uX|;_|)XzHBCv32^1BeKxkH$QH&0BkMN@?16gFxo) zo4dC>bZfsTWs)wyK)Ck;4iXr~2UQqN!1krcviP?3`zW8hm*Kb&{rnt2fHwR2=J6Vv zM_amfnGIn(B=JjQk8-%vxk?jM?b6xB+q^lf zwe^6Ti@U7!ka1Uam$h{uG4Ww@la}`J&r=sUnhd+(%Dl)*qE%yc(wd}iMvcb3qVlAz z`>hu1ZYM0(+$}37*sN-I*{^YVt9_hxt1Vm^x;C#Ssr9D+98b080zf%`}yM+WAz8dBNhP=%Y5#R$F<-d zY@7Do&}bZ!NHwI|oE-D*aZJ=|ZY(;rmQ-W$G(X-j3SgE@i{Zx#YH9?r{Ft;f2RC@Y z5XKiIjWsUm_35Hir&Q+^E1Xl+Ce}F>Dh!*~cV(sx-`2M4dV9ae6v+b;PfUAr7d&|I ziRbG2L%%+QE2F@d{n28Folgo4k!AAJKr_pbg=v;62`UcTD@i_8t0r88S8Ct`UgI?W zu9)yV%~=yrr`=)0z~mXt^S3RJFkUOSn6@at1|p0=UOV5jzHY|Ea`AEt4-x z%$0w`uG)5uZMB_8(RjDE9%(aq8^y!2jl4$7Vijdp%i`qEFuNTBny7^V4L(Y@Lr^0> z0D%#_R@HCQ-Z03`<&N=ooBlSc7v6}M5}a`D)HdMv>AROLy0O@p77^rPBryu8n1)9( zCKov_b|M_h?aN*FiX5hrZ6O2ss0^J%&YPNMoEN_J!|~|3q*zj-V9X(P{Uv~!bP69Ry}rKBt6rvbQ8|D5oU_g@ z!#)#KGZlw5fXzrgTrht`Jy)~AZn@fhqjyZbFWryf^?m7sojh{tSd@+J=VJf>BsLi@ zbaWK@ojj#<(lW}k1@kN(CFo5(&kyTjXN?hGr|d?7WzH!#=DLxUE0glCGE5$HW26tZ zw}mD!vz9-bPF?62D)75;L**307@EXvR#^niPBitrGZqtr+2uttt9sQ49uKV^bouee zerkXF(7>@{Px5%aOA~~N!p4+>{P@d`Gi)@L^c@_$vv==+Rr|f4YF158U(4qCECC6E zL_vLGyfGf|q2G2E_=4^*9sQ0;GM5cJ;au&!f2-vudy_BXcJDW&n_OiI?_9oU)Ospsv!CS1YRLOsH&L z`U$9)wJ+T@<9-u?LQ|u0p4MPeCutG@a7z;siAxh;7a%sVk;!iWc)X5v><0$}Bo!is zXOhq(isDCwALw~2pg3W5B*i*K!#6+zLWz-t7*&*rE^tyQm=#5T^AE=$;1aj*axma3 zgdSle=^_=u>qd@@6ZsKv00%DMe}NPWmWZWTPf^U~U3=bS5dEY!EqMHw0;SIOPs_?Y zm7Sf1=K>6IHi^=y*kr{(!D-PudMZ65c~Oh5u)tdwI)woR!>!vhGcu>MvvToafrdz; z+cJe?9an5iVOo@(eS)NaI$^XJiA9kGUMc`t8Q=&=Gd1K9+abP-bC;>K6FWdgI+z0K zAOjQbz@@9jo=O^XX(lxvvX>?p!yc z^!BnZw``})+9^tOOlodUa!eE%{qqjlyFKJ)y@$(2bw-BT%nJMt)Wtc3IfO1;7|Kdq z+*foU;y@Q9j+HXNBGwRNPU!CD=e3N9-`+9~IWXt&a2czdMK0qq6W-eIyCZGvjcjuR z`KL)7JSf*fh`dVz@=K;Uc!)&YYTFH%^WYyfUgtrX*g@o45|BS=l!J#X3yJMQgD|eP zHm*~$vmc_6h)`XiY*yGhl^x^d z1&az@W%KRUzn06U+i&0QLC#605gJ9Y!kVT0SnW#76zY#p?b0}*L6D6fc4J?Zoji7G zMTVWMZ0RDc@ZVQ!K`9}+f3I`GnX`e(Zhi3n^J!^5iJw1Xn1dTUlF#q&b4Yd#DtMja z8wSDL3=R&|-%vnK#4$Igrk-DNB|7R0XkCI}^oAjQ89!j2I3M4MyD?E3EsdeApiC*w zK0jU+QDdlwdY2eYGYft*s)^>_}5DCKYj zB#9W4SjoX5BMb5(CdoAbjCw8q{LICL0hpO_)x?F{d{r&VaZ$OdSkcJOzlAxCj(U_< zrr`$cBR$Q&y3X#WQZ8wpS3Moyz@DUX0Kc(*mTfa8v~ha6h_%AvWh-DU1J|io~3nWy+8Ftih6JL?Ru@Go2ikJ zq|nY`KjvvEInj4RIqMb48#uq?v>e#CVqTo&&^8_*LWVuofQSF#yXWy$04W}II$HqO zBporzuh5GZEX18P&w1$#)~{TTld2t8C?;PP3bbsWOMSG_}Wk!O25 zAYkwuMOj`K3&tm_PS+fay06vBlRx0)QjN`;IY-g7!9A@Mh*=||5T?GE!Aj^y7TAHSwZzRw07?7o51ob#~Jmj zR_SxgbC-*h?vp#B;o#ue*~W4tdeI7ZtslJ7?EjGayzXw*AS9m zmR$h*Ph0Dsm*>Aa68yWqIL|1e+C43K^JFfPJOM@f$fTXppfte4$=ZHEI6N2TK;QqE z6UcbLu?d`4b&<$B1!zK&6H&+pDwa7Ka&*R3tLTk%jFAkec<`V+;LDeK#`(j!dabwU zT&4Sx6>xM#`P*5akSKUZzq{727cV*iY8lwrO;V1vLF zx+h9A{f|xnbT+L9VC%moR>e|5tDuvq(aFI$B0vy=kikr`vIC2$65yOf5}}4lg*cET?Z9A( zBBD5NJwzaqkoi$Db$}wRXo5eztZ+5)`H%57vD~ef7ahFXENik_H2(Jww;xp3n>RP* zy;5ZAZ59%q|MVZ23wW%cup=xi%bH*s;X+80O0__Xw5gEL#A-6Z+_H13Px|}23ktRt z6xwa+$=Np)zpnXRQfM4Q(-e$~8wOO8iC>KDT*o(WQWw-RrxnT@*!&JY?({@E$ot33 zzKv`40bFX~!jxa{e#PTW>9^~<+qwa`+Jl)goz`3C7kr})`R@!4gB~6^@+XuN7M5#m z!_-Zcc)~Jy!QqfP&K4(4{@m^@S!2T?>sqdb`FZEqI}!`ccJ}7dwXg!6l)5U^yVoqS z)HRrPWt5G9HQh1}Nx^7F+Inr-y|rr!e}6>-itG1}<3DbE2*c(A%>5#04F$O8P8RYM zEIx?!NDP6G%S`Oon9Vq}Uwwc=KBiJP1d0d!k}UPaI3go*Gwvdw_+xB3Tc3iFee$5e ztmg1R^*Dw6l&oE5k6?kXfPrQBZ6qX9NoM@!J5$ZTvmeh(j14GE6U6dk7cH72g0!?) z4{E|+FHjmQ1|micFAiDS%~nxTb%!zTRbvB(8q-m$cNbnVlB^73m!9CEXMXIhODukN z$mk**!Vw6k$qx=41;>=SvB+r#ytQ&|tS%(G(qr9VgEpRm4%H##90_9;ETPAQ|GaoljQ zk6#dR*GiPrD3E)OI#qG%rdjwOX1i8^#+l&{z2OKEb`osKgrLSj%1tl7I`<=rs@6 z^+=TRhDbIx6M%$RBA%NQEoC8NsGm(9d`;<&^zyI98DLkVEPE(9w_QeET&{YG%k-}P zO5yBb_p$pqpy4$)iTkH{N6eqx9Nri6fNrxZ*#qpUK4KopB6Se2B*kqLopEk4PQk_k z(k#-F7yw}p{!JVUH@X@c3djq|OJXFFvW%_KcC*DPgE*dO_eFix9kKmLUV%;AP$X&y$NKyxJ5wU}0%gHht7+HEsq`%uSKM#!Ro{D0bJ$!Tw z_YpU?UWOTqvN^tC1eJ}FRjD*;?j*&NcJk3uNqvdl5r<=<7k#(Mk$1#F+KPAC+c~Z6`ITyg*z+wCSSP5ktB{RZYZMr{0 z0Q3xH)|x1-a6mAWV3%&VWs=inm)o$;vvYQdNt%rLuKQEO=91_BK>i$QAlz%h2NB)ds`MvNAp`N9%4lXHYK^ zaZ4Bv85P|mO0ifbCibb- z?3PqJrFiN(~QyZSq@DdMml-dx)#d*LqHc=>F3Njc2 z4Eg{gg8?~~YXKw6;8u_y%+K)^7Sb88q7NXu`b?a)&+ge;yy(F>v_!5Y!|Qch(*6dk z6zGksw;YZ?YgaIb3a%be2Ic753(5!!p{-?z!e zaOoi#j%676_;4q-VT0|)RY-k9mxIF<4fV*XjkeJ*B&>65K415tWc zOQojbcnF_wV>UvG#YT+f{3qqmkIcmI@Mo>tB`X5FH~uZYvSz;bxpPyG$d7kUPRQpk zKMs>NwoA6PJr7R~Wj=E56UA3VQx59~>=nYfu<y&Ul%DU!2n;yBmBgtJSiCMTX<`B{?#TRr#Jo@;k+v@wjxJBXBBb9HSG!1#a zx=!oH?+Yl-Vt&Fg#TG%60ZrKGeToJiRe6!m_0Hzr|W*?6#mk&-QbyiSZ{%x~Qii{0(fS-|1l zkN*XUU$8*;>7tLKXm=9u8riSROatO~Gr41&Bc2Z_gBLJO#}$h2W?o8N(QNmEyV~mx z7;%s4TptT%5_lV|tmZ*7bEQ!dzxk-ia?*}2SobFOpHfo=cy${WNtq=wyS+3AkR|jF$RV(yq7NJyx?!3hceN{E)`1EP*;v!-$rj}dfb?*PmMr~tVq2tyL z4SWwe8HN;T0eA%)pCsEn>#9r*ddBJVhF5(*+i@el8xYNX`WSW@l@=dF_P!hHcdrRn_LzJGRgLW*v{s z((Wh|E0tm|0Aj|gn$UMPl+}9uBVC$2Q?9$JS1qIzrac2v?vw7c!t?0W)XrzbN6t0* zL`U%cXrYSBRUryyN{i&my;qoj_eRkm@07`tY=kzzY+}sP?VnhN&E_GK_gVH91LpH% z8z*CJEIzjQMtf(9Lbmm@{%d;iJX$aHZgLlW8qE*}N%-Vk-gMYt_Egp0W_ zmyt?K{l$%vwMAq;DwrzK#CWw1y%tlodgwFEW!-MhIut&i6mBnZZkbaS4%2GcIK zQYx2NwPKS-%_wdr{^4b#+u|j2z~uIgK5Mbj?efETaH+ZVzK`>CWXGf@&;rBn-{Ge_ zSAGa)(DpgeU2*!RM^Ci0vQwPoarXjjIMJ@La|(&=Pk2$AnQ41iVr6ZhaAVhTI)Ni> ztaV}7^Hc|uY^(p%2}u@65i9!0l&3Y|hhqXiSx)%J;nV6vCQQ=-k&Ab)98Kvga}6NT zkV-j+19;gk10jc$XE?0K)VO`e1CYSJuI3Dd+^6Jjt2=8rPSTp>5gw@^30yY(_F;8( zbv{8N$pU+qyWe+rvsgFxBoKb(YIp=$9sAgQdhWo$sR+Lk+MwZ+!@cb68~q|q;qK?2 z?*6pznrB3~=e2#G_@I^)(@Mcr)6K$y2QuYlgJx#h1qkin+VXOpnOgboW2&q!&D?9+omF+f#nnBkw7;_1n^w7o zm@{%W-sk7zn6d(>5b$;2ejQ4;#LW9SiSh@{-AzjOb2+*%m4unuoB3jO@mrr)jZ zeYq+>retBXvV^c0eH>2X!ZRQ_e)ju9@#*3_FtkDIJfs0xDL?_R)JzQ+3K1=I1wSsN zZ!)^yo+l6OtfzAy;##`4qQk?Em)e)S)xo?%3*4F7V>(&U*k?Cvy#7qBjtZ15ED8zs zzX~hU(v&y?Y+#%UM=CS@rE`lmr4C)K{-tIL4tz|@V3qR@*E)L*kA3>YF|#hy>B(c6 z@?CITh3EvALl?ES1-Ds-{lUu!FnqwwD?jVm%{?!uc6j9vQEuYo|MX?q`*?MA-qmPz zj)~P}FMj9vJ|bc0w8N9u$#<66cLXh1nY5%kiSdnnLRhGbQG+sK4Kd$_hf!uU%}fsu z$4jJXMmk8XsF-nI&4?wU_rJd_yqd?1n)&l{gnQ_Tev6aNzj)taKe24<#U30BB}Xr0 z^WI%Ahe<9)i)+uWInOpA4Z6qyJZ?{f&X-feU-|p|Puu%kL*M`P8EwT|%c1Zf3KH zP#b%}E*?JpkY|0Fl<@TtIdJyMIy7(E;d5zGPUL(60L(7;qz#m{!P_p^Tbw%U0MG~M zO>4H!A!`X^ocX{yV6S-q+sM_*1~LT8kYCL#Y}xiqS&ipJXwk0-7Y%kK9=DiWE#_hfYbduj{ql0 z_LrEPHvuyO%>B;H&yE=csq9%Nlt7~Yc}g;z$Uaca!Vu|qujB)aT>Y0R-pMxU%+3y( zV(D&CSr$*xQ(Yw4seA))8M8u%?2(PL-XpigGVE6|Y8cA?PcA%wXz7W0M%`S?W z!QRcPz)_kx((XqWLx4@2@BH7ZZ#L#PE1g4U<;Pd-`asHldH(Y0-AVyi5fH{-mn;a9 z0{Ca+Ap@7ZaU948H?iihWjliC#lQ3?(Cq7>94}xtAT4wN?(M= z?1Tnm*Y4VKZ)S0O_WQ6INxE%XXN`YMqWN#hyZiGjIXwdx5piAtWucQW=FIO8veq2A zo`!xe52gut+l6 zDGU7$ag*iYHWXZR#Lh@2>}{`MOk5LoSGb{kv0Pk-Gd{&9xPdNc3TkJ-By_gQ$xXU1 zU!UzpR^TR05$i((@vXEGMmg&vjlRUAc1SP$r*HU>HY~19&JU29r%aX#MRMa%wnY(; zoCXr | bwi-sticky-note | secure note item type | | | bwi-users | user group | | | bwi-vault | general vault | +| | bwi-vault-f | general vault | ## Actions @@ -68,8 +69,10 @@ or an options menu icon. | | bwi-minus-square | unselect all action | | | bwi-paste | paste from clipboard action | | | bwi-pencil-square | edit action | +| | bwi-popout | popout action | | | bwi-play | start or play action | | | bwi-plus | new or add option in contained buttons/links | +| | bwi-plus-f | new or add option in contained buttons/links | | | bwi-plus-circle | new or add option in text buttons/links | | | bwi-plus-square | - | | | bwi-refresh | "re"-action; such as refresh or regenerate | @@ -101,6 +104,7 @@ or an options menu icon. | | bwi-arrow-circle-left | - | | | bwi-arrow-circle-right | - | | | bwi-arrow-circle-up | - | +| | bwi-back | back arrow | | | bwi-caret-down | table sort order | | | bwi-caret-right | - | | | bwi-caret-up | table sort order | @@ -128,6 +132,7 @@ or an options menu icon. | | bwi-bolt | deprecated "danger" icon | | | bwi-bookmark | bookmark or save related actions | | | bwi-browser | web browser | +| | bwi-browser-alt | web browser | | | bwi-bug | test or debug action | | | bwi-camera | actions related to camera use | | | bwi-chain-broken | unlink action | @@ -138,6 +143,7 @@ or an options menu icon. | | bwi-cut | cut or omit actions | | | bwi-dashboard | statuses or dashboard views | | | bwi-desktop | desktop client | +| | bwi-desktop-alt | desktop client | | | bwi-dollar | account credit | | | bwi-file | file related objects or actions | | | bwi-file-pdf | PDF related object or actions | @@ -153,7 +159,9 @@ or an options menu icon. | | bwi-lightbulb | - | | | bwi-link | link action | | | bwi-mobile | mobile client | +| | bwi-mobile-alt | mobile client | | | bwi-money | - | +| | bwi-msp | - | | | bwi-paperclip | attachments | | | bwi-passkey | passkey | | | bwi-pencil | editing | @@ -176,6 +184,8 @@ or an options menu icon. | | bwi-user | relates to current user or organization member | | | bwi-user-circle | - | | | bwi-user-f | - | +| | bwi-user-monitor | - | +| | bwi-wand | - | | | bwi-wireless | - | | | bwi-wrench | tools or additional configuration options | @@ -203,4 +213,5 @@ or an options menu icon. | | bwi-twitch | link to our Twitch page | | | bwi-twitter | link to our twitter page | | | bwi-windows | support for windows | +| | bwi-x-twitter | x version of twitter | | | bwi-youtube | link to our youtube page | From 91f1d9fb86142805d0774182c3ee6234e13946e3 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:44:24 -0400 Subject: [PATCH 55/86] Auth/PM-6689 - Migrate Security Stamp to Token Service and State Provider (#8792) * PM-6689 - Add security stamp to Token state * PM-6689 - Remove Security Stamp from account and state service * PM-6689 - Add security stamp get and set to token service + abstraction + tests * PM-6689 - Add migration for security stamp, test it, and register it with migrator * PM-6689 - Update sync service + deps to use token service. * PM-6689 - Cleanup missed usages of account tokens which has been removed. * PM-6689 - Per PR feedback, remove unnecessary data migration as the security stamp is only in memory and doesn't need to be migrated. --- .../browser/src/background/main.background.ts | 1 + apps/cli/src/bw.ts | 1 + .../src/services/jslib-services.module.ts | 1 + .../login-strategies/login.strategy.spec.ts | 4 - .../common/login-strategies/login.strategy.ts | 9 +-- .../src/auth/abstractions/token.service.ts | 6 ++ .../src/auth/services/token.service.spec.ts | 79 +++++++++++++++++++ .../common/src/auth/services/token.service.ts | 25 ++++++ .../src/auth/services/token.state.spec.ts | 2 + libs/common/src/auth/services/token.state.ts | 5 ++ .../platform/abstractions/state.service.ts | 2 - .../models/domain/account-tokens.spec.ts | 9 --- .../platform/models/domain/account.spec.ts | 4 +- .../src/platform/models/domain/account.ts | 18 ----- .../src/platform/services/state.service.ts | 17 ---- .../src/vault/services/sync/sync.service.ts | 6 +- 16 files changed, 126 insertions(+), 63 deletions(-) delete mode 100644 libs/common/src/platform/models/domain/account-tokens.spec.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 622a115067..7d3471e598 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -788,6 +788,7 @@ export default class MainBackground { this.avatarService, logoutCallback, this.billingAccountProfileStateService, + this.tokenService, ); this.eventUploadService = new EventUploadService( this.apiService, diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index e784997d82..c3c4042adf 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -633,6 +633,7 @@ export class Main { this.avatarService, async (expired: boolean) => await this.logout(), this.billingAccountProfileStateService, + this.tokenService, ); this.totpService = new TotpService(this.cryptoFunctionService, this.logService); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 204ff5a294..9d311d34af 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -628,6 +628,7 @@ const safeProviders: SafeProvider[] = [ AvatarServiceAbstraction, LOGOUT_CALLBACK, BillingAccountProfileStateService, + TokenServiceAbstraction, ], }), safeProvider({ diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index 431f736e94..e0833342ce 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -27,7 +27,6 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Account, AccountProfile, - AccountTokens, AccountKeys, } from "@bitwarden/common/platform/models/domain/account"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -213,9 +212,6 @@ describe("LoginStrategy", () => { kdfType: kdf, }, }, - tokens: { - ...new AccountTokens(), - }, keys: new AccountKeys(), }), ); diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index a6dc193183..a73c32e120 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -27,11 +27,7 @@ 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 { - Account, - AccountProfile, - AccountTokens, -} from "@bitwarden/common/platform/models/domain/account"; +import { Account, AccountProfile } from "@bitwarden/common/platform/models/domain/account"; import { UserId } from "@bitwarden/common/types/guid"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; @@ -192,9 +188,6 @@ export abstract class LoginStrategy { kdfType: tokenResponse.kdf, }, }, - tokens: { - ...new AccountTokens(), - }, }), ); diff --git a/libs/common/src/auth/abstractions/token.service.ts b/libs/common/src/auth/abstractions/token.service.ts index 75bb383882..fc3bd317f4 100644 --- a/libs/common/src/auth/abstractions/token.service.ts +++ b/libs/common/src/auth/abstractions/token.service.ts @@ -213,4 +213,10 @@ export abstract class TokenService { * @returns A promise that resolves with a boolean representing the user's external authN status. */ getIsExternal: () => Promise; + + /** Gets the active or passed in user's security stamp */ + getSecurityStamp: (userId?: UserId) => Promise; + + /** Sets the security stamp for the active or passed in user */ + setSecurityStamp: (securityStamp: string, userId?: UserId) => Promise; } diff --git a/libs/common/src/auth/services/token.service.spec.ts b/libs/common/src/auth/services/token.service.spec.ts index d32c4d8e1c..3e92053d2f 100644 --- a/libs/common/src/auth/services/token.service.spec.ts +++ b/libs/common/src/auth/services/token.service.spec.ts @@ -23,6 +23,7 @@ import { EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL, REFRESH_TOKEN_DISK, REFRESH_TOKEN_MEMORY, + SECURITY_STAMP_MEMORY, } from "./token.state"; describe("TokenService", () => { @@ -2191,6 +2192,84 @@ describe("TokenService", () => { }); }); + describe("Security Stamp methods", () => { + const mockSecurityStamp = "securityStamp"; + + describe("setSecurityStamp", () => { + it("should throw an error if no user id is provided and there is no active user in global state", async () => { + // Act + // note: don't await here because we want to test the error + const result = tokenService.setSecurityStamp(mockSecurityStamp); + // Assert + await expect(result).rejects.toThrow("User id not found. Cannot set security stamp."); + }); + + it("should set the security stamp in memory when there is an active user in global state", async () => { + // Arrange + globalStateProvider + .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) + .stateSubject.next(userIdFromAccessToken); + + // Act + await tokenService.setSecurityStamp(mockSecurityStamp); + + // Assert + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY).nextMock, + ).toHaveBeenCalledWith(mockSecurityStamp); + }); + + it("should set the security stamp in memory for the specified user id", async () => { + // Act + await tokenService.setSecurityStamp(mockSecurityStamp, userIdFromAccessToken); + + // Assert + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY).nextMock, + ).toHaveBeenCalledWith(mockSecurityStamp); + }); + }); + + describe("getSecurityStamp", () => { + it("should throw an error if no user id is provided and there is no active user in global state", async () => { + // Act + // note: don't await here because we want to test the error + const result = tokenService.getSecurityStamp(); + // Assert + await expect(result).rejects.toThrow("User id not found. Cannot get security stamp."); + }); + + it("should return the security stamp from memory with no user id specified (uses global active user)", async () => { + // Arrange + globalStateProvider + .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) + .stateSubject.next(userIdFromAccessToken); + + singleUserStateProvider + .getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY) + .stateSubject.next([userIdFromAccessToken, mockSecurityStamp]); + + // Act + const result = await tokenService.getSecurityStamp(); + + // Assert + expect(result).toEqual(mockSecurityStamp); + }); + + it("should return the security stamp from memory for the specified user id", async () => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY) + .stateSubject.next([userIdFromAccessToken, mockSecurityStamp]); + + // Act + const result = await tokenService.getSecurityStamp(userIdFromAccessToken); + // Assert + expect(result).toEqual(mockSecurityStamp); + }); + }); + }); + // Helpers function createTokenService(supportsSecureStorage: boolean) { return new TokenService( diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index c24a2c186b..40036a8453 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -32,6 +32,7 @@ import { EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL, REFRESH_TOKEN_DISK, REFRESH_TOKEN_MEMORY, + SECURITY_STAMP_MEMORY, } from "./token.state"; export enum TokenStorageLocation { @@ -850,6 +851,30 @@ export class TokenService implements TokenServiceAbstraction { return Array.isArray(decoded.amr) && decoded.amr.includes("external"); } + async getSecurityStamp(userId?: UserId): Promise { + userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$); + + if (!userId) { + throw new Error("User id not found. Cannot get security stamp."); + } + + const securityStamp = await this.getStateValueByUserIdAndKeyDef(userId, SECURITY_STAMP_MEMORY); + + return securityStamp; + } + + async setSecurityStamp(securityStamp: string, userId?: UserId): Promise { + userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$); + + if (!userId) { + throw new Error("User id not found. Cannot set security stamp."); + } + + await this.singleUserStateProvider + .get(userId, SECURITY_STAMP_MEMORY) + .update((_) => securityStamp); + } + private async getStateValueByUserIdAndKeyDef( userId: UserId, storageLocation: UserKeyDefinition, diff --git a/libs/common/src/auth/services/token.state.spec.ts b/libs/common/src/auth/services/token.state.spec.ts index dc00fec383..bb82410fac 100644 --- a/libs/common/src/auth/services/token.state.spec.ts +++ b/libs/common/src/auth/services/token.state.spec.ts @@ -10,6 +10,7 @@ import { EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL, REFRESH_TOKEN_DISK, REFRESH_TOKEN_MEMORY, + SECURITY_STAMP_MEMORY, } from "./token.state"; describe.each([ @@ -22,6 +23,7 @@ describe.each([ [API_KEY_CLIENT_ID_MEMORY, "apiKeyClientIdMemory"], [API_KEY_CLIENT_SECRET_DISK, "apiKeyClientSecretDisk"], [API_KEY_CLIENT_SECRET_MEMORY, "apiKeyClientSecretMemory"], + [SECURITY_STAMP_MEMORY, "securityStamp"], ])( "deserializes state key definitions", ( diff --git a/libs/common/src/auth/services/token.state.ts b/libs/common/src/auth/services/token.state.ts index 458d6846c1..57d85f2a55 100644 --- a/libs/common/src/auth/services/token.state.ts +++ b/libs/common/src/auth/services/token.state.ts @@ -69,3 +69,8 @@ export const API_KEY_CLIENT_SECRET_MEMORY = new UserKeyDefinition( clearOn: [], // Manually handled }, ); + +export const SECURITY_STAMP_MEMORY = new UserKeyDefinition(TOKEN_MEMORY, "securityStamp", { + deserializer: (securityStamp) => securityStamp, + clearOn: ["logout"], +}); diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 051604f0ae..f1d4b3848e 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -181,8 +181,6 @@ export abstract class StateService { * Sets the user's Pin, encrypted by the user key */ setProtectedPin: (value: string, options?: StorageOptions) => Promise; - getSecurityStamp: (options?: StorageOptions) => Promise; - setSecurityStamp: (value: string, options?: StorageOptions) => Promise; getUserId: (options?: StorageOptions) => Promise; getVaultTimeout: (options?: StorageOptions) => Promise; setVaultTimeout: (value: number, options?: StorageOptions) => Promise; diff --git a/libs/common/src/platform/models/domain/account-tokens.spec.ts b/libs/common/src/platform/models/domain/account-tokens.spec.ts deleted file mode 100644 index 733b3908e9..0000000000 --- a/libs/common/src/platform/models/domain/account-tokens.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AccountTokens } from "./account"; - -describe("AccountTokens", () => { - describe("fromJSON", () => { - it("should deserialize to an instance of itself", () => { - expect(AccountTokens.fromJSON({})).toBeInstanceOf(AccountTokens); - }); - }); -}); diff --git a/libs/common/src/platform/models/domain/account.spec.ts b/libs/common/src/platform/models/domain/account.spec.ts index 0c76c16cc2..77c242b6ff 100644 --- a/libs/common/src/platform/models/domain/account.spec.ts +++ b/libs/common/src/platform/models/domain/account.spec.ts @@ -1,4 +1,4 @@ -import { Account, AccountKeys, AccountProfile, AccountSettings, AccountTokens } from "./account"; +import { Account, AccountKeys, AccountProfile, AccountSettings } from "./account"; describe("Account", () => { describe("fromJSON", () => { @@ -10,14 +10,12 @@ describe("Account", () => { const keysSpy = jest.spyOn(AccountKeys, "fromJSON"); const profileSpy = jest.spyOn(AccountProfile, "fromJSON"); const settingsSpy = jest.spyOn(AccountSettings, "fromJSON"); - const tokensSpy = jest.spyOn(AccountTokens, "fromJSON"); Account.fromJSON({}); expect(keysSpy).toHaveBeenCalled(); expect(profileSpy).toHaveBeenCalled(); expect(settingsSpy).toHaveBeenCalled(); - expect(tokensSpy).toHaveBeenCalled(); }); }); }); diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index 5a9a764696..cd416ec1f9 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -171,24 +171,11 @@ export class AccountSettings { } } -export class AccountTokens { - securityStamp?: string; - - static fromJSON(obj: Jsonify): AccountTokens { - if (obj == null) { - return null; - } - - return Object.assign(new AccountTokens(), obj); - } -} - export class Account { data?: AccountData = new AccountData(); keys?: AccountKeys = new AccountKeys(); profile?: AccountProfile = new AccountProfile(); settings?: AccountSettings = new AccountSettings(); - tokens?: AccountTokens = new AccountTokens(); constructor(init: Partial) { Object.assign(this, { @@ -208,10 +195,6 @@ export class Account { ...new AccountSettings(), ...init?.settings, }, - tokens: { - ...new AccountTokens(), - ...init?.tokens, - }, }); } @@ -225,7 +208,6 @@ export class Account { data: AccountData.fromJSON(json?.data), profile: AccountProfile.fromJSON(json?.profile), settings: AccountSettings.fromJSON(json?.settings), - tokens: AccountTokens.fromJSON(json?.tokens), }); } } diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index f660cd7a34..d0a55d7a47 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -839,23 +839,6 @@ export class StateService< ); } - async getSecurityStamp(options?: StorageOptions): Promise { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) - )?.tokens?.securityStamp; - } - - async setSecurityStamp(value: string, options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - account.tokens.securityStamp = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - } - async getUserId(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) diff --git a/libs/common/src/vault/services/sync/sync.service.ts b/libs/common/src/vault/services/sync/sync.service.ts index ff8e9f1f4f..73869ff488 100644 --- a/libs/common/src/vault/services/sync/sync.service.ts +++ b/libs/common/src/vault/services/sync/sync.service.ts @@ -15,6 +15,7 @@ import { AccountService } from "../../../auth/abstractions/account.service"; import { AvatarService } from "../../../auth/abstractions/avatar.service"; import { KeyConnectorService } from "../../../auth/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "../../../auth/abstractions/master-password.service.abstraction"; +import { TokenService } from "../../../auth/abstractions/token.service"; import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; import { BillingAccountProfileStateService } from "../../../billing/abstractions/account/billing-account-profile-state.service"; @@ -73,6 +74,7 @@ export class SyncService implements SyncServiceAbstraction { private avatarService: AvatarService, private logoutCallback: (expired: boolean) => Promise, private billingAccountProfileStateService: BillingAccountProfileStateService, + private tokenService: TokenService, ) {} async getLastSync(): Promise { @@ -309,7 +311,7 @@ export class SyncService implements SyncServiceAbstraction { } private async syncProfile(response: ProfileResponse) { - const stamp = await this.stateService.getSecurityStamp(); + const stamp = await this.tokenService.getSecurityStamp(response.id as UserId); if (stamp != null && stamp !== response.securityStamp) { if (this.logoutCallback != null) { await this.logoutCallback(true); @@ -323,7 +325,7 @@ export class SyncService implements SyncServiceAbstraction { await this.cryptoService.setProviderKeys(response.providers); await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations); await this.avatarService.setSyncAvatarColor(response.id as UserId, response.avatarColor); - await this.stateService.setSecurityStamp(response.securityStamp); + await this.tokenService.setSecurityStamp(response.securityStamp, response.id as UserId); await this.stateService.setEmailVerified(response.emailVerified); await this.billingAccountProfileStateService.setHasPremium( From f829cdd8a724dae42ab93aaadf930d433dd2cc4d Mon Sep 17 00:00:00 2001 From: aj-rosado <109146700+aj-rosado@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:18:11 +0100 Subject: [PATCH 56/86] [PM-7603] Fix individual vault export not appearing on Event Logs (#8829) * Added validation to update User_ClientExportedVault on events even with no organization id or cipher id * Fixed missing data and validation --- .../common/src/services/event/event-collection.service.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/common/src/services/event/event-collection.service.ts b/libs/common/src/services/event/event-collection.service.ts index 641c1b4d44..1482bb8b61 100644 --- a/libs/common/src/services/event/event-collection.service.ts +++ b/libs/common/src/services/event/event-collection.service.ts @@ -36,7 +36,7 @@ export class EventCollectionService implements EventCollectionServiceAbstraction const userId = await firstValueFrom(this.stateProvider.activeUserId$); const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION); - if (!(await this.shouldUpdate(cipherId, organizationId))) { + if (!(await this.shouldUpdate(cipherId, organizationId, eventType))) { return; } @@ -64,6 +64,7 @@ export class EventCollectionService implements EventCollectionServiceAbstraction private async shouldUpdate( cipherId: string = null, organizationId: string = null, + eventType: EventType = null, ): Promise { const orgIds$ = this.organizationService.organizations$.pipe( map((orgs) => orgs?.filter((o) => o.useEvents)?.map((x) => x.id) ?? []), @@ -85,6 +86,11 @@ export class EventCollectionService implements EventCollectionServiceAbstraction return false; } + // Individual vault export doesn't need cipher id or organization id. + if (eventType == EventType.User_ClientExportedVault) { + return true; + } + // If the cipher is null there must be an organization id provided if (cipher == null && organizationId == null) { return false; From b5362ca1ce6f4260b3ae52eecc6b0e9ace325945 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 22 Apr 2024 08:55:19 -0400 Subject: [PATCH 57/86] Browser MV3: Default store values to session storage (#8844) * Introduce browser large object storage location. This location is encrypted and serialized to disk in order to allow for storage of uncountable things like vault items that take a significant amount of time to prepare, but are not guaranteed to fit within session storage. however, limit the need to write to disk is a big benefit, so _most_ things are written to storage.session instead, where things specifically flagged as large will be moved to disk-backed memory * Store derived values in large object store for browser * Fix AbstractMemoryStorageService implementation --- .../browser/src/background/main.background.ts | 18 ++++++---- .../derived-state-provider.factory.ts | 10 +++--- .../browser-memory-storage.service.ts | 11 +++++- .../background-derived-state.provider.ts | 9 ++++- .../state/background-derived-state.ts | 2 +- .../state/derived-state-interactions.spec.ts | 28 +++++++++------ .../foreground-derived-state.provider.ts | 9 +++-- .../state/foreground-derived-state.spec.ts | 3 +- .../state/foreground-derived-state.ts | 3 +- .../browser-storage-service.provider.ts | 35 +++++++++++++++++++ .../src/popup/services/services.module.ts | 32 ++++++++++++++++- apps/cli/src/bw.ts | 4 +-- apps/desktop/src/main.ts | 2 +- .../src/services/jslib-services.module.ts | 2 +- libs/common/spec/fake-state-provider.ts | 4 +-- .../src/platform/state/derive-definition.ts | 4 +-- .../default-derived-state.provider.ts | 17 ++++++--- .../default-derived-state.spec.ts | 16 ++++----- .../src/platform/state/state-definition.ts | 4 ++- .../src/platform/state/state-definitions.ts | 16 ++++++--- 20 files changed, 171 insertions(+), 58 deletions(-) create mode 100644 apps/browser/src/platform/storage/browser-storage-service.provider.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 7d3471e598..0a9ad44962 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -111,7 +111,6 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; -import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { SystemService } from "@bitwarden/common/platform/services/system.service"; import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service"; import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; @@ -226,6 +225,7 @@ import { BackgroundPlatformUtilsService } from "../platform/services/platform-ut import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider"; 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 { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; @@ -246,6 +246,8 @@ export default class MainBackground { secureStorageService: AbstractStorageService; memoryStorageService: AbstractMemoryStorageService; memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService; + largeObjectMemoryStorageForStateProviders: AbstractMemoryStorageService & + ObservableStorageService; i18nService: I18nServiceAbstraction; platformUtilsService: PlatformUtilsServiceAbstraction; logService: LogServiceAbstraction; @@ -424,12 +426,16 @@ export default class MainBackground { ? mv3MemoryStorageCreator("stateService") : new MemoryStorageService(); this.memoryStorageForStateProviders = BrowserApi.isManifestVersion(3) - ? mv3MemoryStorageCreator("stateProviders") - : new BackgroundMemoryStorageService(); + ? new BrowserMemoryStorageService() // mv3 stores to storage.session + : new BackgroundMemoryStorageService(); // mv2 stores to memory + this.largeObjectMemoryStorageForStateProviders = BrowserApi.isManifestVersion(3) + ? mv3MemoryStorageCreator("stateProviders") // mv3 stores to local-backed session storage + : this.memoryStorageForStateProviders; // mv2 stores to the same location - const storageServiceProvider = new StorageServiceProvider( + const storageServiceProvider = new BrowserStorageServiceProvider( this.storageService, this.memoryStorageForStateProviders, + this.largeObjectMemoryStorageForStateProviders, ); this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider); @@ -466,9 +472,7 @@ export default class MainBackground { this.accountService, this.singleUserStateProvider, ); - this.derivedStateProvider = new BackgroundDerivedStateProvider( - this.memoryStorageForStateProviders, - ); + this.derivedStateProvider = new BackgroundDerivedStateProvider(storageServiceProvider); this.stateProvider = new DefaultStateProvider( this.activeUserStateProvider, this.singleUserStateProvider, diff --git a/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts b/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts index 4f329c93d5..4025d01950 100644 --- a/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts +++ b/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts @@ -4,14 +4,14 @@ import { BackgroundDerivedStateProvider } from "../../state/background-derived-s import { CachedServices, FactoryOptions, factory } from "./factory-options"; import { - MemoryStorageServiceInitOptions, - observableMemoryStorageServiceFactory, -} from "./storage-service.factory"; + StorageServiceProviderInitOptions, + storageServiceProviderFactory, +} from "./storage-service-provider.factory"; type DerivedStateProviderFactoryOptions = FactoryOptions; export type DerivedStateProviderInitOptions = DerivedStateProviderFactoryOptions & - MemoryStorageServiceInitOptions; + StorageServiceProviderInitOptions; export async function derivedStateProviderFactory( cache: { derivedStateProvider?: DerivedStateProvider } & CachedServices, @@ -22,6 +22,6 @@ export async function derivedStateProviderFactory( "derivedStateProvider", opts, async () => - new BackgroundDerivedStateProvider(await observableMemoryStorageServiceFactory(cache, opts)), + new BackgroundDerivedStateProvider(await storageServiceProviderFactory(cache, opts)), ); } diff --git a/apps/browser/src/platform/services/browser-memory-storage.service.ts b/apps/browser/src/platform/services/browser-memory-storage.service.ts index f824a1df0d..b067dc5a12 100644 --- a/apps/browser/src/platform/services/browser-memory-storage.service.ts +++ b/apps/browser/src/platform/services/browser-memory-storage.service.ts @@ -1,7 +1,16 @@ +import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; + import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service"; -export default class BrowserMemoryStorageService extends AbstractChromeStorageService { +export default class BrowserMemoryStorageService + extends AbstractChromeStorageService + implements AbstractMemoryStorageService +{ constructor() { super(chrome.storage.session); } + type = "MemoryStorageService" as const; + getBypassCache(key: string): Promise { + return this.get(key); + } } diff --git a/apps/browser/src/platform/state/background-derived-state.provider.ts b/apps/browser/src/platform/state/background-derived-state.provider.ts index 95eec71113..f3d217789e 100644 --- a/apps/browser/src/platform/state/background-derived-state.provider.ts +++ b/apps/browser/src/platform/state/background-derived-state.provider.ts @@ -1,5 +1,9 @@ import { Observable } from "rxjs"; +import { + AbstractStorageService, + ObservableStorageService, +} from "@bitwarden/common/platform/abstractions/storage.service"; import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- extending this class for this client import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider"; @@ -12,11 +16,14 @@ export class BackgroundDerivedStateProvider extends DefaultDerivedStateProvider parentState$: Observable, deriveDefinition: DeriveDefinition, dependencies: TDeps, + storageLocation: [string, AbstractStorageService & ObservableStorageService], ): DerivedState { + const [cacheKey, storageService] = storageLocation; return new BackgroundDerivedState( parentState$, deriveDefinition, - this.memoryStorage, + storageService, + cacheKey, dependencies, ); } diff --git a/apps/browser/src/platform/state/background-derived-state.ts b/apps/browser/src/platform/state/background-derived-state.ts index 7a7146aa88..c62795acdc 100644 --- a/apps/browser/src/platform/state/background-derived-state.ts +++ b/apps/browser/src/platform/state/background-derived-state.ts @@ -23,10 +23,10 @@ export class BackgroundDerivedState< parentState$: Observable, deriveDefinition: DeriveDefinition, memoryStorage: AbstractStorageService & ObservableStorageService, + portName: string, dependencies: TDeps, ) { super(parentState$, deriveDefinition, memoryStorage, dependencies); - const portName = deriveDefinition.buildCacheKey(); // listen for foreground derived states to connect BrowserApi.addListener(chrome.runtime.onConnect, (port) => { diff --git a/apps/browser/src/platform/state/derived-state-interactions.spec.ts b/apps/browser/src/platform/state/derived-state-interactions.spec.ts index d709c401af..a5df01bc98 100644 --- a/apps/browser/src/platform/state/derived-state-interactions.spec.ts +++ b/apps/browser/src/platform/state/derived-state-interactions.spec.ts @@ -38,14 +38,21 @@ describe("foreground background derived state interactions", () => { let memoryStorage: FakeStorageService; const initialParent = "2020-01-01"; const ngZone = mock(); + const portName = "testPort"; beforeEach(() => { mockPorts(); parentState$ = new Subject(); memoryStorage = new FakeStorageService(); - background = new BackgroundDerivedState(parentState$, deriveDefinition, memoryStorage, {}); - foreground = new ForegroundDerivedState(deriveDefinition, memoryStorage, ngZone); + background = new BackgroundDerivedState( + parentState$, + deriveDefinition, + memoryStorage, + portName, + {}, + ); + foreground = new ForegroundDerivedState(deriveDefinition, memoryStorage, portName, ngZone); }); afterEach(() => { @@ -65,7 +72,12 @@ describe("foreground background derived state interactions", () => { }); it("should initialize a late-connected foreground", async () => { - const newForeground = new ForegroundDerivedState(deriveDefinition, memoryStorage, ngZone); + const newForeground = new ForegroundDerivedState( + deriveDefinition, + memoryStorage, + portName, + ngZone, + ); const backgroundEmissions = trackEmissions(background.state$); parentState$.next(initialParent); await awaitAsync(); @@ -82,8 +94,6 @@ describe("foreground background derived state interactions", () => { const dateString = "2020-12-12"; const emissions = trackEmissions(background.state$); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises await foreground.forceValue(new Date(dateString)); await awaitAsync(); @@ -99,9 +109,7 @@ describe("foreground background derived state interactions", () => { expect(foreground["port"]).toBeDefined(); const newDate = new Date(); - // 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 - foreground.forceValue(newDate); + await foreground.forceValue(newDate); await awaitAsync(); expect(connectMock.mock.calls.length).toBe(initialConnectCalls); @@ -114,9 +122,7 @@ describe("foreground background derived state interactions", () => { expect(foreground["port"]).toBeUndefined(); const newDate = new Date(); - // 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 - foreground.forceValue(newDate); + await foreground.forceValue(newDate); await awaitAsync(); expect(connectMock.mock.calls.length).toBe(initialConnectCalls + 1); diff --git a/apps/browser/src/platform/state/foreground-derived-state.provider.ts b/apps/browser/src/platform/state/foreground-derived-state.provider.ts index ccefb1157c..d9262e3b6e 100644 --- a/apps/browser/src/platform/state/foreground-derived-state.provider.ts +++ b/apps/browser/src/platform/state/foreground-derived-state.provider.ts @@ -5,6 +5,7 @@ import { AbstractStorageService, ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; +import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- extending this class for this client import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider"; @@ -14,16 +15,18 @@ import { ForegroundDerivedState } from "./foreground-derived-state"; export class ForegroundDerivedStateProvider extends DefaultDerivedStateProvider { constructor( - memoryStorage: AbstractStorageService & ObservableStorageService, + storageServiceProvider: StorageServiceProvider, private ngZone: NgZone, ) { - super(memoryStorage); + super(storageServiceProvider); } override buildDerivedState( _parentState$: Observable, deriveDefinition: DeriveDefinition, _dependencies: TDeps, + storageLocation: [string, AbstractStorageService & ObservableStorageService], ): DerivedState { - return new ForegroundDerivedState(deriveDefinition, this.memoryStorage, this.ngZone); + const [cacheKey, storageService] = storageLocation; + return new ForegroundDerivedState(deriveDefinition, storageService, cacheKey, this.ngZone); } } diff --git a/apps/browser/src/platform/state/foreground-derived-state.spec.ts b/apps/browser/src/platform/state/foreground-derived-state.spec.ts index fce672a5ef..2c29f39bc1 100644 --- a/apps/browser/src/platform/state/foreground-derived-state.spec.ts +++ b/apps/browser/src/platform/state/foreground-derived-state.spec.ts @@ -33,13 +33,14 @@ jest.mock("../browser/run-inside-angular.operator", () => { describe("ForegroundDerivedState", () => { let sut: ForegroundDerivedState; let memoryStorage: FakeStorageService; + const portName = "testPort"; const ngZone = mock(); beforeEach(() => { memoryStorage = new FakeStorageService(); memoryStorage.internalUpdateValuesRequireDeserialization(true); mockPorts(); - sut = new ForegroundDerivedState(deriveDefinition, memoryStorage, ngZone); + sut = new ForegroundDerivedState(deriveDefinition, memoryStorage, portName, ngZone); }); afterEach(() => { diff --git a/apps/browser/src/platform/state/foreground-derived-state.ts b/apps/browser/src/platform/state/foreground-derived-state.ts index b005697be8..b9dda763df 100644 --- a/apps/browser/src/platform/state/foreground-derived-state.ts +++ b/apps/browser/src/platform/state/foreground-derived-state.ts @@ -35,6 +35,7 @@ export class ForegroundDerivedState implements DerivedState { constructor( private deriveDefinition: DeriveDefinition, private memoryStorage: AbstractStorageService & ObservableStorageService, + private portName: string, private ngZone: NgZone, ) { this.storageKey = deriveDefinition.storageKey; @@ -88,7 +89,7 @@ export class ForegroundDerivedState implements DerivedState { return; } - this.port = chrome.runtime.connect({ name: this.deriveDefinition.buildCacheKey() }); + this.port = chrome.runtime.connect({ name: this.portName }); this.backgroundResponses$ = fromChromeEvent(this.port.onMessage).pipe( map(([message]) => message as DerivedStateMessage), diff --git a/apps/browser/src/platform/storage/browser-storage-service.provider.ts b/apps/browser/src/platform/storage/browser-storage-service.provider.ts new file mode 100644 index 0000000000..e0214baef4 --- /dev/null +++ b/apps/browser/src/platform/storage/browser-storage-service.provider.ts @@ -0,0 +1,35 @@ +import { + AbstractStorageService, + ObservableStorageService, +} from "@bitwarden/common/platform/abstractions/storage.service"; +import { + PossibleLocation, + StorageServiceProvider, +} from "@bitwarden/common/platform/services/storage-service.provider"; +// eslint-disable-next-line import/no-restricted-paths +import { ClientLocations } from "@bitwarden/common/platform/state/state-definition"; + +export class BrowserStorageServiceProvider extends StorageServiceProvider { + constructor( + diskStorageService: AbstractStorageService & ObservableStorageService, + limitedMemoryStorageService: AbstractStorageService & ObservableStorageService, + private largeObjectMemoryStorageService: AbstractStorageService & ObservableStorageService, + ) { + super(diskStorageService, limitedMemoryStorageService); + } + + override get( + defaultLocation: PossibleLocation, + overrides: Partial, + ): [location: PossibleLocation, service: AbstractStorageService & ObservableStorageService] { + const location = overrides["browser"] ?? defaultLocation; + switch (location) { + case "memory-large-object": + return ["memory-large-object", this.largeObjectMemoryStorageService]; + default: + // Pass in computed location to super because they could have + // override default "disk" with web "memory". + return super.get(location, overrides); + } + } +} diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 123e901e4e..a7da6b7612 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -71,6 +71,7 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; +import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; import { DerivedStateProvider, @@ -108,6 +109,7 @@ import { DefaultBrowserStateService } from "../../platform/services/default-brow import I18nService from "../../platform/services/i18n.service"; import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service"; import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider"; +import { BrowserStorageServiceProvider } from "../../platform/storage/browser-storage-service.provider"; import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging"; import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service"; @@ -120,6 +122,10 @@ import { InitService } from "./init.service"; import { PopupCloseWarningService } from "./popup-close-warning.service"; import { PopupSearchService } from "./popup-search.service"; +const OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE = new SafeInjectionToken< + AbstractStorageService & ObservableStorageService +>("OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE"); + const needsBackgroundInit = BrowserPopupUtils.backgroundInitializationRequired(); const isPrivateMode = BrowserPopupUtils.inPrivateMode(); const mainBackground: MainBackground = needsBackgroundInit @@ -380,6 +386,21 @@ const safeProviders: SafeProvider[] = [ }, deps: [], }), + safeProvider({ + provide: OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE, + useFactory: ( + regularMemoryStorageService: AbstractMemoryStorageService & ObservableStorageService, + ) => { + if (BrowserApi.isManifestVersion(2)) { + return regularMemoryStorageService; + } + + return getBgService( + "largeObjectMemoryStorageForStateProviders", + )(); + }, + deps: [OBSERVABLE_MEMORY_STORAGE], + }), safeProvider({ provide: OBSERVABLE_DISK_STORAGE, useExisting: AbstractStorageService, @@ -466,7 +487,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: DerivedStateProvider, useClass: ForegroundDerivedStateProvider, - deps: [OBSERVABLE_MEMORY_STORAGE, NgZone], + deps: [StorageServiceProvider, NgZone], }), safeProvider({ provide: AutofillSettingsServiceAbstraction, @@ -542,6 +563,15 @@ const safeProviders: SafeProvider[] = [ }, deps: [], }), + safeProvider({ + provide: StorageServiceProvider, + useClass: BrowserStorageServiceProvider, + deps: [ + OBSERVABLE_DISK_STORAGE, + OBSERVABLE_MEMORY_STORAGE, + OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE, + ], + }), ]; @NgModule({ diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index c3c4042adf..437f807bc6 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -309,9 +309,7 @@ export class Main { this.singleUserStateProvider, ); - this.derivedStateProvider = new DefaultDerivedStateProvider( - this.memoryStorageForStateProviders, - ); + this.derivedStateProvider = new DefaultDerivedStateProvider(storageServiceProvider); this.stateProvider = new DefaultStateProvider( this.activeUserStateProvider, diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 0655e5600d..bffd2002ff 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -157,7 +157,7 @@ export class Main { activeUserStateProvider, singleUserStateProvider, globalStateProvider, - new DefaultDerivedStateProvider(this.memoryStorageForStateProviders), + new DefaultDerivedStateProvider(storageServiceProvider), ); this.environmentService = new DefaultEnvironmentService(stateProvider, accountService); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 9d311d34af..27b182de5d 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1047,7 +1047,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: DerivedStateProvider, useClass: DefaultDerivedStateProvider, - deps: [OBSERVABLE_MEMORY_STORAGE], + deps: [StorageServiceProvider], }), safeProvider({ provide: StateProvider, diff --git a/libs/common/spec/fake-state-provider.ts b/libs/common/spec/fake-state-provider.ts index 2078fe3abd..306ae00c21 100644 --- a/libs/common/spec/fake-state-provider.ts +++ b/libs/common/spec/fake-state-provider.ts @@ -249,11 +249,11 @@ export class FakeDerivedStateProvider implements DerivedStateProvider { deriveDefinition: DeriveDefinition, dependencies: TDeps, ): DerivedState { - let result = this.states.get(deriveDefinition.buildCacheKey()) as DerivedState; + let result = this.states.get(deriveDefinition.buildCacheKey("memory")) as DerivedState; if (result == null) { result = new FakeDerivedState(parentState$, deriveDefinition, dependencies); - this.states.set(deriveDefinition.buildCacheKey(), result); + this.states.set(deriveDefinition.buildCacheKey("memory"), result); } return result; } diff --git a/libs/common/src/platform/state/derive-definition.ts b/libs/common/src/platform/state/derive-definition.ts index 8f62d3a342..9cb5eff3e8 100644 --- a/libs/common/src/platform/state/derive-definition.ts +++ b/libs/common/src/platform/state/derive-definition.ts @@ -171,8 +171,8 @@ export class DeriveDefinition> = {}; - constructor(protected memoryStorage: AbstractStorageService & ObservableStorageService) {} + constructor(protected storageServiceProvider: StorageServiceProvider) {} get( parentState$: Observable, deriveDefinition: DeriveDefinition, dependencies: TDeps, ): DerivedState { - const cacheKey = deriveDefinition.buildCacheKey(); + // TODO: we probably want to support optional normal memory storage for browser + const [location, storageService] = this.storageServiceProvider.get("memory", { + browser: "memory-large-object", + }); + const cacheKey = deriveDefinition.buildCacheKey(location); const existingDerivedState = this.cache[cacheKey]; if (existingDerivedState != null) { // I have to cast out of the unknown generic but this should be safe if rules @@ -29,7 +34,10 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { return existingDerivedState as DefaultDerivedState; } - const newDerivedState = this.buildDerivedState(parentState$, deriveDefinition, dependencies); + const newDerivedState = this.buildDerivedState(parentState$, deriveDefinition, dependencies, [ + location, + storageService, + ]); this.cache[cacheKey] = newDerivedState; return newDerivedState; } @@ -38,11 +46,12 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { parentState$: Observable, deriveDefinition: DeriveDefinition, dependencies: TDeps, + storageLocation: [string, AbstractStorageService & ObservableStorageService], ): DerivedState { return new DefaultDerivedState( parentState$, deriveDefinition, - this.memoryStorage, + storageLocation[1], dependencies, ); } diff --git a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts index 958a938611..e3b1587e3a 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts @@ -72,12 +72,12 @@ describe("DefaultDerivedState", () => { parentState$.next(dateString); await awaitAsync(); - expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual( + expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( derivedValue(new Date(dateString)), ); const calls = memoryStorage.mock.save.mock.calls; expect(calls.length).toBe(1); - expect(calls[0][0]).toBe(deriveDefinition.buildCacheKey()); + expect(calls[0][0]).toBe(deriveDefinition.storageKey); expect(calls[0][1]).toEqual(derivedValue(new Date(dateString))); }); @@ -94,7 +94,7 @@ describe("DefaultDerivedState", () => { it("should store the forced value", async () => { await sut.forceValue(forced); - expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual( + expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( derivedValue(forced), ); }); @@ -109,7 +109,7 @@ describe("DefaultDerivedState", () => { it("should store the forced value", async () => { await sut.forceValue(forced); - expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual( + expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( derivedValue(forced), ); }); @@ -153,7 +153,7 @@ describe("DefaultDerivedState", () => { parentState$.next(newDate); await awaitAsync(); - expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual( + expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( derivedValue(new Date(newDate)), ); @@ -161,7 +161,7 @@ describe("DefaultDerivedState", () => { // Wait for cleanup await awaitAsync(cleanupDelayMs * 2); - expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toBeUndefined(); + expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toBeUndefined(); }); it("should not clear state after cleanup if clearOnCleanup is false", async () => { @@ -171,7 +171,7 @@ describe("DefaultDerivedState", () => { parentState$.next(newDate); await awaitAsync(); - expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual( + expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( derivedValue(new Date(newDate)), ); @@ -179,7 +179,7 @@ describe("DefaultDerivedState", () => { // Wait for cleanup await awaitAsync(cleanupDelayMs * 2); - expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual( + expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual( derivedValue(new Date(newDate)), ); }); diff --git a/libs/common/src/platform/state/state-definition.ts b/libs/common/src/platform/state/state-definition.ts index 15dc9ff757..f1e7dc80ab 100644 --- a/libs/common/src/platform/state/state-definition.ts +++ b/libs/common/src/platform/state/state-definition.ts @@ -24,8 +24,10 @@ export type ClientLocations = { web: StorageLocation | "disk-local"; /** * 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. */ - //browser: StorageLocation; + browser: StorageLocation | "memory-large-object"; /** * 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 18df252062..e04110f28b 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -116,7 +116,9 @@ export const EVENT_COLLECTION_DISK = new StateDefinition("eventCollection", "dis export const SEND_DISK = new StateDefinition("encryptedSend", "disk", { web: "memory", }); -export const SEND_MEMORY = new StateDefinition("decryptedSend", "memory"); +export const SEND_MEMORY = new StateDefinition("decryptedSend", "memory", { + browser: "memory-large-object", +}); // Vault @@ -133,10 +135,16 @@ export const VAULT_ONBOARDING = new StateDefinition("vaultOnboarding", "disk", { export const VAULT_SETTINGS_DISK = new StateDefinition("vaultSettings", "disk", { web: "disk-local", }); -export const VAULT_BROWSER_MEMORY = new StateDefinition("vaultBrowser", "memory"); -export const VAULT_SEARCH_MEMORY = new StateDefinition("vaultSearch", "memory"); +export const VAULT_BROWSER_MEMORY = new StateDefinition("vaultBrowser", "memory", { + browser: "memory-large-object", +}); +export const VAULT_SEARCH_MEMORY = new StateDefinition("vaultSearch", "memory", { + browser: "memory-large-object", +}); export const CIPHERS_DISK = new StateDefinition("ciphers", "disk", { web: "memory" }); export const CIPHERS_DISK_LOCAL = new StateDefinition("ciphersLocal", "disk", { web: "disk-local", }); -export const CIPHERS_MEMORY = new StateDefinition("ciphersMemory", "memory"); +export const CIPHERS_MEMORY = new StateDefinition("ciphersMemory", "memory", { + browser: "memory-large-object", +}); From 300b17aaeb327d6593d8e90e4943ecd703e9fcdb Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 22 Apr 2024 10:14:38 -0400 Subject: [PATCH 58/86] [PM-7653] Do not store disk-backed sessions as single blobs (#8852) * Implement a lazy value class This will be used as a source for composing key-protected storage from a single key source. * Simplify local-backed-session-storage The new implementation stores each value to a unique location, prefixed with `session_` to help indicate the purpose. I've also removed the complexity around session keys, favoring passing in a pre-defined value that is determined lazily once for the service worker. This is more in line with how I expect a key-protected storage would work. * Remove decrypted session flag This has been nothing but an annoyance. If it's ever added back, it needs to have some way to determine if the session key matches the one it was written with * Remove unnecessary string interpolation * Remove sync Lazy This is better done as a separate class. * Handle async through type * prefer two factory calls to incorrect value on races. * Fix type * Remove log * Update libs/common/src/platform/misc/lazy.ts Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> --- apps/browser/config/development.json | 1 - .../browser/src/background/main.background.ts | 47 +- .../storage-service.factory.ts | 26 +- .../decorators/dev-flag.decorator.spec.ts | 2 +- apps/browser/src/platform/flags.ts | 1 - ...cal-backed-session-storage.service.spec.ts | 508 +++++------------- .../local-backed-session-storage.service.ts | 225 +++----- libs/common/spec/index.ts | 1 + libs/common/src/platform/misc/lazy.spec.ts | 85 +++ libs/common/src/platform/misc/lazy.ts | 20 + 10 files changed, 380 insertions(+), 536 deletions(-) create mode 100644 libs/common/src/platform/misc/lazy.spec.ts create mode 100644 libs/common/src/platform/misc/lazy.ts diff --git a/apps/browser/config/development.json b/apps/browser/config/development.json index 1b628c173c..aba10eb25b 100644 --- a/apps/browser/config/development.json +++ b/apps/browser/config/development.json @@ -1,6 +1,5 @@ { "devFlags": { - "storeSessionDecrypted": false, "managedEnvironment": { "base": "https://localhost:8080" } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 0a9ad44962..fa1add0602 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -98,7 +98,9 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- Used for dependency creation import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; +import { Lazy } from "@bitwarden/common/platform/misc/lazy"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { AppIdService } from "@bitwarden/common/platform/services/app-id.service"; import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; @@ -404,32 +406,51 @@ export default class MainBackground { self, ); - const mv3MemoryStorageCreator = (partitionName: string) => { - if (this.popupOnlyContext) { - return new ForegroundMemoryStorageService(partitionName); + // Creates a session key for mv3 storage of large memory items + const sessionKey = new Lazy(async () => { + // Key already in session storage + const sessionStorage = new BrowserMemoryStorageService(); + const existingKey = await sessionStorage.get("session-key"); + if (existingKey) { + if (sessionStorage.valuesRequireDeserialization) { + return SymmetricCryptoKey.fromJSON(existingKey); + } + return existingKey; + } + + // New key + const { derivedKey } = await this.keyGenerationService.createKeyWithPurpose( + 128, + "ephemeral", + "bitwarden-ephemeral", + ); + await sessionStorage.save("session-key", derivedKey); + return derivedKey; + }); + + const mv3MemoryStorageCreator = () => { + if (this.popupOnlyContext) { + return new ForegroundMemoryStorageService(); } - // TODO: Consider using multithreaded encrypt service in popup only context return new LocalBackedSessionStorageService( - this.logService, + sessionKey, + this.storageService, new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false), - this.keyGenerationService, - new BrowserLocalStorageService(), - new BrowserMemoryStorageService(), this.platformUtilsService, - partitionName, + this.logService, ); }; this.secureStorageService = this.storageService; // secure storage is not supported in browsers, so we use local storage and warn users when it is used - this.memoryStorageService = BrowserApi.isManifestVersion(3) - ? mv3MemoryStorageCreator("stateService") - : new MemoryStorageService(); this.memoryStorageForStateProviders = BrowserApi.isManifestVersion(3) ? new BrowserMemoryStorageService() // mv3 stores to storage.session : new BackgroundMemoryStorageService(); // mv2 stores to memory + this.memoryStorageService = BrowserApi.isManifestVersion(3) + ? this.memoryStorageForStateProviders // manifest v3 can reuse the same storage. They are split for v2 due to lacking a good sync mechanism, which isn't true for v3 + : new MemoryStorageService(); this.largeObjectMemoryStorageForStateProviders = BrowserApi.isManifestVersion(3) - ? mv3MemoryStorageCreator("stateProviders") // mv3 stores to local-backed session storage + ? mv3MemoryStorageCreator() // mv3 stores to local-backed session storage : this.memoryStorageForStateProviders; // mv2 stores to the same location const storageServiceProvider = new BrowserStorageServiceProvider( diff --git a/apps/browser/src/platform/background/service-factories/storage-service.factory.ts b/apps/browser/src/platform/background/service-factories/storage-service.factory.ts index 83e8a780a6..e63e39944d 100644 --- a/apps/browser/src/platform/background/service-factories/storage-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/storage-service.factory.ts @@ -3,6 +3,8 @@ import { AbstractStorageService, ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; +import { Lazy } from "@bitwarden/common/platform/misc/lazy"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; import { BrowserApi } from "../../browser/browser-api"; @@ -17,10 +19,10 @@ import { KeyGenerationServiceInitOptions, keyGenerationServiceFactory, } from "./key-generation-service.factory"; -import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; +import { LogServiceInitOptions, logServiceFactory } from "./log-service.factory"; import { - platformUtilsServiceFactory, PlatformUtilsServiceInitOptions, + platformUtilsServiceFactory, } from "./platform-utils-service.factory"; export type DiskStorageServiceInitOptions = FactoryOptions; @@ -70,13 +72,23 @@ export function memoryStorageServiceFactory( return factory(cache, "memoryStorageService", opts, async () => { if (BrowserApi.isManifestVersion(3)) { return new LocalBackedSessionStorageService( - await logServiceFactory(cache, opts), - await encryptServiceFactory(cache, opts), - await keyGenerationServiceFactory(cache, opts), + new Lazy(async () => { + const existingKey = await ( + await sessionStorageServiceFactory(cache, opts) + ).get("session-key"); + if (existingKey) { + return existingKey; + } + const { derivedKey } = await ( + await keyGenerationServiceFactory(cache, opts) + ).createKeyWithPurpose(128, "ephemeral", "bitwarden-ephemeral"); + await (await sessionStorageServiceFactory(cache, opts)).save("session-key", derivedKey); + return derivedKey; + }), await diskStorageServiceFactory(cache, opts), - await sessionStorageServiceFactory(cache, opts), + await encryptServiceFactory(cache, opts), await platformUtilsServiceFactory(cache, opts), - "serviceFactories", + await logServiceFactory(cache, opts), ); } return new MemoryStorageService(); diff --git a/apps/browser/src/platform/decorators/dev-flag.decorator.spec.ts b/apps/browser/src/platform/decorators/dev-flag.decorator.spec.ts index c5401f8a09..da00bc6fe3 100644 --- a/apps/browser/src/platform/decorators/dev-flag.decorator.spec.ts +++ b/apps/browser/src/platform/decorators/dev-flag.decorator.spec.ts @@ -9,7 +9,7 @@ jest.mock("../flags", () => ({ })); class TestClass { - @devFlag("storeSessionDecrypted") test() { + @devFlag("managedEnvironment") test() { return "test"; } } diff --git a/apps/browser/src/platform/flags.ts b/apps/browser/src/platform/flags.ts index 36aa698a7b..383e982f06 100644 --- a/apps/browser/src/platform/flags.ts +++ b/apps/browser/src/platform/flags.ts @@ -19,7 +19,6 @@ export type Flags = { // required to avoid linting errors when there are no flags // eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = { - storeSessionDecrypted?: boolean; managedEnvironment?: GroupPolicyEnvironment; } & SharedDevFlags; diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts index a4581e6ac1..7114bda06e 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts @@ -1,412 +1,200 @@ import { mock, MockProxy } from "jest-mock-extended"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { - AbstractMemoryStorageService, - AbstractStorageService, - StorageUpdate, -} from "@bitwarden/common/platform/abstractions/storage.service"; +import { Lazy } from "@bitwarden/common/platform/misc/lazy"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; - -import { BrowserApi } from "../browser/browser-api"; +import { FakeStorageService, makeEncString } from "@bitwarden/common/spec"; import { LocalBackedSessionStorageService } from "./local-backed-session-storage.service"; -describe.skip("LocalBackedSessionStorage", () => { - const sendMessageWithResponseSpy: jest.SpyInstance = jest.spyOn( - BrowserApi, - "sendMessageWithResponse", +describe("LocalBackedSessionStorage", () => { + const sessionKey = new SymmetricCryptoKey( + Utils.fromUtf8ToArray("00000000000000000000000000000000"), ); - + let localStorage: FakeStorageService; let encryptService: MockProxy; - let keyGenerationService: MockProxy; - let localStorageService: MockProxy; - let sessionStorageService: MockProxy; - let logService: MockProxy; let platformUtilsService: MockProxy; - - let cache: Record; - const testObj = { a: 1, b: 2 }; - const stringifiedTestObj = JSON.stringify(testObj); - - const key = new SymmetricCryptoKey(Utils.fromUtf8ToArray("00000000000000000000000000000000")); - let getSessionKeySpy: jest.SpyInstance; - let sendUpdateSpy: jest.SpyInstance; - const mockEnc = (input: string) => Promise.resolve(new EncString("ENCRYPTED" + input)); + let logService: MockProxy; let sut: LocalBackedSessionStorageService; - const mockExistingSessionKey = (key: SymmetricCryptoKey) => { - sessionStorageService.get.mockImplementation((storageKey) => { - if (storageKey === "localEncryptionKey_test") { - return Promise.resolve(key?.toJSON()); - } - - return Promise.reject("No implementation for " + storageKey); - }); - }; - beforeEach(() => { - sendMessageWithResponseSpy.mockResolvedValue(null); - logService = mock(); + localStorage = new FakeStorageService(); encryptService = mock(); - keyGenerationService = mock(); - localStorageService = mock(); - sessionStorageService = mock(); + platformUtilsService = mock(); + logService = mock(); sut = new LocalBackedSessionStorageService( - logService, + new Lazy(async () => sessionKey), + localStorage, encryptService, - keyGenerationService, - localStorageService, - sessionStorageService, platformUtilsService, - "test", + logService, ); - - cache = sut["cachedSession"]; - - keyGenerationService.createKeyWithPurpose.mockResolvedValue({ - derivedKey: key, - salt: "bitwarden-ephemeral", - material: null, // Not used - }); - - getSessionKeySpy = jest.spyOn(sut, "getSessionEncKey"); - getSessionKeySpy.mockResolvedValue(key); - - // sendUpdateSpy = jest.spyOn(sut, "sendUpdate"); - // sendUpdateSpy.mockReturnValue(); }); describe("get", () => { - describe("in local cache or external context cache", () => { - it("should return from local cache", async () => { - cache["test"] = stringifiedTestObj; - const result = await sut.get("test"); - expect(result).toStrictEqual(testObj); - }); - - it("should return from external context cache when local cache is not available", async () => { - sendMessageWithResponseSpy.mockResolvedValue(stringifiedTestObj); - const result = await sut.get("test"); - expect(result).toStrictEqual(testObj); - }); + it("return the cached value when one is cached", async () => { + sut["cache"]["test"] = "cached"; + const result = await sut.get("test"); + expect(result).toEqual("cached"); }); - describe("not in cache", () => { - const session = { test: stringifiedTestObj }; + it("returns a decrypted value when one is stored in local storage", async () => { + const encrypted = makeEncString("encrypted"); + localStorage.internalStore["session_test"] = encrypted.encryptedString; + encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + const result = await sut.get("test"); + expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey); + expect(result).toEqual("decrypted"); + }); - beforeEach(() => { - mockExistingSessionKey(key); - }); + it("caches the decrypted value when one is stored in local storage", async () => { + const encrypted = makeEncString("encrypted"); + localStorage.internalStore["session_test"] = encrypted.encryptedString; + encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + await sut.get("test"); + expect(sut["cache"]["test"]).toEqual("decrypted"); + }); + }); - describe("no session retrieved", () => { - let result: any; - let spy: jest.SpyInstance; - beforeEach(async () => { - spy = jest.spyOn(sut, "getLocalSession").mockResolvedValue(null); - localStorageService.get.mockResolvedValue(null); - result = await sut.get("test"); - }); + describe("getBypassCache", () => { + it("ignores cached values", async () => { + sut["cache"]["test"] = "cached"; + const encrypted = makeEncString("encrypted"); + localStorage.internalStore["session_test"] = encrypted.encryptedString; + encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + const result = await sut.getBypassCache("test"); + expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey); + expect(result).toEqual("decrypted"); + }); - it("should grab from session if not in cache", async () => { - expect(spy).toHaveBeenCalledWith(key); - }); + it("returns a decrypted value when one is stored in local storage", async () => { + const encrypted = makeEncString("encrypted"); + localStorage.internalStore["session_test"] = encrypted.encryptedString; + encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + const result = await sut.getBypassCache("test"); + expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey); + expect(result).toEqual("decrypted"); + }); - it("should return null if session is null", async () => { - expect(result).toBeNull(); - }); - }); + it("caches the decrypted value when one is stored in local storage", async () => { + const encrypted = makeEncString("encrypted"); + localStorage.internalStore["session_test"] = encrypted.encryptedString; + encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + await sut.getBypassCache("test"); + expect(sut["cache"]["test"]).toEqual("decrypted"); + }); - describe("session retrieved from storage", () => { - beforeEach(() => { - jest.spyOn(sut, "getLocalSession").mockResolvedValue(session); - }); - - it("should return null if session does not have the key", async () => { - const result = await sut.get("DNE"); - expect(result).toBeNull(); - }); - - it("should return the value retrieved from session", async () => { - const result = await sut.get("test"); - expect(result).toEqual(session.test); - }); - - it("should set retrieved values in cache", async () => { - await sut.get("test"); - expect(cache["test"]).toBeTruthy(); - expect(cache["test"]).toEqual(session.test); - }); - - it("should use a deserializer if provided", async () => { - const deserializer = jest.fn().mockReturnValue(testObj); - const result = await sut.get("test", { deserializer: deserializer }); - expect(deserializer).toHaveBeenCalledWith(session.test); - expect(result).toEqual(testObj); - }); - }); + it("deserializes when a deserializer is provided", async () => { + const encrypted = makeEncString("encrypted"); + localStorage.internalStore["session_test"] = encrypted.encryptedString; + encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + const deserializer = jest.fn().mockReturnValue("deserialized"); + const result = await sut.getBypassCache("test", { deserializer }); + expect(deserializer).toHaveBeenCalledWith("decrypted"); + expect(result).toEqual("deserialized"); }); }); describe("has", () => { - it("should be false if `get` returns null", async () => { - const spy = jest.spyOn(sut, "get"); - spy.mockResolvedValue(null); - expect(await sut.has("test")).toBe(false); + it("returns false when the key is not in cache", async () => { + const result = await sut.has("test"); + expect(result).toBe(false); + }); + + it("returns true when the key is in cache", async () => { + sut["cache"]["test"] = "cached"; + const result = await sut.has("test"); + expect(result).toBe(true); + }); + + it("returns true when the key is in local storage", async () => { + localStorage.internalStore["session_test"] = makeEncString("encrypted").encryptedString; + encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + const result = await sut.has("test"); + expect(result).toBe(true); + }); + + it.each([null, undefined])("returns false when %s is cached", async (nullish) => { + sut["cache"]["test"] = nullish; + await expect(sut.has("test")).resolves.toBe(false); + }); + + it.each([null, undefined])( + "returns false when null is stored in local storage", + async (nullish) => { + localStorage.internalStore["session_test"] = nullish; + await expect(sut.has("test")).resolves.toBe(false); + expect(encryptService.decryptToUtf8).not.toHaveBeenCalled(); + }, + ); + }); + + describe("save", () => { + const encString = makeEncString("encrypted"); + beforeEach(() => { + encryptService.encrypt.mockResolvedValue(encString); + }); + + it("logs a warning when saving the same value twice and in a dev environment", async () => { + platformUtilsService.isDev.mockReturnValue(true); + sut["cache"]["test"] = "cached"; + await sut.save("test", "cached"); + expect(logService.warning).toHaveBeenCalled(); + }); + + it("does not log when saving the same value twice and not in a dev environment", async () => { + platformUtilsService.isDev.mockReturnValue(false); + sut["cache"]["test"] = "cached"; + await sut.save("test", "cached"); + expect(logService.warning).not.toHaveBeenCalled(); + }); + + it("removes the key when saving a null value", async () => { + const spy = jest.spyOn(sut, "remove"); + await sut.save("test", null); expect(spy).toHaveBeenCalledWith("test"); }); - it("should be true if `get` returns non-null", async () => { - const spy = jest.spyOn(sut, "get"); - spy.mockResolvedValue({}); - expect(await sut.has("test")).toBe(true); - expect(spy).toHaveBeenCalledWith("test"); + it("saves the value to cache", async () => { + await sut.save("test", "value"); + expect(sut["cache"]["test"]).toEqual("value"); + }); + + it("encrypts and saves the value to local storage", async () => { + await sut.save("test", "value"); + expect(encryptService.encrypt).toHaveBeenCalledWith(JSON.stringify("value"), sessionKey); + expect(localStorage.internalStore["session_test"]).toEqual(encString.encryptedString); + }); + + it("emits an update", async () => { + const spy = jest.spyOn(sut["updatesSubject"], "next"); + await sut.save("test", "value"); + expect(spy).toHaveBeenCalledWith({ key: "test", updateType: "save" }); }); }); describe("remove", () => { - describe("existing cache value is null", () => { - it("should not save null if the local cached value is already null", async () => { - cache["test"] = null; - await sut.remove("test"); - expect(sendUpdateSpy).not.toHaveBeenCalled(); - }); - - it("should not save null if the externally cached value is already null", async () => { - sendMessageWithResponseSpy.mockResolvedValue(null); - await sut.remove("test"); - expect(sendUpdateSpy).not.toHaveBeenCalled(); - }); - }); - - it("should save null", async () => { - cache["test"] = stringifiedTestObj; - + it("nulls the value in cache", async () => { + sut["cache"]["test"] = "cached"; await sut.remove("test"); - expect(sendUpdateSpy).toHaveBeenCalledWith({ key: "test", updateType: "remove" }); - }); - }); - - describe("save", () => { - describe("currently cached", () => { - it("does not save the value a local cached value exists which is an exact match", async () => { - cache["test"] = stringifiedTestObj; - await sut.save("test", testObj); - expect(sendUpdateSpy).not.toHaveBeenCalled(); - }); - - it("does not save the value if a local cached value exists, even if the keys not in the same order", async () => { - cache["test"] = JSON.stringify({ b: 2, a: 1 }); - await sut.save("test", testObj); - expect(sendUpdateSpy).not.toHaveBeenCalled(); - }); - - it("does not save the value a externally cached value exists which is an exact match", async () => { - sendMessageWithResponseSpy.mockResolvedValue(stringifiedTestObj); - await sut.save("test", testObj); - expect(sendUpdateSpy).not.toHaveBeenCalled(); - expect(cache["test"]).toBe(stringifiedTestObj); - }); - - it("saves the value if the currently cached string value evaluates to a falsy value", async () => { - cache["test"] = "null"; - await sut.save("test", testObj); - expect(sendUpdateSpy).toHaveBeenCalledWith({ key: "test", updateType: "save" }); - }); + expect(sut["cache"]["test"]).toBeNull(); }); - describe("caching", () => { - beforeEach(() => { - localStorageService.get.mockResolvedValue(null); - sessionStorageService.get.mockResolvedValue(null); - - localStorageService.save.mockResolvedValue(); - sessionStorageService.save.mockResolvedValue(); - - encryptService.encrypt.mockResolvedValue(mockEnc("{}")); - }); - - it("should remove key from cache if value is null", async () => { - cache["test"] = {}; - // const cacheSetSpy = jest.spyOn(cache, "set"); - expect(cache["test"]).toBe(true); - await sut.save("test", null); - // Don't remove from cache, just replace with null - expect(cache["test"]).toBe(null); - // expect(cacheSetSpy).toHaveBeenCalledWith("test", null); - }); - - it("should set cache if value is non-null", async () => { - expect(cache["test"]).toBe(false); - // const setSpy = jest.spyOn(cache, "set"); - await sut.save("test", testObj); - expect(cache["test"]).toBe(stringifiedTestObj); - // expect(setSpy).toHaveBeenCalledWith("test", stringifiedTestObj); - }); + it("removes the key from local storage", async () => { + localStorage.internalStore["session_test"] = makeEncString("encrypted").encryptedString; + await sut.remove("test"); + expect(localStorage.internalStore["session_test"]).toBeUndefined(); }); - describe("local storing", () => { - let setSpy: jest.SpyInstance; - - beforeEach(() => { - setSpy = jest.spyOn(sut, "setLocalSession").mockResolvedValue(); - }); - - it("should store a new session", async () => { - jest.spyOn(sut, "getLocalSession").mockResolvedValue(null); - await sut.save("test", testObj); - - expect(setSpy).toHaveBeenCalledWith({ test: testObj }, key); - }); - - it("should update an existing session", async () => { - const existingObj = { test: testObj }; - jest.spyOn(sut, "getLocalSession").mockResolvedValue(existingObj); - await sut.save("test2", testObj); - - expect(setSpy).toHaveBeenCalledWith({ test2: testObj, ...existingObj }, key); - }); - - it("should overwrite an existing item in session", async () => { - const existingObj = { test: {} }; - jest.spyOn(sut, "getLocalSession").mockResolvedValue(existingObj); - await sut.save("test", testObj); - - expect(setSpy).toHaveBeenCalledWith({ test: testObj }, key); - }); - }); - }); - - describe("getSessionKey", () => { - beforeEach(() => { - getSessionKeySpy.mockRestore(); - }); - - it("should return the stored symmetric crypto key", async () => { - sessionStorageService.get.mockResolvedValue({ ...key }); - const result = await sut.getSessionEncKey(); - - expect(result).toStrictEqual(key); - }); - - describe("new key creation", () => { - beforeEach(() => { - keyGenerationService.createKeyWithPurpose.mockResolvedValue({ - salt: "salt", - material: null, - derivedKey: key, - }); - jest.spyOn(sut, "setSessionEncKey").mockResolvedValue(); - }); - - it("should create a symmetric crypto key", async () => { - const result = await sut.getSessionEncKey(); - - expect(result).toStrictEqual(key); - expect(keyGenerationService.createKeyWithPurpose).toHaveBeenCalledTimes(1); - }); - - it("should store a symmetric crypto key if it makes one", async () => { - const spy = jest.spyOn(sut, "setSessionEncKey").mockResolvedValue(); - await sut.getSessionEncKey(); - - expect(spy).toHaveBeenCalledWith(key); - }); - }); - }); - - describe("getLocalSession", () => { - it("should return null if session is null", async () => { - const result = await sut.getLocalSession(key); - - expect(result).toBeNull(); - expect(localStorageService.get).toHaveBeenCalledWith("session_test"); - }); - - describe("non-null sessions", () => { - const session = { test: "test" }; - const encSession = new EncString(JSON.stringify(session)); - const decryptedSession = JSON.stringify(session); - - beforeEach(() => { - localStorageService.get.mockResolvedValue(encSession.encryptedString); - }); - - it("should decrypt returned sessions", async () => { - encryptService.decryptToUtf8 - .calledWith(expect.anything(), key) - .mockResolvedValue(decryptedSession); - await sut.getLocalSession(key); - expect(encryptService.decryptToUtf8).toHaveBeenNthCalledWith(1, encSession, key); - }); - - it("should parse session", async () => { - encryptService.decryptToUtf8 - .calledWith(expect.anything(), key) - .mockResolvedValue(decryptedSession); - const result = await sut.getLocalSession(key); - expect(result).toEqual(session); - }); - - it("should remove state if decryption fails", async () => { - encryptService.decryptToUtf8.mockResolvedValue(null); - const setSessionEncKeySpy = jest.spyOn(sut, "setSessionEncKey").mockResolvedValue(); - - const result = await sut.getLocalSession(key); - - expect(result).toBeNull(); - expect(setSessionEncKeySpy).toHaveBeenCalledWith(null); - expect(localStorageService.remove).toHaveBeenCalledWith("session_test"); - }); - }); - }); - - describe("setLocalSession", () => { - const testSession = { test: "a" }; - const testJSON = JSON.stringify(testSession); - - it("should encrypt a stringified session", async () => { - encryptService.encrypt.mockImplementation(mockEnc); - localStorageService.save.mockResolvedValue(); - await sut.setLocalSession(testSession, key); - - expect(encryptService.encrypt).toHaveBeenNthCalledWith(1, testJSON, key); - }); - - it("should remove local session if null", async () => { - encryptService.encrypt.mockResolvedValue(null); - await sut.setLocalSession(null, key); - - expect(localStorageService.remove).toHaveBeenCalledWith("session_test"); - }); - - it("should save encrypted string", async () => { - encryptService.encrypt.mockImplementation(mockEnc); - await sut.setLocalSession(testSession, key); - - expect(localStorageService.save).toHaveBeenCalledWith( - "session_test", - (await mockEnc(testJSON)).encryptedString, - ); - }); - }); - - describe("setSessionKey", () => { - it("should remove if null", async () => { - await sut.setSessionEncKey(null); - expect(sessionStorageService.remove).toHaveBeenCalledWith("localEncryptionKey_test"); - }); - - it("should save key when not null", async () => { - await sut.setSessionEncKey(key); - expect(sessionStorageService.save).toHaveBeenCalledWith("localEncryptionKey_test", key); + it("emits an update", async () => { + const spy = jest.spyOn(sut["updatesSubject"], "next"); + await sut.remove("test"); + expect(spy).toHaveBeenCalledWith({ key: "test", updateType: "remove" }); }); }); }); diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.ts index 146eb11b2b..5432e8d918 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.ts @@ -2,7 +2,6 @@ import { Subject } from "rxjs"; import { Jsonify } from "type-fest"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { @@ -11,13 +10,12 @@ import { ObservableStorageService, StorageUpdate, } from "@bitwarden/common/platform/abstractions/storage.service"; +import { Lazy } from "@bitwarden/common/platform/misc/lazy"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { MemoryStorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { BrowserApi } from "../browser/browser-api"; -import { devFlag } from "../decorators/dev-flag.decorator"; -import { devFlagEnabled } from "../flags"; import { MemoryStoragePortMessage } from "../storage/port-messages"; import { portName } from "../storage/port-name"; @@ -25,85 +23,64 @@ export class LocalBackedSessionStorageService extends AbstractMemoryStorageService implements ObservableStorageService { + private ports: Set = new Set([]); + private cache: Record = {}; private updatesSubject = new Subject(); - private commandName = `localBackedSessionStorage_${this.partitionName}`; - private encKey = `localEncryptionKey_${this.partitionName}`; - private sessionKey = `session_${this.partitionName}`; - private cachedSession: Record = {}; - private _ports: Set = new Set([]); - private knownNullishCacheKeys: Set = new Set([]); + readonly valuesRequireDeserialization = true; + updates$ = this.updatesSubject.asObservable(); constructor( - private logService: LogService, - private encryptService: EncryptService, - private keyGenerationService: KeyGenerationService, - private localStorage: AbstractStorageService, - private sessionStorage: AbstractStorageService, - private platformUtilsService: PlatformUtilsService, - private partitionName: string, + private readonly sessionKey: Lazy>, + private readonly localStorage: AbstractStorageService, + private readonly encryptService: EncryptService, + private readonly platformUtilsService: PlatformUtilsService, + private readonly logService: LogService, ) { super(); BrowserApi.addListener(chrome.runtime.onConnect, (port) => { - if (port.name !== `${portName(chrome.storage.session)}_${partitionName}`) { + if (port.name !== portName(chrome.storage.session)) { return; } - this._ports.add(port); + this.ports.add(port); const listenerCallback = this.onMessageFromForeground.bind(this); port.onDisconnect.addListener(() => { - this._ports.delete(port); + this.ports.delete(port); port.onMessage.removeListener(listenerCallback); }); port.onMessage.addListener(listenerCallback); // Initialize the new memory storage service with existing data this.sendMessageTo(port, { action: "initialization", - data: Array.from(Object.keys(this.cachedSession)), + data: Array.from(Object.keys(this.cache)), + }); + this.updates$.subscribe((update) => { + this.broadcastMessage({ + action: "subject_update", + data: update, + }); }); }); - this.updates$.subscribe((update) => { - this.broadcastMessage({ - action: "subject_update", - data: update, - }); - }); - } - - get valuesRequireDeserialization(): boolean { - return true; - } - - get updates$() { - return this.updatesSubject.asObservable(); } async get(key: string, options?: MemoryStorageOptions): Promise { - if (this.cachedSession[key] != null) { - return this.cachedSession[key] as T; - } - - if (this.knownNullishCacheKeys.has(key)) { - return null; + if (this.cache[key] !== undefined) { + return this.cache[key] as T; } return await this.getBypassCache(key, options); } async getBypassCache(key: string, options?: MemoryStorageOptions): Promise { - const session = await this.getLocalSession(await this.getSessionEncKey()); - if (session[key] == null) { - this.knownNullishCacheKeys.add(key); - return null; - } + let value = await this.getLocalSessionValue(await this.sessionKey.get(), key); - let value = session[key]; if (options?.deserializer != null) { value = options.deserializer(value as Jsonify); } - void this.save(key, value); + this.cache[key] = value; return value as T; } @@ -114,7 +91,7 @@ export class LocalBackedSessionStorageService async save(key: string, obj: T): Promise { // This is for observation purposes only. At some point, we don't want to write to local session storage if the value is the same. if (this.platformUtilsService.isDev()) { - const existingValue = this.cachedSession[key] as T; + const existingValue = this.cache[key] as T; if (this.compareValues(existingValue, obj)) { this.logService.warning(`Possible unnecessary write to local session storage. Key: ${key}`); this.logService.warning(obj as any); @@ -125,128 +102,42 @@ export class LocalBackedSessionStorageService return await this.remove(key); } - this.knownNullishCacheKeys.delete(key); - this.cachedSession[key] = obj; + this.cache[key] = obj; await this.updateLocalSessionValue(key, obj); this.updatesSubject.next({ key, updateType: "save" }); } async remove(key: string): Promise { - this.knownNullishCacheKeys.add(key); - delete this.cachedSession[key]; + this.cache[key] = null; await this.updateLocalSessionValue(key, null); this.updatesSubject.next({ key, updateType: "remove" }); } - private async updateLocalSessionValue(key: string, obj: T) { - const sessionEncKey = await this.getSessionEncKey(); - const localSession = (await this.getLocalSession(sessionEncKey)) ?? {}; - localSession[key] = obj; - void this.setLocalSession(localSession, sessionEncKey); - } - - async getLocalSession(encKey: SymmetricCryptoKey): Promise> { - if (Object.keys(this.cachedSession).length > 0) { - return this.cachedSession; - } - - this.cachedSession = {}; - const local = await this.localStorage.get(this.sessionKey); + private async getLocalSessionValue(encKey: SymmetricCryptoKey, key: string): Promise { + const local = await this.localStorage.get(this.sessionStorageKey(key)); if (local == null) { - return this.cachedSession; + return null; } - if (devFlagEnabled("storeSessionDecrypted")) { - return local as any as Record; + const valueJson = await this.encryptService.decryptToUtf8(new EncString(local), encKey); + if (valueJson == null) { + // error with decryption, value is lost, delete state and start over + await this.localStorage.remove(this.sessionStorageKey(key)); + return null; } - const sessionJson = await this.encryptService.decryptToUtf8(new EncString(local), encKey); - if (sessionJson == null) { - // Error with decryption -- session is lost, delete state and key and start over - await this.setSessionEncKey(null); - await this.localStorage.remove(this.sessionKey); - return this.cachedSession; - } - - this.cachedSession = JSON.parse(sessionJson); - return this.cachedSession; + return JSON.parse(valueJson); } - async setLocalSession(session: Record, key: SymmetricCryptoKey) { - if (devFlagEnabled("storeSessionDecrypted")) { - await this.setDecryptedLocalSession(session); - } else { - await this.setEncryptedLocalSession(session, key); - } - } - - @devFlag("storeSessionDecrypted") - async setDecryptedLocalSession(session: Record): Promise { - // Make sure we're storing the jsonified version of the session - const jsonSession = JSON.parse(JSON.stringify(session)); - if (session == null) { - await this.localStorage.remove(this.sessionKey); - } else { - await this.localStorage.save(this.sessionKey, jsonSession); - } - } - - async setEncryptedLocalSession(session: Record, key: SymmetricCryptoKey) { - const jsonSession = JSON.stringify(session); - const encSession = await this.encryptService.encrypt(jsonSession, key); - - if (encSession == null) { - return await this.localStorage.remove(this.sessionKey); - } - await this.localStorage.save(this.sessionKey, encSession.encryptedString); - } - - async getSessionEncKey(): Promise { - let storedKey = await this.sessionStorage.get(this.encKey); - if (storedKey == null || Object.keys(storedKey).length == 0) { - const generatedKey = await this.keyGenerationService.createKeyWithPurpose( - 128, - "ephemeral", - "bitwarden-ephemeral", - ); - storedKey = generatedKey.derivedKey; - await this.setSessionEncKey(storedKey); - return storedKey; - } else { - return SymmetricCryptoKey.fromJSON(storedKey); - } - } - - async setSessionEncKey(input: SymmetricCryptoKey): Promise { - if (input == null) { - await this.sessionStorage.remove(this.encKey); - } else { - await this.sessionStorage.save(this.encKey, input); - } - } - - private compareValues(value1: T, value2: T): boolean { - if (value1 == null && value2 == null) { - return true; + private async updateLocalSessionValue(key: string, value: unknown): Promise { + if (value == null) { + await this.localStorage.remove(this.sessionStorageKey(key)); + return; } - if (value1 && value2 == null) { - return false; - } - - if (value1 == null && value2) { - return false; - } - - if (typeof value1 !== "object" || typeof value2 !== "object") { - return value1 === value2; - } - - if (JSON.stringify(value1) === JSON.stringify(value2)) { - return true; - } - - return Object.entries(value1).sort().toString() === Object.entries(value2).sort().toString(); + const valueJson = JSON.stringify(value); + const encValue = await this.encryptService.encrypt(valueJson, await this.sessionKey.get()); + await this.localStorage.save(this.sessionStorageKey(key), encValue.encryptedString); } private async onMessageFromForeground( @@ -282,7 +173,7 @@ export class LocalBackedSessionStorageService } protected broadcastMessage(data: Omit) { - this._ports.forEach((port) => { + this.ports.forEach((port) => { this.sendMessageTo(port, data); }); } @@ -296,4 +187,32 @@ export class LocalBackedSessionStorageService originator: "background", }); } + + private sessionStorageKey(key: string) { + return `session_${key}`; + } + + private compareValues(value1: T, value2: T): boolean { + if (value1 == null && value2 == null) { + return true; + } + + if (value1 && value2 == null) { + return false; + } + + if (value1 == null && value2) { + return false; + } + + if (typeof value1 !== "object" || typeof value2 !== "object") { + return value1 === value2; + } + + if (JSON.stringify(value1) === JSON.stringify(value2)) { + return true; + } + + return Object.entries(value1).sort().toString() === Object.entries(value2).sort().toString(); + } } diff --git a/libs/common/spec/index.ts b/libs/common/spec/index.ts index 72bd28aca4..6e9af8400e 100644 --- a/libs/common/spec/index.ts +++ b/libs/common/spec/index.ts @@ -4,3 +4,4 @@ export * from "./matchers"; export * from "./fake-state-provider"; export * from "./fake-state"; export * from "./fake-account-service"; +export * from "./fake-storage.service"; diff --git a/libs/common/src/platform/misc/lazy.spec.ts b/libs/common/src/platform/misc/lazy.spec.ts new file mode 100644 index 0000000000..76ee085d3d --- /dev/null +++ b/libs/common/src/platform/misc/lazy.spec.ts @@ -0,0 +1,85 @@ +import { Lazy } from "./lazy"; + +describe("Lazy", () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("async", () => { + let factory: jest.Mock>; + let lazy: Lazy>; + + beforeEach(() => { + factory = jest.fn(); + lazy = new Lazy(factory); + }); + + describe("get", () => { + it("should call the factory once", async () => { + await lazy.get(); + await lazy.get(); + + expect(factory).toHaveBeenCalledTimes(1); + }); + + it("should return the value from the factory", async () => { + factory.mockResolvedValue(42); + + const value = await lazy.get(); + + expect(value).toBe(42); + }); + }); + + describe("factory throws", () => { + it("should throw the error", async () => { + factory.mockRejectedValue(new Error("factory error")); + + await expect(lazy.get()).rejects.toThrow("factory error"); + }); + }); + + describe("factory returns undefined", () => { + it("should return undefined", async () => { + factory.mockResolvedValue(undefined); + + const value = await lazy.get(); + + expect(value).toBeUndefined(); + }); + }); + + describe("factory returns null", () => { + it("should return null", async () => { + factory.mockResolvedValue(null); + + const value = await lazy.get(); + + expect(value).toBeNull(); + }); + }); + }); + + describe("sync", () => { + const syncFactory = jest.fn(); + let lazy: Lazy; + + beforeEach(() => { + syncFactory.mockReturnValue(42); + lazy = new Lazy(syncFactory); + }); + + it("should return the value from the factory", () => { + const value = lazy.get(); + + expect(value).toBe(42); + }); + + it("should call the factory once", () => { + lazy.get(); + lazy.get(); + + expect(syncFactory).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/libs/common/src/platform/misc/lazy.ts b/libs/common/src/platform/misc/lazy.ts new file mode 100644 index 0000000000..fb85b93678 --- /dev/null +++ b/libs/common/src/platform/misc/lazy.ts @@ -0,0 +1,20 @@ +export class Lazy { + private _value: T | undefined = undefined; + private _isCreated = false; + + constructor(private readonly factory: () => T) {} + + /** + * Resolves the factory and returns the result. Guaranteed to resolve the value only once. + * + * @returns The value produced by your factory. + */ + get(): T { + if (!this._isCreated) { + this._value = this.factory(); + this._isCreated = true; + } + + return this._value as T; + } +} From 100b43dd8f7ac23cb888b0f031353aa68beffb82 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:06:43 -0400 Subject: [PATCH 59/86] =?UTF-8?q?Revert=20"Auth/PM-6689=20-=20Migrate=20Se?= =?UTF-8?q?curity=20Stamp=20to=20Token=20Service=20and=20State=20Prov?= =?UTF-8?q?=E2=80=A6"=20(#8860)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 91f1d9fb86142805d0774182c3ee6234e13946e3. --- .../browser/src/background/main.background.ts | 1 - apps/cli/src/bw.ts | 1 - .../src/services/jslib-services.module.ts | 1 - .../login-strategies/login.strategy.spec.ts | 4 + .../common/login-strategies/login.strategy.ts | 9 ++- .../src/auth/abstractions/token.service.ts | 6 -- .../src/auth/services/token.service.spec.ts | 79 ------------------- .../common/src/auth/services/token.service.ts | 25 ------ .../src/auth/services/token.state.spec.ts | 2 - libs/common/src/auth/services/token.state.ts | 5 -- .../platform/abstractions/state.service.ts | 2 + .../models/domain/account-tokens.spec.ts | 9 +++ .../platform/models/domain/account.spec.ts | 4 +- .../src/platform/models/domain/account.ts | 18 +++++ .../src/platform/services/state.service.ts | 17 ++++ .../src/vault/services/sync/sync.service.ts | 6 +- 16 files changed, 63 insertions(+), 126 deletions(-) create mode 100644 libs/common/src/platform/models/domain/account-tokens.spec.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index fa1add0602..15f21b2501 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -813,7 +813,6 @@ export default class MainBackground { this.avatarService, logoutCallback, this.billingAccountProfileStateService, - this.tokenService, ); this.eventUploadService = new EventUploadService( this.apiService, diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 437f807bc6..58329128b8 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -631,7 +631,6 @@ export class Main { this.avatarService, async (expired: boolean) => await this.logout(), this.billingAccountProfileStateService, - this.tokenService, ); this.totpService = new TotpService(this.cryptoFunctionService, this.logService); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 27b182de5d..f31bcb1c51 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -628,7 +628,6 @@ const safeProviders: SafeProvider[] = [ AvatarServiceAbstraction, LOGOUT_CALLBACK, BillingAccountProfileStateService, - TokenServiceAbstraction, ], }), safeProvider({ diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index e0833342ce..431f736e94 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -27,6 +27,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Account, AccountProfile, + AccountTokens, AccountKeys, } from "@bitwarden/common/platform/models/domain/account"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -212,6 +213,9 @@ describe("LoginStrategy", () => { kdfType: kdf, }, }, + tokens: { + ...new AccountTokens(), + }, keys: new AccountKeys(), }), ); diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index a73c32e120..a6dc193183 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -27,7 +27,11 @@ 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 { Account, AccountProfile } from "@bitwarden/common/platform/models/domain/account"; +import { + Account, + AccountProfile, + AccountTokens, +} from "@bitwarden/common/platform/models/domain/account"; import { UserId } from "@bitwarden/common/types/guid"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; @@ -188,6 +192,9 @@ export abstract class LoginStrategy { kdfType: tokenResponse.kdf, }, }, + tokens: { + ...new AccountTokens(), + }, }), ); diff --git a/libs/common/src/auth/abstractions/token.service.ts b/libs/common/src/auth/abstractions/token.service.ts index fc3bd317f4..75bb383882 100644 --- a/libs/common/src/auth/abstractions/token.service.ts +++ b/libs/common/src/auth/abstractions/token.service.ts @@ -213,10 +213,4 @@ export abstract class TokenService { * @returns A promise that resolves with a boolean representing the user's external authN status. */ getIsExternal: () => Promise; - - /** Gets the active or passed in user's security stamp */ - getSecurityStamp: (userId?: UserId) => Promise; - - /** Sets the security stamp for the active or passed in user */ - setSecurityStamp: (securityStamp: string, userId?: UserId) => Promise; } diff --git a/libs/common/src/auth/services/token.service.spec.ts b/libs/common/src/auth/services/token.service.spec.ts index 3e92053d2f..d32c4d8e1c 100644 --- a/libs/common/src/auth/services/token.service.spec.ts +++ b/libs/common/src/auth/services/token.service.spec.ts @@ -23,7 +23,6 @@ import { EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL, REFRESH_TOKEN_DISK, REFRESH_TOKEN_MEMORY, - SECURITY_STAMP_MEMORY, } from "./token.state"; describe("TokenService", () => { @@ -2192,84 +2191,6 @@ describe("TokenService", () => { }); }); - describe("Security Stamp methods", () => { - const mockSecurityStamp = "securityStamp"; - - describe("setSecurityStamp", () => { - it("should throw an error if no user id is provided and there is no active user in global state", async () => { - // Act - // note: don't await here because we want to test the error - const result = tokenService.setSecurityStamp(mockSecurityStamp); - // Assert - await expect(result).rejects.toThrow("User id not found. Cannot set security stamp."); - }); - - it("should set the security stamp in memory when there is an active user in global state", async () => { - // Arrange - globalStateProvider - .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) - .stateSubject.next(userIdFromAccessToken); - - // Act - await tokenService.setSecurityStamp(mockSecurityStamp); - - // Assert - expect( - singleUserStateProvider.getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY).nextMock, - ).toHaveBeenCalledWith(mockSecurityStamp); - }); - - it("should set the security stamp in memory for the specified user id", async () => { - // Act - await tokenService.setSecurityStamp(mockSecurityStamp, userIdFromAccessToken); - - // Assert - expect( - singleUserStateProvider.getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY).nextMock, - ).toHaveBeenCalledWith(mockSecurityStamp); - }); - }); - - describe("getSecurityStamp", () => { - it("should throw an error if no user id is provided and there is no active user in global state", async () => { - // Act - // note: don't await here because we want to test the error - const result = tokenService.getSecurityStamp(); - // Assert - await expect(result).rejects.toThrow("User id not found. Cannot get security stamp."); - }); - - it("should return the security stamp from memory with no user id specified (uses global active user)", async () => { - // Arrange - globalStateProvider - .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) - .stateSubject.next(userIdFromAccessToken); - - singleUserStateProvider - .getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY) - .stateSubject.next([userIdFromAccessToken, mockSecurityStamp]); - - // Act - const result = await tokenService.getSecurityStamp(); - - // Assert - expect(result).toEqual(mockSecurityStamp); - }); - - it("should return the security stamp from memory for the specified user id", async () => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY) - .stateSubject.next([userIdFromAccessToken, mockSecurityStamp]); - - // Act - const result = await tokenService.getSecurityStamp(userIdFromAccessToken); - // Assert - expect(result).toEqual(mockSecurityStamp); - }); - }); - }); - // Helpers function createTokenService(supportsSecureStorage: boolean) { return new TokenService( diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index 40036a8453..c24a2c186b 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -32,7 +32,6 @@ import { EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL, REFRESH_TOKEN_DISK, REFRESH_TOKEN_MEMORY, - SECURITY_STAMP_MEMORY, } from "./token.state"; export enum TokenStorageLocation { @@ -851,30 +850,6 @@ export class TokenService implements TokenServiceAbstraction { return Array.isArray(decoded.amr) && decoded.amr.includes("external"); } - async getSecurityStamp(userId?: UserId): Promise { - userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$); - - if (!userId) { - throw new Error("User id not found. Cannot get security stamp."); - } - - const securityStamp = await this.getStateValueByUserIdAndKeyDef(userId, SECURITY_STAMP_MEMORY); - - return securityStamp; - } - - async setSecurityStamp(securityStamp: string, userId?: UserId): Promise { - userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$); - - if (!userId) { - throw new Error("User id not found. Cannot set security stamp."); - } - - await this.singleUserStateProvider - .get(userId, SECURITY_STAMP_MEMORY) - .update((_) => securityStamp); - } - private async getStateValueByUserIdAndKeyDef( userId: UserId, storageLocation: UserKeyDefinition, diff --git a/libs/common/src/auth/services/token.state.spec.ts b/libs/common/src/auth/services/token.state.spec.ts index bb82410fac..dc00fec383 100644 --- a/libs/common/src/auth/services/token.state.spec.ts +++ b/libs/common/src/auth/services/token.state.spec.ts @@ -10,7 +10,6 @@ import { EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL, REFRESH_TOKEN_DISK, REFRESH_TOKEN_MEMORY, - SECURITY_STAMP_MEMORY, } from "./token.state"; describe.each([ @@ -23,7 +22,6 @@ describe.each([ [API_KEY_CLIENT_ID_MEMORY, "apiKeyClientIdMemory"], [API_KEY_CLIENT_SECRET_DISK, "apiKeyClientSecretDisk"], [API_KEY_CLIENT_SECRET_MEMORY, "apiKeyClientSecretMemory"], - [SECURITY_STAMP_MEMORY, "securityStamp"], ])( "deserializes state key definitions", ( diff --git a/libs/common/src/auth/services/token.state.ts b/libs/common/src/auth/services/token.state.ts index 57d85f2a55..458d6846c1 100644 --- a/libs/common/src/auth/services/token.state.ts +++ b/libs/common/src/auth/services/token.state.ts @@ -69,8 +69,3 @@ export const API_KEY_CLIENT_SECRET_MEMORY = new UserKeyDefinition( clearOn: [], // Manually handled }, ); - -export const SECURITY_STAMP_MEMORY = new UserKeyDefinition(TOKEN_MEMORY, "securityStamp", { - deserializer: (securityStamp) => securityStamp, - clearOn: ["logout"], -}); diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index f1d4b3848e..051604f0ae 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -181,6 +181,8 @@ export abstract class StateService { * Sets the user's Pin, encrypted by the user key */ setProtectedPin: (value: string, options?: StorageOptions) => Promise; + getSecurityStamp: (options?: StorageOptions) => Promise; + setSecurityStamp: (value: string, options?: StorageOptions) => Promise; getUserId: (options?: StorageOptions) => Promise; getVaultTimeout: (options?: StorageOptions) => Promise; setVaultTimeout: (value: number, options?: StorageOptions) => Promise; diff --git a/libs/common/src/platform/models/domain/account-tokens.spec.ts b/libs/common/src/platform/models/domain/account-tokens.spec.ts new file mode 100644 index 0000000000..733b3908e9 --- /dev/null +++ b/libs/common/src/platform/models/domain/account-tokens.spec.ts @@ -0,0 +1,9 @@ +import { AccountTokens } from "./account"; + +describe("AccountTokens", () => { + describe("fromJSON", () => { + it("should deserialize to an instance of itself", () => { + expect(AccountTokens.fromJSON({})).toBeInstanceOf(AccountTokens); + }); + }); +}); diff --git a/libs/common/src/platform/models/domain/account.spec.ts b/libs/common/src/platform/models/domain/account.spec.ts index 77c242b6ff..0c76c16cc2 100644 --- a/libs/common/src/platform/models/domain/account.spec.ts +++ b/libs/common/src/platform/models/domain/account.spec.ts @@ -1,4 +1,4 @@ -import { Account, AccountKeys, AccountProfile, AccountSettings } from "./account"; +import { Account, AccountKeys, AccountProfile, AccountSettings, AccountTokens } from "./account"; describe("Account", () => { describe("fromJSON", () => { @@ -10,12 +10,14 @@ describe("Account", () => { const keysSpy = jest.spyOn(AccountKeys, "fromJSON"); const profileSpy = jest.spyOn(AccountProfile, "fromJSON"); const settingsSpy = jest.spyOn(AccountSettings, "fromJSON"); + const tokensSpy = jest.spyOn(AccountTokens, "fromJSON"); Account.fromJSON({}); expect(keysSpy).toHaveBeenCalled(); expect(profileSpy).toHaveBeenCalled(); expect(settingsSpy).toHaveBeenCalled(); + expect(tokensSpy).toHaveBeenCalled(); }); }); }); diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index cd416ec1f9..5a9a764696 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -171,11 +171,24 @@ export class AccountSettings { } } +export class AccountTokens { + securityStamp?: string; + + static fromJSON(obj: Jsonify): AccountTokens { + if (obj == null) { + return null; + } + + return Object.assign(new AccountTokens(), obj); + } +} + export class Account { data?: AccountData = new AccountData(); keys?: AccountKeys = new AccountKeys(); profile?: AccountProfile = new AccountProfile(); settings?: AccountSettings = new AccountSettings(); + tokens?: AccountTokens = new AccountTokens(); constructor(init: Partial) { Object.assign(this, { @@ -195,6 +208,10 @@ export class Account { ...new AccountSettings(), ...init?.settings, }, + tokens: { + ...new AccountTokens(), + ...init?.tokens, + }, }); } @@ -208,6 +225,7 @@ export class Account { data: AccountData.fromJSON(json?.data), profile: AccountProfile.fromJSON(json?.profile), settings: AccountSettings.fromJSON(json?.settings), + tokens: AccountTokens.fromJSON(json?.tokens), }); } } diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index d0a55d7a47..f660cd7a34 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -839,6 +839,23 @@ export class StateService< ); } + async getSecurityStamp(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) + )?.tokens?.securityStamp; + } + + async setSecurityStamp(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultInMemoryOptions()), + ); + account.tokens.securityStamp = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultInMemoryOptions()), + ); + } + async getUserId(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) diff --git a/libs/common/src/vault/services/sync/sync.service.ts b/libs/common/src/vault/services/sync/sync.service.ts index 73869ff488..ff8e9f1f4f 100644 --- a/libs/common/src/vault/services/sync/sync.service.ts +++ b/libs/common/src/vault/services/sync/sync.service.ts @@ -15,7 +15,6 @@ import { AccountService } from "../../../auth/abstractions/account.service"; import { AvatarService } from "../../../auth/abstractions/avatar.service"; import { KeyConnectorService } from "../../../auth/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "../../../auth/abstractions/master-password.service.abstraction"; -import { TokenService } from "../../../auth/abstractions/token.service"; import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; import { BillingAccountProfileStateService } from "../../../billing/abstractions/account/billing-account-profile-state.service"; @@ -74,7 +73,6 @@ export class SyncService implements SyncServiceAbstraction { private avatarService: AvatarService, private logoutCallback: (expired: boolean) => Promise, private billingAccountProfileStateService: BillingAccountProfileStateService, - private tokenService: TokenService, ) {} async getLastSync(): Promise { @@ -311,7 +309,7 @@ export class SyncService implements SyncServiceAbstraction { } private async syncProfile(response: ProfileResponse) { - const stamp = await this.tokenService.getSecurityStamp(response.id as UserId); + const stamp = await this.stateService.getSecurityStamp(); if (stamp != null && stamp !== response.securityStamp) { if (this.logoutCallback != null) { await this.logoutCallback(true); @@ -325,7 +323,7 @@ export class SyncService implements SyncServiceAbstraction { await this.cryptoService.setProviderKeys(response.providers); await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations); await this.avatarService.setSyncAvatarColor(response.id as UserId, response.avatarColor); - await this.tokenService.setSecurityStamp(response.securityStamp, response.id as UserId); + await this.stateService.setSecurityStamp(response.securityStamp); await this.stateService.setEmailVerified(response.emailVerified); await this.billingAccountProfileStateService.setHasPremium( From b395cb40a7a0e35d836009028f4f32242858d0f7 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Mon, 22 Apr 2024 09:32:44 -0700 Subject: [PATCH 60/86] [AC-1999] Fix deleting collections from collection dialog (#8647) * [AC-1999] Fix null check this.collection can be both null or unassigned and `!= null` will handle both cases. * [AC-1999] Navigate away when selected collection is deleted --------- Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com> --- .../vault/individual-vault/vault.component.ts | 12 +++++++++--- .../vault-header/vault-header.component.ts | 2 +- .../src/app/vault/org-vault/vault.component.ts | 18 ++++++++++++++---- 3 files changed, 24 insertions(+), 8 deletions(-) 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 a25ba6edbc..2c20328336 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -679,6 +679,14 @@ export class VaultComponent implements OnInit, OnDestroy { } else if (result.action === CollectionDialogAction.Deleted) { await this.collectionService.delete(result.collection?.id); this.refresh(); + // Navigate away if we deleted the collection we were viewing + if (this.selectedCollection?.node.id === c?.id) { + void this.router.navigate([], { + queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null }, + queryParamsHandling: "merge", + replaceUrl: true, + }); + } } } @@ -710,9 +718,7 @@ export class VaultComponent implements OnInit, OnDestroy { ); // Navigate away if we deleted the collection we were viewing if (this.selectedCollection?.node.id === collection.id) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([], { + void this.router.navigate([], { queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null }, queryParamsHandling: "merge", replaceUrl: true, 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 c4c67759c7..a5cd468008 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 @@ -80,7 +80,7 @@ export class VaultHeaderComponent implements OnInit { ? this.i18nService.t("collections").toLowerCase() : this.i18nService.t("vault").toLowerCase(); - if (this.collection !== undefined) { + if (this.collection != null) { return this.collection.node.name; } diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 587758dda1..9de404e969 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -958,11 +958,9 @@ export class VaultComponent implements OnInit, OnDestroy { this.i18nService.t("deletedCollectionId", collection.name), ); - // Navigate away if we deleted the colletion we were viewing + // Navigate away if we deleted the collection we were viewing if (this.selectedCollection?.node.id === collection.id) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([], { + void this.router.navigate([], { queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null }, queryParamsHandling: "merge", replaceUrl: true, @@ -1095,6 +1093,18 @@ export class VaultComponent implements OnInit, OnDestroy { result.action === CollectionDialogAction.Deleted ) { this.refresh(); + + // If we deleted the selected collection, navigate up/away + if ( + result.action === CollectionDialogAction.Deleted && + this.selectedCollection?.node.id === c?.id + ) { + void this.router.navigate([], { + queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null }, + queryParamsHandling: "merge", + replaceUrl: true, + }); + } } } From fb211c5feea0594ccdd992c2729154424376bc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 22 Apr 2024 18:58:29 +0200 Subject: [PATCH 61/86] Add rust analyzer support for desktop-native (#8830) --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 27e3a9b293..3a70af3481 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,6 @@ "**/locales/*[^n]/messages.json": true, "**/_locales/[^e]*/messages.json": true, "**/_locales/*[^n]/messages.json": true - } + }, + "rust-analyzer.linkedProjects": ["apps/desktop/desktop_native/Cargo.toml"] } From 29d4f1aad5fade6c85749e002a4d0b1a05f2fdbb Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Mon, 22 Apr 2024 12:58:20 -0500 Subject: [PATCH 62/86] [PM-7660] Master Password Re-Prompt from Autofill Not Working (#8862) --- .../browser/src/background/main.background.ts | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 15f21b2501..a64ee2b8a0 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1105,20 +1105,22 @@ export default class MainBackground { await (this.eventUploadService as EventUploadService).init(true); this.twoFactorService.init(); - if (!this.popupOnlyContext) { - await this.vaultTimeoutService.init(true); - this.fido2Background.init(); - await this.runtimeBackground.init(); - await this.notificationBackground.init(); - this.filelessImporterBackground.init(); - await this.commandsBackground.init(); - await this.overlayBackground.init(); - await this.tabsBackground.init(); - this.contextMenusBackground?.init(); - await this.idleBackground.init(); - if (BrowserApi.isManifestVersion(2)) { - await this.webRequestBackground.init(); - } + if (this.popupOnlyContext) { + return; + } + + await this.vaultTimeoutService.init(true); + this.fido2Background.init(); + await this.runtimeBackground.init(); + await this.notificationBackground.init(); + this.filelessImporterBackground.init(); + await this.commandsBackground.init(); + await this.overlayBackground.init(); + await this.tabsBackground.init(); + this.contextMenusBackground?.init(); + await this.idleBackground.init(); + if (BrowserApi.isManifestVersion(2)) { + await this.webRequestBackground.init(); } if (this.platformUtilsService.isFirefox() && !this.isPrivateMode) { From e29779875797cb8a508d3857aa25ecefca5aa5c1 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:54:41 -0400 Subject: [PATCH 63/86] Stop CryptoService from using `getBgService` (#8843) --- .../src/popup/services/services.module.ts | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index a7da6b7612..b344a18492 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -31,6 +31,7 @@ import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/ab import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; @@ -55,6 +56,7 @@ import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt. import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; @@ -63,6 +65,7 @@ import { AbstractStorageService, ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; +import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- Used for dependency injection @@ -102,6 +105,7 @@ import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service"; import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service"; import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; +import { BrowserCryptoService } from "../../platform/services/browser-crypto.service"; import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service"; import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service"; import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service"; @@ -232,12 +236,45 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: CryptoService, - useFactory: (encryptService: EncryptService) => { - const cryptoService = getBgService("cryptoService")(); + useFactory: ( + masterPasswordService: InternalMasterPasswordServiceAbstraction, + keyGenerationService: KeyGenerationService, + cryptoFunctionService: CryptoFunctionService, + encryptService: EncryptService, + platformUtilsService: PlatformUtilsService, + logService: LogService, + stateService: StateServiceAbstraction, + accountService: AccountServiceAbstraction, + stateProvider: StateProvider, + biometricStateService: BiometricStateService, + ) => { + const cryptoService = new BrowserCryptoService( + masterPasswordService, + keyGenerationService, + cryptoFunctionService, + encryptService, + platformUtilsService, + logService, + stateService, + accountService, + stateProvider, + biometricStateService, + ); new ContainerService(cryptoService, encryptService).attachToGlobal(self); return cryptoService; }, - deps: [EncryptService], + deps: [ + InternalMasterPasswordServiceAbstraction, + KeyGenerationService, + CryptoFunctionService, + EncryptService, + PlatformUtilsService, + LogService, + StateServiceAbstraction, + AccountServiceAbstraction, + StateProvider, + BiometricStateService, + ], }), safeProvider({ provide: TotpServiceAbstraction, From 33dae77a4d9caab3a0ea88ec89d10efb38097a84 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Mon, 22 Apr 2024 17:11:30 -0400 Subject: [PATCH 64/86] Revert "Stop CryptoService from using `getBgService` (#8843)" (#8867) This reverts commit e29779875797cb8a508d3857aa25ecefca5aa5c1. --- .../src/popup/services/services.module.ts | 43 ++----------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index b344a18492..a7da6b7612 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -31,7 +31,6 @@ import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/ab import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; @@ -56,7 +55,6 @@ import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt. import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; @@ -65,7 +63,6 @@ import { AbstractStorageService, ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; -import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- Used for dependency injection @@ -105,7 +102,6 @@ import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service"; import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service"; import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; -import { BrowserCryptoService } from "../../platform/services/browser-crypto.service"; import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service"; import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service"; import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service"; @@ -236,45 +232,12 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: CryptoService, - useFactory: ( - masterPasswordService: InternalMasterPasswordServiceAbstraction, - keyGenerationService: KeyGenerationService, - cryptoFunctionService: CryptoFunctionService, - encryptService: EncryptService, - platformUtilsService: PlatformUtilsService, - logService: LogService, - stateService: StateServiceAbstraction, - accountService: AccountServiceAbstraction, - stateProvider: StateProvider, - biometricStateService: BiometricStateService, - ) => { - const cryptoService = new BrowserCryptoService( - masterPasswordService, - keyGenerationService, - cryptoFunctionService, - encryptService, - platformUtilsService, - logService, - stateService, - accountService, - stateProvider, - biometricStateService, - ); + useFactory: (encryptService: EncryptService) => { + const cryptoService = getBgService("cryptoService")(); new ContainerService(cryptoService, encryptService).attachToGlobal(self); return cryptoService; }, - deps: [ - InternalMasterPasswordServiceAbstraction, - KeyGenerationService, - CryptoFunctionService, - EncryptService, - PlatformUtilsService, - LogService, - StateServiceAbstraction, - AccountServiceAbstraction, - StateProvider, - BiometricStateService, - ], + deps: [EncryptService], }), safeProvider({ provide: TotpServiceAbstraction, From 4afb5d04f00c4a4a883d3dfc20a8752f929880d6 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Mon, 22 Apr 2024 17:14:14 -0400 Subject: [PATCH 65/86] Remove `alarms` Permission (#8866) --- apps/browser/src/manifest.v3.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index cdd0869fc5..26efbbbf9b 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -59,7 +59,6 @@ "clipboardRead", "clipboardWrite", "idle", - "alarms", "scripting", "offscreen" ], From 714ca66f331ec4ffde3a3e40fc767b27a4967903 Mon Sep 17 00:00:00 2001 From: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Date: Tue, 23 Apr 2024 07:32:09 -0400 Subject: [PATCH 66/86] Bumped browser,cli,desktop,web version to (#8875) --- 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 ee6d100572..506f19f279 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2024.4.1", + "version": "2024.4.2", "scripts": { "build": "webpack", "build:mv3": "cross-env MANIFEST_VERSION=3 webpack", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 78f1e2cc41..18bfaf8acb 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.4.1", + "version": "2024.4.2", "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 26efbbbf9b..c0c88706b8 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.4.1", + "version": "2024.4.2", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/cli/package.json b/apps/cli/package.json index 690842d831..b06caacfd4 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.3.1", + "version": "2024.4.0", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 4bb0ab2d93..5e098eb213 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.4.2", + "version": "2024.4.3", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 11b38bd273..d6945fd16e 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2024.4.2", + "version": "2024.4.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2024.4.2", + "version": "2024.4.3", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native", diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index a65dab016c..fa190af9a6 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.4.2", + "version": "2024.4.3", "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 55fe0987d7..434712cdf4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2024.4.1", + "version": "2024.4.2", "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 d72ba9cb19..f5932a25b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -193,11 +193,11 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2024.4.1" + "version": "2024.4.2" }, "apps/cli": { "name": "@bitwarden/cli", - "version": "2024.3.1", + "version": "2024.4.0", "license": "GPL-3.0-only", "dependencies": { "@koa/multer": "3.0.2", @@ -233,7 +233,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2024.4.2", + "version": "2024.4.3", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -247,7 +247,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2024.4.1" + "version": "2024.4.2" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From e4ebf4aeccb4c214e0b483113d9b9b53cc6f7438 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Tue, 23 Apr 2024 09:18:49 -0400 Subject: [PATCH 67/86] [PM-7349] Update snap description with new URL to help docs (#8703) * Updated snap summary with new URL to help docs. * Updated to use summary and description. --- apps/desktop/electron-builder.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 4f0d05581c..960d56b036 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -228,7 +228,8 @@ "artifactName": "${productName}-${version}-${arch}.${ext}" }, "snap": { - "summary": "After installation enable required `password-manager-service` by running `sudo snap connect bitwarden:password-manager-service`.", + "summary": "Bitwarden is a secure and free password manager for all of your devices.", + "description": "**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.", "autoStart": true, "base": "core22", "confinement": "strict", From 73d0782b6ce4a0be1cf0480d0a9658420b5ef438 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Tue, 23 Apr 2024 09:45:11 -0400 Subject: [PATCH 68/86] [CL-110] fix code block text color in Storybook (#8868) --- libs/components/src/styles.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/components/src/styles.scss b/libs/components/src/styles.scss index f03d2dd340..ae97838e09 100644 --- a/libs/components/src/styles.scss +++ b/libs/components/src/styles.scss @@ -47,3 +47,8 @@ $card-icons-base: "../../src/billing/images/cards/"; @import "bootstrap/scss/_print"; @import "multi-select/scss/bw.theme.scss"; + +// Workaround for https://bitwarden.atlassian.net/browse/CL-110 +#storybook-docs pre.prismjs { + color: white; +} From 7d58b21856c5551dadd7127c725be2882076cf20 Mon Sep 17 00:00:00 2001 From: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> Date: Tue, 23 Apr 2024 19:20:15 +0530 Subject: [PATCH 69/86] migrating activate autofill component (#8782) --- .../policies/activate-autofill.component.html | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.html b/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.html index 9f08e98dae..94f2e8a422 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.html @@ -5,15 +5,7 @@ }} -
-
- - -
-
+ + + {{ "turnOn" | i18n }} + From 38ea110755a4256f96ba7946b680070b5c32cdc6 Mon Sep 17 00:00:00 2001 From: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> Date: Tue, 23 Apr 2024 19:22:26 +0530 Subject: [PATCH 70/86] migrating two factor authentication component (#8760) --- .../two-factor-authentication.component.html | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.html b/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.html index 3286c08689..d0be72a52e 100644 --- a/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.html @@ -2,15 +2,7 @@ {{ "twoStepLoginPolicyWarning" | i18n }} -
-
- - -
-
+ + + {{ "turnOn" | i18n }} + From 5f3844aa38de27dff8986b399a5db327a6101c04 Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:26:31 -0400 Subject: [PATCH 71/86] Getting the user's access token for file upload (#8877) --- libs/common/src/abstractions/api.service.ts | 9 ++++++++- libs/common/src/services/api.service.ts | 5 +++-- libs/common/src/services/event/event-upload.service.ts | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 9b3160ee19..c1a0e1f9cd 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -103,6 +103,7 @@ import { EventResponse } from "../models/response/event.response"; import { ListResponse } from "../models/response/list.response"; import { ProfileResponse } from "../models/response/profile.response"; import { UserKeyResponse } from "../models/response/user-key.response"; +import { UserId } from "../types/guid"; import { AttachmentRequest } from "../vault/models/request/attachment.request"; import { CipherBulkDeleteRequest } from "../vault/models/request/cipher-bulk-delete.request"; import { CipherBulkMoveRequest } from "../vault/models/request/cipher-bulk-move.request"; @@ -451,7 +452,13 @@ export abstract class ApiService { end: string, token: string, ) => Promise>; - postEventsCollect: (request: EventRequest[]) => Promise; + + /** + * Posts events for a user + * @param request The array of events to upload + * @param userId The optional user id the events belong to. If no user id is provided the active user id is used. + */ + postEventsCollect: (request: EventRequest[], userId?: UserId) => Promise; deleteSsoUser: (organizationId: string) => Promise; getSsoUserIdentifier: () => Promise; diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index e8135f3d6c..84fa7bd077 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -118,6 +118,7 @@ import { EnvironmentService } from "../platform/abstractions/environment.service import { PlatformUtilsService } from "../platform/abstractions/platform-utils.service"; import { StateService } from "../platform/abstractions/state.service"; import { Utils } from "../platform/misc/utils"; +import { UserId } from "../types/guid"; import { AttachmentRequest } from "../vault/models/request/attachment.request"; import { CipherBulkDeleteRequest } from "../vault/models/request/cipher-bulk-delete.request"; import { CipherBulkMoveRequest } from "../vault/models/request/cipher-bulk-move.request"; @@ -1423,8 +1424,8 @@ export class ApiService implements ApiServiceAbstraction { return new ListResponse(r, EventResponse); } - async postEventsCollect(request: EventRequest[]): Promise { - const authHeader = await this.getActiveBearerToken(); + async postEventsCollect(request: EventRequest[], userId?: UserId): Promise { + const authHeader = await this.tokenService.getAccessToken(userId); const headers = new Headers({ "Device-Type": this.deviceType, Authorization: "Bearer " + authHeader, diff --git a/libs/common/src/services/event/event-upload.service.ts b/libs/common/src/services/event/event-upload.service.ts index 6f229751bf..c87d3b2024 100644 --- a/libs/common/src/services/event/event-upload.service.ts +++ b/libs/common/src/services/event/event-upload.service.ts @@ -70,7 +70,7 @@ export class EventUploadService implements EventUploadServiceAbstraction { return req; }); try { - await this.apiService.postEventsCollect(request); + await this.apiService.postEventsCollect(request, userId); } catch (e) { this.logService.error(e); // Add the events back to state if there was an error and they were not uploaded. From ca38a5bc1f76f816de4f5c049d69b2fba8c33b54 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:29:47 +0000 Subject: [PATCH 72/86] Autosync the updated translations (#8878) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/cy/messages.json | 8 +- apps/browser/src/_locales/de/messages.json | 20 ++-- apps/browser/src/_locales/ko/messages.json | 44 ++++----- apps/browser/src/_locales/nl/messages.json | 6 +- apps/browser/src/_locales/pl/messages.json | 4 +- apps/browser/src/_locales/pt_BR/messages.json | 96 +++++++++---------- apps/browser/src/_locales/sr/messages.json | 16 ++-- apps/browser/src/_locales/zh_CN/messages.json | 12 +-- apps/browser/store/locales/de/copy.resx | 56 +++++------ apps/browser/store/locales/ko/copy.resx | 4 +- apps/browser/store/locales/pl/copy.resx | 60 ++++++------ apps/browser/store/locales/pt_BR/copy.resx | 6 +- apps/browser/store/locales/zh_CN/copy.resx | 60 ++++++------ 13 files changed, 195 insertions(+), 197 deletions(-) diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index c718c1d876..2be868872c 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -23,7 +23,7 @@ "message": "Enterprise single sign-on" }, "cancel": { - "message": "Cancel" + "message": "Canslo" }, "close": { "message": "Cau" @@ -318,7 +318,7 @@ "message": "Golygu" }, "view": { - "message": "View" + "message": "Gweld" }, "noItemsInList": { "message": "Does dim eitemau i'w rhestru." @@ -549,10 +549,10 @@ "message": "Ydych chi'n siŵr eich bod am allgofnodi?" }, "yes": { - "message": "Yes" + "message": "Ydw" }, "no": { - "message": "No" + "message": "Na" }, "unexpectedError": { "message": "An unexpected error has occurred." diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 8f2a59af1e..d55d499b3c 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden Passwortmanager", "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": "Zu Hause, am Arbeitsplatz oder unterwegs schützt Bitwarden einfach alle deine Passwörter, Passkeys und vertraulichen Informationen.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -173,10 +173,10 @@ "message": "Master-Passwort ändern" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Weiter zur Web-App?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Du kannst dein Master-Passwort in der Bitwarden Web-App ändern." }, "fingerprintPhrase": { "message": "Fingerabdruck-Phrase", @@ -3001,7 +3001,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Erfolg" }, "removePasskey": { "message": "Passkey entfernen" @@ -3010,21 +3010,21 @@ "message": "Passkey entfernt" }, "unassignedItemsBannerNotice": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." + "message": "Hinweis: Nicht zugeordnete Organisationseinträge sind nicht mehr in der Ansicht aller Tresore sichtbar und nur über die Administrator-Konsole zugänglich." }, "unassignedItemsBannerSelfHostNotice": { - "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + "message": "Hinweis: Ab dem 16. Mai 2024 sind nicht zugewiesene Organisationselemente nicht mehr in der Ansicht aller Tresore sichtbar und nur über die Administrator-Konsole zugänglich." }, "unassignedItemsBannerCTAPartOne": { - "message": "Assign these items to a collection from the", + "message": "Weise diese Einträge einer Sammlung aus der", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "unassignedItemsBannerCTAPartTwo": { - "message": "to make them visible.", + "message": "zu, um sie sichtbar zu machen.", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "adminConsole": { - "message": "Admin Console" + "message": "Administrator-Konsole" }, "errorAssigningTargetCollection": { "message": "Fehler beim Zuweisen der Ziel-Sammlung." diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 4bc4302f8b..1724225b0e 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden 비밀번호 관리자", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", + "message": "집에서도, 직장에서도, 이동 중에도 Bitwarden은 비밀번호, 패스키, 민감 정보를 쉽게 보호합니다.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -173,7 +173,7 @@ "message": "마스터 비밀번호 변경" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "웹 앱에서 계속하시겠용?" }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." @@ -688,10 +688,10 @@ "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." }, "enableUsePasskeys": { - "message": "Ask to save and use passkeys" + "message": "패스키를 저장 및 사용할지 묻기" }, "usePasskeysDesc": { - "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." + "message": "보관함에 새 패스키를 저장하거나 로그인할지 물어봅니다. 모든 로그인된 계정에 적용됩니다." }, "notificationChangeDesc": { "message": "Bitwarden에 저장되어 있는 비밀번호를 이 비밀번호로 변경하시겠습니까?" @@ -2786,55 +2786,55 @@ "message": "Confirm file password" }, "typePasskey": { - "message": "Passkey" + "message": "패스키" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "패스키가 복사되지 않습니다" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "패스키는 복제된 아이템에 복사되지 않습니다. 계속 이 항목을 복제하시겠어요?" }, "passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": { - "message": "Verification required by the initiating site. This feature is not yet implemented for accounts without master password." + "message": "사이트에서 인증을 요구합니다. 이 기능은 비밀번호가 없는 계정에서는 아직 지원하지 않습니다." }, "logInWithPasskey": { - "message": "Log in with passkey?" + "message": "패스키로 로그인하시겠어요?" }, "passkeyAlreadyExists": { - "message": "A passkey already exists for this application." + "message": "이미 이 애플리케이션에 해당하는 패스키가 있습니다." }, "noPasskeysFoundForThisApplication": { - "message": "No passkeys found for this application." + "message": "이 애플리케이션에 대한 패스키를 찾을 수 없습니다." }, "noMatchingPasskeyLogin": { - "message": "You do not have a matching login for this site." + "message": "사이트와 일치하는 로그인이 없습니다." }, "confirm": { "message": "Confirm" }, "savePasskey": { - "message": "Save passkey" + "message": "패스키 저장" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "새 로그인으로 패스키 저장" }, "choosePasskey": { - "message": "Choose a login to save this passkey to" + "message": "패스키를 저장할 로그인 선택하기" }, "passkeyItem": { - "message": "Passkey Item" + "message": "패스키 항목" }, "overwritePasskey": { - "message": "Overwrite passkey?" + "message": "비밀번호를 덮어쓰시겠어요?" }, "overwritePasskeyAlert": { - "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + "message": "이 항목은 이미 패스키가 있습니다. 정말로 현재 패스키를 덮어쓰시겠어요?" }, "featureNotSupported": { "message": "Feature not yet supported" }, "yourPasskeyIsLocked": { - "message": "Authentication required to use passkey. Verify your identity to continue." + "message": "패스키를 사용하려면 인증이 필요합니다. 인증을 진행해주세요." }, "multifactorAuthenticationCancelled": { "message": "Multifactor authentication cancelled" @@ -3004,10 +3004,10 @@ "message": "Success" }, "removePasskey": { - "message": "Remove passkey" + "message": "패스키 제거" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "패스키 제거됨" }, "unassignedItemsBannerNotice": { "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 5a52b4f7ef..f1424df0b9 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -531,7 +531,7 @@ "message": "Kan de QR-code van de huidige webpagina niet scannen" }, "totpCaptureSuccess": { - "message": "Authenticatie-sleutel toegevoegd" + "message": "Authenticatiesleutel toegevoegd" }, "totpCapture": { "message": "Scan de authenticatie-QR-code van de huidige webpagina" @@ -1673,10 +1673,10 @@ "message": "Browserintegratie is niet ingeschakeld in de Bitwarden-desktopapplicatie. Schakel deze optie in de instellingen binnen de desktop-applicatie in." }, "startDesktopTitle": { - "message": "Bitwarden-desktopapplicatie opstarten" + "message": "Bitwarden desktopapplicatie opstarten" }, "startDesktopDesc": { - "message": "Je moet de Bitwarden-desktopapplicatie starten om deze functie te gebruiken." + "message": "Je moet de Bitwarden desktopapplicatie starten om deze functie te gebruiken." }, "errorEnableBiometricTitle": { "message": "Kon biometrie niet inschakelen" diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index e768a70d52..9b3e8f20fc 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Menedżer Haseł Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", + "message": "W domu, w pracy, lub w ruchu, Bitwarden z łatwością zabezpiecza wszystkie Twoje hasła, passkeys i poufne informacje.", "description": "Extension description" }, "loginOrCreateNewAccount": { diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index c6e62fbd4f..ef2b6f2dca 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden Gerenciador de Senhas", "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": "Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -173,10 +173,10 @@ "message": "Alterar Senha Mestra" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Continuar no aplicativo web?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Você pode alterar a sua senha mestra no aplicativo web Bitwarden." }, "fingerprintPhrase": { "message": "Frase Biométrica", @@ -500,10 +500,10 @@ "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Você logou na sua conta com sucesso" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Você pode fechar esta janela" }, "masterPassSent": { "message": "Enviamos um e-mail com a dica da sua senha mestra." @@ -1500,7 +1500,7 @@ "message": "Código PIN inválido." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "Muitas tentativas de entrada de PIN inválidas. Desconectando." }, "unlockWithBiometrics": { "message": "Desbloquear com a biometria" @@ -2005,7 +2005,7 @@ "message": "Selecionar pasta..." }, "noFoldersFound": { - "message": "No folders found", + "message": "Nenhuma pasta encontrada", "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { @@ -2017,7 +2017,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "verificationRequired": { - "message": "Verification required", + "message": "Verificação necessária", "description": "Default title for the user verification dialog." }, "hours": { @@ -2652,40 +2652,40 @@ } }, "tryAgain": { - "message": "Try again" + "message": "Tentar novamente" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "Verificação necessária para esta ação. Defina um PIN para continuar." }, "setPin": { - "message": "Set PIN" + "message": "Definir PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "Verificiar com biometria" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "Aguardando confirmação" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "Não foi possível completar a biometria." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "Precisa de um método diferente?" }, "useMasterPassword": { - "message": "Use master password" + "message": "Usar a senha mestra" }, "usePin": { - "message": "Use PIN" + "message": "Usar PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "Usar biometria" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "Digite o código de verificação que foi enviado para o seu e-mail." }, "resendCode": { - "message": "Resend code" + "message": "Reenviar código" }, "total": { "message": "Total" @@ -2700,19 +2700,19 @@ } }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "Inicie o Duo e siga os passos para finalizar o login." }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "A autenticação em duas etapas do Duo é necessária para sua conta." }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "Abra a extensão para concluir o login." }, "popoutExtension": { - "message": "Popout extension" + "message": "Extensão pop-out" }, "launchDuo": { - "message": "Launch Duo" + "message": "Abrir o Duo" }, "importFormatError": { "message": "Os dados não estão formatados corretamente. Por favor, verifique o seu arquivo de importação e tente novamente." @@ -2846,13 +2846,13 @@ "message": "Nome de usuário ou senha incorretos" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "Senha incorreta" }, "incorrectCode": { - "message": "Incorrect code" + "message": "Código incorreto" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "PIN incorreto" }, "multifactorAuthenticationFailed": { "message": "Falha na autenticação de múltiplos fatores" @@ -2965,71 +2965,71 @@ "description": "Label indicating the most common import formats" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "Tornar o Bitwarden seu gerenciador de senhas padrão?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between the Bitwarden auto-fill menu and your browser's.", + "message": "Ignorar esta opção pode causar conflitos entre o menu de autopreenchimento do Bitwarden e o do seu navegador.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "Faça do Bitwarden seu gerenciador de senhas padrão", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "Não é possível definir o Bitwarden como o gerenciador de senhas padrão", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.", + "message": "Você deve conceder permissões de privacidade do navegador ao Bitwarden para defini-lo como o Gerenciador de Senhas padrão.", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "Tornar padrão", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "Credenciais salvas com sucesso!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "Credenciais atualizadas com sucesso!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "Erro ao salvar credenciais. Verifique o console para detalhes.", "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Sucesso" }, "removePasskey": { - "message": "Remove passkey" + "message": "Remover senha" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Chave de acesso removida" }, "unassignedItemsBannerNotice": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." + "message": "Aviso: Itens da organização não atribuídos não estão mais visíveis na visualização Todos os Cofres e só são acessíveis por meio do painel de administração." }, "unassignedItemsBannerSelfHostNotice": { - "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + "message": "Aviso: Em 16 de maio, 2024, itens da organização não serão mais visíveis na visualização Todos os Cofres e só serão acessíveis por meio do painel de administração." }, "unassignedItemsBannerCTAPartOne": { - "message": "Assign these items to a collection from the", + "message": "Atribua estes itens a uma coleção da", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "unassignedItemsBannerCTAPartTwo": { - "message": "to make them visible.", + "message": "para torná-los visíveis.", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "adminConsole": { - "message": "Admin Console" + "message": "Painel de administração" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Erro ao atribuir coleção de destino." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Erro ao atribuir pasta de destino." } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 5819546800..acac4d14c6 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -173,10 +173,10 @@ "message": "Промени главну лозинку" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Ићи на веб апликацију?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Можете променити главну лозинку на Bitwarden веб апликацији." }, "fingerprintPhrase": { "message": "Сигурносна Фраза Сефа", @@ -3001,7 +3001,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Успех" }, "removePasskey": { "message": "Уклонити приступачни кључ" @@ -3010,10 +3010,10 @@ "message": "Приступачни кључ је уклоњен" }, "unassignedItemsBannerNotice": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." + "message": "Напомена: Недодељене ставке организације више нису видљиве у приказу Сви сефови и доступне су само преко Админ конзоле." }, "unassignedItemsBannerSelfHostNotice": { - "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + "message": "Напомена: од 16 Маја 2024м недодељене ставке организације више нису видљиве у приказу Сви сефови и доступне су само преко Админ конзоле." }, "unassignedItemsBannerCTAPartOne": { "message": "Assign these items to a collection from the", @@ -3024,12 +3024,12 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "adminConsole": { - "message": "Admin Console" + "message": "Администраторска конзола" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Грешка при додељивању циљне колекције." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Грешка при додељивању циљне фасцикле." } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index a2f856c31b..fa4dab6a8f 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden 密码管理器", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.", + "message": "无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -176,7 +176,7 @@ "message": "前往网页 App 吗?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "您可以在 Bitwarden 网页应用上更改您的主密码。" }, "fingerprintPhrase": { "message": "指纹短语", @@ -3010,17 +3010,17 @@ "message": "通行密钥已移除" }, "unassignedItemsBannerNotice": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." + "message": "注意:未分配的组织项目在「所有密码库」视图中不再可见,只能通过管理控制台访问。" }, "unassignedItemsBannerSelfHostNotice": { - "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console." + "message": "注意:从 2024 年 5 月 16 日起,未分配的组织项目在「所有密码库」视图中将不再可见,只能通过管理控制台访问。" }, "unassignedItemsBannerCTAPartOne": { "message": "Assign these items to a collection from the", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "unassignedItemsBannerCTAPartTwo": { - "message": "to make them visible.", + "message": "以使其可见。", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "adminConsole": { diff --git a/apps/browser/store/locales/de/copy.resx b/apps/browser/store/locales/de/copy.resx index 2267c6c85e..eb3ab2afd4 100644 --- a/apps/browser/store/locales/de/copy.resx +++ b/apps/browser/store/locales/de/copy.resx @@ -121,55 +121,55 @@ Bitwarden Passwort-Manager
- At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Zu Hause, am Arbeitsplatz oder unterwegs schützt Bitwarden einfach alle deine Passwörter, Passkeys und vertraulichen Informationen. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Ausgezeichnet als bester Passwortmanager von PCMag, WIRED, The Verge, CNET, G2 und vielen anderen! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +SCHÜTZE DEIN DIGITALES LEBEN +Sicher dein digitales Leben und schütze dich vor Passwortdiebstählen, indem du individuelle, sichere Passwörter für jedes Konto erstellest und speicherst. Verwalte alles in einem Ende-zu-Ende verschlüsselten Passwort-Tresor, auf den nur du Zugriff hast. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +ZUGRIFF AUF DEINE DATEN, ÜBERALL, JEDERZEIT UND AUF JEDEM GERÄT +Verwalte, speichere, sichere und teile einfach eine unbegrenzte Anzahl von Passwörtern auf einer unbegrenzten Anzahl von Geräten ohne Einschränkungen. -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +JEDER SOLLTE DIE MÖGLICHKEIT HABEN, ONLINE GESCHÜTZT ZU BLEIBEN +Verwende Bitwarden kostenlos, ohne Werbung oder Datenverkauf. Bitwarden glaubt, dass jeder die Möglichkeit haben sollte, online geschützt zu bleiben. Premium-Abos bieten Zugang zu erweiterten Funktionen. -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +STÄRKE DEINE TEAMS MIT BITWARDEN +Tarife für Teams und Enterprise enthalten professionelle Business-Funktionen. Einige Beispiele sind SSO-Integration, Selbst-Hosting, Directory-Integration und SCIM-Bereitstellung, globale Richtlinien, API-Zugang, Ereignisprotokolle und mehr. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Nutze Bitwarden, um deine Mitarbeiter abzusichern und sensible Informationen mit Kollegen zu teilen. -More reasons to choose Bitwarden: +Weitere Gründe, Bitwarden zu wählen: -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +Weltklasse-Verschlüsselung +Passwörter werden mit fortschrittlicher Ende-zu-Ende-Verschlüsselung (AES-256 bit, salted hashtag und PBKDF2 SHA-256) geschützt, damit deine Daten sicher und geheim bleiben. -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. +3rd-Party-Prüfungen +Bitwarden führt regelmäßig umfassende Sicherheitsprüfungen durch Dritte von namhaften Sicherheitsfirmen durch. Diese jährlichen Prüfungen umfassen Quellcode-Bewertungen und Penetration-Tests für Bitwarden-IPs, Server und Webanwendungen. -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Erweiterte 2FA +Schütze deine Zugangsdaten mit einem Authentifikator eines Drittanbieters, per E-Mail verschickten Codes oder FIDO2 WebAuthn-Zugangsadaten wie einem Hardware-Sicherheitsschlüssel oder Passkey. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Übertrage Daten direkt an andere, während die Ende-zu-Ende-Verschlüsselung beibehalten wird und die Verbreitung begrenzt werden kann. -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +Eingebauter Generator +Erstelle lange, komplexe und eindeutige Passwörter und eindeutige Benutzernamen für jede Website, die du besuchst. Integriere E-Mail-Alias-Anbieter für zusätzlichen Datenschutz. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Globale Übersetzungen +Es gibt Bitwarden-Übersetzungen für mehr als 60 Sprachen, die von der weltweiten Community über Crowdin übersetzt werden. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Plattformübergreifende Anwendungen +Schütze und teile sensible Daten in deinem Bitwarden Tresor von jedem Browser, mobilen Gerät oder Desktop-Betriebssystem und mehr. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +Bitwarden schützt mehr als nur Passwörter +Ende-zu-Ende verschlüsselte Zugangsverwaltungs-Lösungen von Bitwarden ermöglicht es Organisationen, alles zu sichern, einschließlich Entwicklergeheimnissen und Passkeys. Besuche Bitwarden.com, um mehr über den Bitwarden Secrets Manager und Bitwarden Passwordless.dev zu erfahren! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Zu Hause, am Arbeitsplatz oder unterwegs schützt Bitwarden einfach alle deine Passwörter, Passkeys und vertraulichen Informationen. Synchronisiere und greife auf deinen Tresor von unterschiedlichen Geräten aus zu diff --git a/apps/browser/store/locales/ko/copy.resx b/apps/browser/store/locales/ko/copy.resx index fdfb93ad6a..595663b1ca 100644 --- a/apps/browser/store/locales/ko/copy.resx +++ b/apps/browser/store/locales/ko/copy.resx @@ -121,7 +121,7 @@ Bitwarden Password Manager - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + 집에서도, 직장에서도, 이동 중에도 Bitwarden은 비밀번호, 패스키, 민감 정보를 쉽게 보호합니다. Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! @@ -169,7 +169,7 @@ End-to-end encrypted credential management solutions from Bitwarden empower orga - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + 집에서도, 직장에서도, 이동 중에도 Bitwarden은 비밀번호, 패스키, 민감 정보를 쉽게 보호합니다. 여러 기기에서 보관함에 접근하고 동기화할 수 있습니다. diff --git a/apps/browser/store/locales/pl/copy.resx b/apps/browser/store/locales/pl/copy.resx index 60709c7d4d..5641c68c48 100644 --- a/apps/browser/store/locales/pl/copy.resx +++ b/apps/browser/store/locales/pl/copy.resx @@ -118,58 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Menedżer Haseł Bitwarden - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + W domu, w pracy, lub w ruchu, Bitwarden z łatwością zabezpiecza wszystkie Twoje hasła, passkeys i poufne informacje. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Uznany za najlepszego menedżera haseł przez PCMag, WIRED, The Verge, CNET, G2 i wielu innych! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +ZABEZPIECZ SWOJE CYFROWE ŻYCIE +Zabezpiecz swoje cyfrowe życie i chroń przed naruszeniami danych, generując i zapisując unikalne, silne hasła do każdego konta. Przechowuj wszystko w zaszyfrowanym end-to-end magazynie haseł, do którego tylko Ty masz dostęp. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +DOSTĘP DO SWOICH DANYCH W KAŻDYM MIEJSCU, W DOWOLNYM CZASIE, NA KAŻDYM URZĄDZENIU +Z łatwością zarządzaj, przechowuj, zabezpieczaj i udostępniaj nieograniczoną liczbę haseł na nieograniczonej liczbie urządzeń. -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +KAŻDY POWINIEN POSIADAĆ NARZĘDZIA ABY ZACHOWAĆ BEZPIECZEŃSTWO W INTERNECIE +Korzystaj z Bitwarden za darmo, bez reklam i sprzedawania Twoich danych. Bitwarden wierzy, że każdy powinien mieć możliwość zachowania bezpieczeństwa w Internecie. Plany premium oferują dostęp do zaawansowanych funkcji. -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +WZMOCNIJ SWOJE ZESPOŁY DZIĘKI BITWARDEN +Plany dla Zespołów i Enterprise oferują profesjonalne funkcje biznesowe. Na przykład obejmują integrację z SSO, własny hosting, integrację katalogów i udostępnianie SCIM, zasady globalne, dostęp do API, dzienniki zdarzeń i inne. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Użyj Bitwarden, aby zabezpieczyć swoich pracowników i udostępniać poufne informacje współpracownikom. -More reasons to choose Bitwarden: +Więcej powodów, aby wybrać Bitwarden: -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +Szyfrowanie na światowym poziomie +Hasła są chronione za pomocą zaawansowanego, kompleksowego szyfrowania (AES-256-bitowy, solony hashtag i PBKDF2 SHA-256), dzięki czemu Twoje dane pozostają bezpieczne i prywatne. -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. +Audyty stron trzecich +Bitwarden regularnie przeprowadza kompleksowe audyty bezpieczeństwa stron trzecich we współpracy ze znanymi firmami security. Te coroczne audyty obejmują ocenę kodu źródłowego i testy penetracyjne adresów IP Bitwarden, serwerów i aplikacji internetowych. -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Zaawansowane 2FA +Zabezpiecz swój login za pomocą zewnętrznego narzędzia uwierzytelniającego, kodów przesłanych pocztą elektroniczną lub poświadczeń FIDO2 WebAuthn, takich jak sprzętowy klucz bezpieczeństwa lub hasło. -Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Bitwarden Wyślij +Przesyłaj dane bezpośrednio do innych, zachowując kompleksowe szyfrowane bezpieczeństwo i ograniczając ryzyko. -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +Wbudowany generator +Twórz długie, złożone i różne hasła oraz unikalne nazwy użytkowników dla każdej odwiedzanej witryny. Zintegruj się z dostawcami aliasów e-mail, aby uzyskać dodatkową prywatność. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Tłumaczenia globalne +Istnieją tłumaczenia Bitwarden na ponad 60 języków, tłumaczone przez globalną społeczność za pośrednictwem Crowdin. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Aplikacje wieloplatformowe +Zabezpiecz i udostępniaj poufne dane w swoim Sejfie Bitwarden z dowolnej przeglądarki, urządzenia mobilnego lub systemu operacyjnego na komputerze stacjonarnym i nie tylko. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +Bitwarden zabezpiecza nie tylko hasła +Rozwiązania do zarządzania danymi zaszyfrownaymi end-to-end od firmy Bitwarden umożliwiają organizacjom zabezpieczanie wszystkiego, w tym tajemnic programistów i kluczy dostępu. Odwiedź Bitwarden.com, aby dowiedzieć się więcej o Mendżerze Sekretów Bitwarden i Bitwarden Passwordless.dev! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + W domu, w pracy, lub w ruchu, Bitwarden z łatwością zabezpiecza wszystkie Twoje hasła, passkeys i poufne informacje. Synchronizacja i dostęp do sejfu z różnych urządzeń diff --git a/apps/browser/store/locales/pt_BR/copy.resx b/apps/browser/store/locales/pt_BR/copy.resx index 8b99c436d0..067f9357b2 100644 --- a/apps/browser/store/locales/pt_BR/copy.resx +++ b/apps/browser/store/locales/pt_BR/copy.resx @@ -118,10 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Gerenciador de Senhas Bitwarden - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais. Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! @@ -169,7 +169,7 @@ End-to-end encrypted credential management solutions from Bitwarden empower orga - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais. Sincronize e acesse o seu cofre através de múltiplos dispositivos diff --git a/apps/browser/store/locales/zh_CN/copy.resx b/apps/browser/store/locales/zh_CN/copy.resx index 94543f8f6f..d010cb1a7b 100644 --- a/apps/browser/store/locales/zh_CN/copy.resx +++ b/apps/browser/store/locales/zh_CN/copy.resx @@ -118,58 +118,56 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Bitwarden 密码管理器 - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + 被 PCMag、WIRED、The Verge、CNET、G2 等评为最佳密码管理器! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +保护您的数字生活 +通过为每个账户生成并保存独特而强大的密码,保护您的数字生活并防范数据泄露。所有内容保存在只有您可以访问的端对端加密的密码库中。 -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +随时随地在任何设备上访问您的数据 +不受任何限制跨无限数量的设备轻松管理、存储、保护和分享不限数量的密码。 -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +每个人都应该拥有的保持在线安全的工具 +使用 Bitwarden 是免费的,没有广告,不会出售数据。Bitwarden 相信每个人都应该拥有保持在线安全的能力。高级计划提供了堆高级功能的访问。 -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +通过 BITWARDEN 为您的团队提供支持 +团队和企业计划具有专业的商业功能。例如 SSO 集成、自托管、目录集成和 SCIM 配置、全局策略、API 访问、事件日志等。 -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +使用 Bitwarden 保护您的团队,并与同事共享敏感信息。 +选择 Bitwarden 的更多理由: -More reasons to choose Bitwarden: +世界级加密 +密码受到先进的端对端加密(AES-256 位、加盐哈希标签和 PBKDF2 SHA-256)保护,使您的数据保持安全和私密。 -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +第三方审计 +Bitwarden 定期与知名的安全公司进行全面的第三方安全审计。这些年度审核包括对 Bitwarden IP、服务器和 Web 应用程序的源代码评估和渗透测试。 -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. - -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +高级两步验证 +使用第三方身份验证器、通过电子邮件发送代码或 FIDO2 WebAuthn 凭据(如硬件安全钥匙或通行密钥)保护您的登录。 Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +直接传输数据给他人,同时保持端对端加密的安全性并防止曝露。 -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +内置生成器 +为您访问的每个网站创建长、复杂且独特的密码和用户名。与电子邮件别名提供商集成,增加隐私保护。 -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +全球翻译 +Bitwarden 的翻译涵盖 60 多种语言,由全球社区通过 Crowdin 翻译。 -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +跨平台应用程序 +从任何浏览器、移动设备或桌面操作系统中安全地访问和共享 Bitwarden 密码库中的敏感数据。 -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - +Bitwarden 保护的不仅仅是密码 +Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 了解更多关于Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev 的信息! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 从多台设备同步和访问密码库 From 68839a80b7b36c785064fb45e9b7ddd05c502da0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:30:01 +0000 Subject: [PATCH 73/86] Autosync the updated translations (#8880) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/az/messages.json | 6 +- apps/web/src/locales/bg/messages.json | 6 +- apps/web/src/locales/cs/messages.json | 10 +- apps/web/src/locales/da/messages.json | 6 +- apps/web/src/locales/de/messages.json | 82 +++++------ apps/web/src/locales/fr/messages.json | 6 +- apps/web/src/locales/hu/messages.json | 6 +- apps/web/src/locales/ja/messages.json | 6 +- apps/web/src/locales/lv/messages.json | 128 ++++++++--------- apps/web/src/locales/pl/messages.json | 6 +- apps/web/src/locales/pt_BR/messages.json | 170 +++++++++++------------ apps/web/src/locales/ru/messages.json | 6 +- apps/web/src/locales/sk/messages.json | 6 +- apps/web/src/locales/sr/messages.json | 86 ++++++------ apps/web/src/locales/uk/messages.json | 6 +- apps/web/src/locales/zh_CN/messages.json | 58 ++++---- 16 files changed, 297 insertions(+), 297 deletions(-) diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index d53278e93a..c3ae7b07b6 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -8042,12 +8042,12 @@ "message": "Bu kolleksiya yalnız admin konsolundan əlçatandır" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Təşkilat Menyusuna keç" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Anbar elementini seç" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Kolleksiya elementini seç" } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index f4ee30ba95..e0d0f00174 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -8042,12 +8042,12 @@ "message": "Тази колекция е достъпна само през административната конзола" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Превключване на менюто на организацията" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Изберете елемент от трезора" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Изберете елемент от колекцията" } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index b6e62aef9a..72e621b1a5 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -8036,18 +8036,18 @@ "message": "Nový klient byl úspěšně vytvořen" }, "noAccess": { - "message": "No access" + "message": "Žádný přístup" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "Tato kolekce je přístupná pouze z konzole administrátora" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Přepnout menu organizace" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Vybrat položku trezoru" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Vybrat položku kolekce" } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index e5042229fe..4c8cbf108a 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -8042,12 +8042,12 @@ "message": "Denne samling er kun tilgængelig via Admin-konsol" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Skift Organisationsmenu" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Vælg boksemne" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Vælg samlingsemne" } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index bd2e0946f6..1c7dd34329 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -4951,7 +4951,7 @@ "message": "Erstelle eine neue Kunden-Organisation, die dir als Anbieter zugeordnet wird. Du kannst auf diese Organisation zugreifen und diese verwalten." }, "newClient": { - "message": "New client" + "message": "Neuer Kunde" }, "addExistingOrganization": { "message": "Bestehende Organisation hinzufügen" @@ -7607,7 +7607,7 @@ "message": "Anbieterportal" }, "success": { - "message": "Success" + "message": "Erfolg" }, "viewCollection": { "message": "Sammlung anzeigen" @@ -7907,30 +7907,30 @@ "message": "Du kannst dich nicht selbst zu einer Gruppe hinzufügen." }, "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "message": "Hinweis: Ab dem 2. Mai 2024 werden nicht zugewiesene Organisationseinträge nicht mehr geräteübergreifend in der Ansicht aller Tresore sichtbar sein und sind nur über die Administrator-Konsole zugänglich. Weise diese Elemente einer Sammlung aus der Administrator-Konsole zu, um sie sichtbar zu machen." }, "unassignedItemsBannerNotice": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + "message": "Hinweis: Nicht zugewiesene Organisationseinträge sind nicht mehr geräteübergreifend in der Ansicht aller Tresore sichtbar und nun nur über die Administrator-Konsole zugänglich." }, "unassignedItemsBannerSelfHostNotice": { - "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + "message": "Hinweis: Ab dem 16. Mai 2024 werden nicht zugewiesene Organisationseinträge nicht mehr geräteübergreifend in der Ansicht aller Tresore sichtbar sein und nur über die Administrator-Konsole zugänglich." }, "unassignedItemsBannerCTAPartOne": { - "message": "Assign these items to a collection from the", + "message": "Weise diese Einträge einer Sammlung aus der", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "unassignedItemsBannerCTAPartTwo": { - "message": "to make them visible.", + "message": "zu, um sie sichtbar zu machen.", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "deleteProvider": { - "message": "Delete provider" + "message": "Anbieter löschen" }, "deleteProviderConfirmation": { - "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + "message": "Das Löschen eines Anbieters ist dauerhaft und unwiderruflich. Gib dein Master-Passwort ein, um die Löschung des Anbieters und aller zugehörigen Daten zu bestätigen." }, "deleteProviderName": { - "message": "Cannot delete $ID$", + "message": "Kann $ID$ nicht löschen", "placeholders": { "id": { "content": "$1", @@ -7939,7 +7939,7 @@ } }, "deleteProviderWarningDesc": { - "message": "You must unlink all clients before you can delete $ID$", + "message": "Du musst die Verknüpfung zu allen Kunden aufheben, bevor du $ID$ löschen kannst", "placeholders": { "id": { "content": "$1", @@ -7948,16 +7948,16 @@ } }, "providerDeleted": { - "message": "Provider deleted" + "message": "Anbieter gelöscht" }, "providerDeletedDesc": { - "message": "The Provider and all associated data has been deleted." + "message": "Der Anbieter und alle zugehörigen Daten wurden gelöscht." }, "deleteProviderRecoverConfirmDesc": { - "message": "You have requested to delete this Provider. Use the button below to confirm." + "message": "Du hast die Löschung dieses Anbieters angefragt. Verwende den Button unten, um dies zu bestätigen." }, "deleteProviderWarning": { - "message": "Deleting your provider is permanent. It cannot be undone." + "message": "Die Löschung deines Anbieters ist dauerhaft. Sie kann nicht widerrufen werden." }, "errorAssigningTargetCollection": { "message": "Fehler beim Zuweisen der Ziel-Sammlung." @@ -7966,88 +7966,88 @@ "message": "Fehler beim Zuweisen des Ziel-Ordners." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "Integrationen & SDKs", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Integrationen" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Geheimnisse des Bitwarden Secrets Managers automatisch mit einem Drittanbieter-Dienst synchronisieren." }, "sdks": { "message": "SDKs" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "Verwende das Bitwarden Secrets Manager SDK in den folgenden Programmiersprachen, um deine eigenen Anwendungen zu erstellen." }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "GitHub Actions einrichten" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "GitLab CI/CD einrichten" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Ansible einrichten" }, "cSharpSDKRepo": { - "message": "View C# repository" + "message": "C#-Repository anzeigen" }, "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "message": "C++-Repository anzeigen" }, "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "message": "JS WebAssembly-Repository anzeigen" }, "javaSDKRepo": { - "message": "View Java repository" + "message": "Java-Repository anzeigen" }, "pythonSDKRepo": { - "message": "View Python repository" + "message": "Python-Repository anzeigen" }, "phpSDKRepo": { - "message": "View php repository" + "message": "PHP-Repository anzeigen" }, "rubySDKRepo": { - "message": "View Ruby repository" + "message": "Ruby-Repository anzeigen" }, "goSDKRepo": { - "message": "View Go repository" + "message": "Go-Repository anzeigen" }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "Erstelle eine neue Kunden-Organisation, um sie als Anbieter zu verwalten. Zusätzliche Benutzerplätze werden im nächsten Abrechnungszeitraum berücksichtigt." }, "selectAPlan": { - "message": "Select a plan" + "message": "Ein Abo auswählen" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "35% Rabatt" }, "monthPerMember": { "message": "month per member" }, "seats": { - "message": "Seats" + "message": "Benutzerplätze" }, "addOrganization": { - "message": "Add organization" + "message": "Organisation hinzufügen" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "Neuer Kunde erfolgreich erstellt" }, "noAccess": { - "message": "No access" + "message": "Kein Zugriff" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "Diese Sammlung ist nur über die Administrator-Konsole zugänglich" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Organisationsmenü umschalten" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Tresor-Eintrag auswählen" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Sammlungseintrag auswählen" } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 1f1f343897..ffb1fe436b 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -8042,12 +8042,12 @@ "message": "Cette collection n'est accessible qu'à partir de la Console Admin" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Afficher/masquer le Menu de l'Organisation" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Sélectionner un élément du coffre" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Sélectionner un élément de la collection" } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index b85733325b..008a20b4f0 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -8042,12 +8042,12 @@ "message": "Ez a gyűjtemény csak az adminisztrátori konzolról érhető el." }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Szervezeti menü váltás" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Széf elem választás" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Gyűjtemény elem választás" } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index a701dd6e8d..1861743320 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -8042,12 +8042,12 @@ "message": "このコレクションは管理コンソールからのみアクセス可能です" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "組織メニューの切り替え" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "保管庫のアイテムを選択" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "コレクションのアイテムを選択" } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index b20f350231..1ad03eeb82 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -7741,22 +7741,22 @@ "description": "The date header used when a subscription is cancelled." }, "machineAccountsCannotCreate": { - "message": "Machine accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "Mašīnu kontus nav iespējams izveidot apturētās apvienībās. Lūgums vērsties pie savas apvienības īpašnieka pēc palīdzības." }, "machineAccount": { - "message": "Machine account", + "message": "Mašīnas konts", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "machineAccounts": { - "message": "Machine accounts", + "message": "Mašīnu konti", "description": "The title for the section that deals with machine accounts." }, "newMachineAccount": { - "message": "New machine account", + "message": "Jauns mašīnas konts", "description": "Title for creating a new machine account." }, "machineAccountsNoItemsMessage": { - "message": "Create a new machine account to get started automating secret access.", + "message": "Jāizveido jauns mašīnas konts, lai sāktu automatizēt piekļuvi noslēpumiem.", "description": "Message to encourage the user to start creating machine accounts." }, "machineAccountsNoItemsTitle": { @@ -7764,19 +7764,19 @@ "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { - "message": "Delete machine accounts", + "message": "Izdzēst mašīnu kontus", "description": "Title for the action to delete one or multiple machine accounts." }, "deleteMachineAccount": { - "message": "Delete machine account", + "message": "Izdzēst mašīnas kontu", "description": "Title for the action to delete a single machine account." }, "viewMachineAccount": { - "message": "View machine account", + "message": "Skatīt mašīnas kontu", "description": "Action to view the details of a machine account." }, "deleteMachineAccountDialogMessage": { - "message": "Deleting machine account $MACHINE_ACCOUNT$ is permanent and irreversible.", + "message": "Mašīnas konta $MACHINE_ACCOUNT$ izdzēšana ir paliekoša un neatgriezeniska.", "placeholders": { "machine_account": { "content": "$1", @@ -7785,10 +7785,10 @@ } }, "deleteMachineAccountsDialogMessage": { - "message": "Deleting machine accounts is permanent and irreversible." + "message": "Mašīnu kontu izdzēšana ir paliekoša un neatgriezeniska." }, "deleteMachineAccountsConfirmMessage": { - "message": "Delete $COUNT$ machine accounts", + "message": "Izdzēst $COUNT$ mašīnu kontu(s)", "placeholders": { "count": { "content": "$1", @@ -7797,60 +7797,60 @@ } }, "deleteMachineAccountToast": { - "message": "Machine account deleted" + "message": "Mašīnas konts ir izdzēsts" }, "deleteMachineAccountsToast": { - "message": "Machine accounts deleted" + "message": "Mašīnu konti ir izdzēsti" }, "searchMachineAccounts": { - "message": "Search machine accounts", + "message": "Meklēt mašīnu kontus", "description": "Placeholder text for searching machine accounts." }, "editMachineAccount": { - "message": "Edit machine account", + "message": "Labot mašīnas kontu", "description": "Title for editing a machine account." }, "machineAccountName": { - "message": "Machine account name", + "message": "Mašīnas konta nosaukums", "description": "Label for the name of a machine account" }, "machineAccountCreated": { - "message": "Machine account created", + "message": "Mašīnas konts ir izveidots", "description": "Notifies that a new machine account has been created" }, "machineAccountUpdated": { - "message": "Machine account updated", + "message": "Mašīnas konts ir atjaunināts", "description": "Notifies that a machine account has been updated" }, "projectMachineAccountsDescription": { - "message": "Grant machine accounts access to this project." + "message": "Piešķirt mašīnau kontiem piekļuvi šim projektam." }, "projectMachineAccountsSelectHint": { - "message": "Type or select machine accounts" + "message": "Ievadīt vai atlasīt mašīnu kontus" }, "projectEmptyMachineAccountAccessPolicies": { - "message": "Add machine accounts to grant access" + "message": "Jāpievieno mašīnu konti, lai piešķirtu piekļuvi" }, "machineAccountPeopleDescription": { - "message": "Grant groups or people access to this machine account." + "message": "Piešķirt kopām vai cilvēkiem piekļuvi šim mašīnas kontam." }, "machineAccountProjectsDescription": { - "message": "Assign projects to this machine account. " + "message": "Piešķirt projektus šim mašīnas kontam. " }, "createMachineAccount": { - "message": "Create a machine account" + "message": "Izveidot mašīnas kontu" }, "maPeopleWarningMessage": { - "message": "Removing people from a machine account does not remove the access tokens they created. For security best practice, it is recommended to revoke access tokens created by people removed from a machine account." + "message": "Cilvēku noņemšana no mašīnas konta nenoņem to izveidotās piekļuves pilnvaras. Labākajai drošības pieejai ir ieteicams atsaukt piekļuves pilnvaras, kuras ir izveidojuši cilvēki, kuri ir noņemti no mašīnas konta." }, "smAccessRemovalWarningMaTitle": { - "message": "Remove access to this machine account" + "message": "Noņemt piekļuvi šim mašīnas kontam" }, "smAccessRemovalWarningMaMessage": { - "message": "This action will remove your access to the machine account." + "message": "Šī darbība noņems piekļuvi mašīnas kontam." }, "machineAccountsIncluded": { - "message": "$COUNT$ machine accounts included", + "message": "Iekļauti $COUNT$ mašīnu konti", "placeholders": { "count": { "content": "$1", @@ -7859,7 +7859,7 @@ } }, "additionalMachineAccountCost": { - "message": "$COST$ per month for additional machine accounts", + "message": "$COST$ mēnesī par papildu mašīnu kontiem", "placeholders": { "cost": { "content": "$1", @@ -7868,10 +7868,10 @@ } }, "additionalMachineAccounts": { - "message": "Additional machine accounts" + "message": "Papildu mašīnu konti" }, "includedMachineAccounts": { - "message": "Your plan comes with $COUNT$ machine accounts.", + "message": "Plānā ir iekļauti $COUNT$ mašīnu konti.", "placeholders": { "count": { "content": "$1", @@ -7880,7 +7880,7 @@ } }, "addAdditionalMachineAccounts": { - "message": "You can add additional machine accounts for $COST$ per month.", + "message": "Papildu mašīnu kontus var pievienot par $COST$ mēnesī.", "placeholders": { "cost": { "content": "$1", @@ -7889,25 +7889,25 @@ } }, "limitMachineAccounts": { - "message": "Limit machine accounts (optional)" + "message": "Ierobežot mašīnu kontus (izvēles)" }, "limitMachineAccountsDesc": { - "message": "Set a limit for your machine accounts. Once this limit is reached, you will not be able to create new machine accounts." + "message": "Uzstāda mašīnu kontu skaita ierobežojumu. Tiklīdz tas ir sasniegts, nebūs iespējams izveidot jaunus mašīnu kontus." }, "machineAccountLimit": { - "message": "Machine account limit (optional)" + "message": "Mašīnu kontu skaita ierobežojums (izvēles)" }, "maxMachineAccountCost": { - "message": "Max potential machine account cost" + "message": "Lielākās iespējamās mašīnas konta izmaksas" }, "machineAccountAccessUpdated": { - "message": "Machine account access updated" + "message": "Mašīnas konta piekļuve ir atjaunināta" }, "restrictedGroupAccessDesc": { "message": "Sevi nevar pievienot kopai." }, "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "message": "Jāņem vērā: no 2024. gada 2. maija nepiešķirti apvienības vienumi vairs nebūs redzami skatā \"Visas glabātavas\" dažādās ierīcēs un būs sasniedzami tikai no pārvaldības konsoles, kur šie vienumi jāpiešķir krājumam, lai padarītu tos redzamus." }, "unassignedItemsBannerNotice": { "message": "Jāņem vērā: nepiešķirti apvienības vienumi vairs nav redzami skatā \"Visas glabātavas\" dažādās ierīcēs un tagad ir pieejami tikai pārvaldības konsolē." @@ -7966,74 +7966,74 @@ "message": "Kļūda mērķa mapes piešķiršanā." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "Integrācijas un izstrādātāju rīkkopas", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Integrācijas" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Automātiski sinhronizēt noslēpumus no Bitwarden Noslēpumu pārvaldnieka uz trešās puses pakalpojumu." }, "sdks": { - "message": "SDKs" + "message": "Izstrādātāju rīkkopas" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "Bitwarden Noslēpumu pārvaldnieka izstrādātāju rīkkopa ir izmantojama ar zemāk esošajām programmēšanas valodām, lai veidotu pats savas lietotnes." }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "Iestatīt GitHub darbības" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "Iestatīt GitLab CI/CD" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Iestatīt Ansible" }, "cSharpSDKRepo": { - "message": "View C# repository" + "message": "Skatīt C# glabātavu" }, "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "message": "Skatīt C++ glabātavu" }, "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "message": "Skatīt JS WebAssembly glabātavu" }, "javaSDKRepo": { - "message": "View Java repository" + "message": "Skatīt Java glabātavu" }, "pythonSDKRepo": { - "message": "View Python repository" + "message": "Skatīt Python glabātavu" }, "phpSDKRepo": { - "message": "View php repository" + "message": "Skatīt PHP glabātavu" }, "rubySDKRepo": { - "message": "View Ruby repository" + "message": "Skatīt Ruby glabātavu" }, "goSDKRepo": { - "message": "View Go repository" + "message": "Skatīt Go glabātavu" }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "Izveidot jaunu klienta apvienību, ko pārvaldīt kā nodrošinātājam. Papildu vietas tiks atspoguļotas nākamajā norēķinu posmā." }, "selectAPlan": { - "message": "Select a plan" + "message": "Atlasīt plānu" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "35% atlaide" }, "monthPerMember": { - "message": "month per member" + "message": "mēnesī par dalībnieku" }, "seats": { - "message": "Seats" + "message": "Vietas" }, "addOrganization": { - "message": "Add organization" + "message": "Pievienot apvienību" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "Veiksmīgi izveidots jauns klients" }, "noAccess": { "message": "Nav piekļuves" @@ -8042,12 +8042,12 @@ "message": "Šis krājums ir pieejams tikai pārvaldības konsolē" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Pārslēgt apvienību izvēlni" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Atlasīt glabātavas vienumu" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Atlasīt krājuma vienumu" } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index c54b72c21c..8fc603c1d5 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -4053,7 +4053,7 @@ "message": "Wszystkie funkcje zespołów oraz:" }, "includeAllTeamsStarterFeatures": { - "message": "All Teams Starter features, plus:" + "message": "Wszystkie funkcje z Teams Starter, plus:" }, "chooseMonthlyOrAnnualBilling": { "message": "Wybierz miesięczną lub roczną płatność" @@ -6737,7 +6737,7 @@ } }, "teamsStarterPlanInvLimitReachedManageBilling": { - "message": "Teams Starter plans may have up to $SEATCOUNT$ members. Upgrade to your plan to invite more members.", + "message": "Plan Teams Starter może mieć maksymalnie $SEATCOUNT$ członków. Przejdź na wyższy plan, aby zaprosić więcej członków.", "placeholders": { "seatcount": { "content": "$1", @@ -6746,7 +6746,7 @@ } }, "teamsStarterPlanInvLimitReachedNoManageBilling": { - "message": "Teams Starter plans may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade your plan and invite more members.", + "message": "Plan Teams Starter może mieć maksymalnie $SEATCOUNT$ członków. Skontaktuj się z właścicielem organizacji, aby przejść na wyższy plan i zaprosić więcej członków.", "placeholders": { "seatcount": { "content": "$1", diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 2b296694bb..06b117ed06 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -627,10 +627,10 @@ "message": "Iniciar sessão com a chave de acesso" }, "invalidPasskeyPleaseTryAgain": { - "message": "Invalid Passkey. Please try again." + "message": "Senha inválida. Por favor, tente novamente." }, "twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn": { - "message": "2FA for passkeys is not supported. Update the app to log in." + "message": "2FA para senhas não é suportada. Atualize o aplicativo para iniciar a sessão." }, "loginWithPasskeyInfo": { "message": "Use uma senha gerada que fará o login automaticamente sem uma senha. Biometrias como reconhecimento facial ou impressão digital, ou outro método de segurança FIDO2 verificarão sua identidade." @@ -1359,11 +1359,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": " em vez disso.", "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": " em vez disso, você pode precisar esperar até que o seu administrador confirme a sua associação à organização.", "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": { @@ -2802,10 +2802,10 @@ "message": "CLI" }, "bitWebVault": { - "message": "Bitwarden Web vault" + "message": "Cofre Web do Bitwarden" }, "bitSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Gerenciador de Segredos Bitwarden" }, "loggedIn": { "message": "Conectado(a)." @@ -3489,7 +3489,7 @@ "message": "Defina um limite de vagas para sua assinatura. Quando esse limite for atingido, você não poderá convidar novos usuários." }, "limitSmSubscriptionDesc": { - "message": "Set a seat limit for your Secrets Manager subscription. Once this limit is reached, you will not be able to invite new members." + "message": "Defina um limite de assento para sua assinatura do Gerenciador Secretos. Uma vez que este limite for atingido, você não poderá convidar novos membros." }, "maxSeatLimit": { "message": "Limite Máximo de Vaga (opcional)", @@ -3621,7 +3621,7 @@ "message": "Atualizar Chave de Criptografia" }, "updateEncryptionSchemeDesc": { - "message": "We've changed the encryption scheme to provide better security. Update your encryption key now by entering your master password below." + "message": "Alteramos o esquema de criptografia para fornecer melhor segurança. Atualize sua chave de criptografia agora digitando sua senha mestra abaixo." }, "updateEncryptionKeyWarning": { "message": "Depois de atualizar sua chave de criptografia, é necessário encerrar e iniciar a sessão em todos os aplicativos do Bitwarden que você está usando atualmente (como o aplicativo móvel ou as extensões do navegador). Não encerrar e iniciar sessão (que baixa sua nova chave de criptografia) pode resultar em corrupção de dados. Nós tentaremos desconectá-lo automaticamente, mas isso pode demorar um pouco." @@ -3678,7 +3678,7 @@ "message": "Escolha quando o tempo limite do seu cofre irá se esgotar e execute a ação selecionada." }, "vaultTimeoutLogoutDesc": { - "message": "Choose when your vault will be logged out." + "message": "Escolha quando seu cofre será desconectado." }, "oneMinute": { "message": "1 minuto" @@ -4083,7 +4083,7 @@ "message": "Identificador SSO" }, "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", + "message": "Forneça esse ID aos seus membros para logar com SSO. Para ignorar essa etapa, configure ", "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": { @@ -4695,7 +4695,7 @@ "message": "Inscrito na recuperação de conta" }, "withdrawAccountRecovery": { - "message": "Withdraw from account recovery" + "message": "Retirar da recuperação de conta" }, "enrollPasswordResetSuccess": { "message": "Inscrição com sucesso!" @@ -4704,7 +4704,7 @@ "message": "Retirada com sucesso!" }, "eventEnrollAccountRecovery": { - "message": "User $ID$ enrolled in account recovery.", + "message": "O usuário $ID$ se inscreveu na recuperação de conta.", "placeholders": { "id": { "content": "$1", @@ -4713,7 +4713,7 @@ } }, "eventWithdrawAccountRecovery": { - "message": "User $ID$ withdrew from account recovery.", + "message": "O usuário $ID$ retirou da recuperação de conta.", "placeholders": { "id": { "content": "$1", @@ -4887,7 +4887,7 @@ "message": "Erro" }, "accountRecoveryManageUsers": { - "message": "Manage users must also be granted with the manage account recovery permission" + "message": "Gerenciar usuários também devem ser concedidos com a permissão de gerenciar a recuperação de contas" }, "setupProvider": { "message": "Configuração do Provedor" @@ -5117,7 +5117,7 @@ "message": "Ativar preenchimento automático" }, "activateAutofillPolicyDesc": { - "message": "Activate the auto-fill on page load setting on the browser extension for all existing and new members." + "message": "Ative o autopreenchimento na configuração de carregamento de página na extensão do navegador para todos os membros existentes e novos." }, "experimentalFeature": { "message": "Sites comprometidos ou não confiáveis podem tomar vantagem do autopreenchimento ao carregar a página." @@ -5432,7 +5432,7 @@ "message": "Conector de Chave" }, "memberDecryptionKeyConnectorDescStart": { - "message": "Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The", + "message": "Conecte o login com SSO ao seu servidor de chave de descriptografia auto-hospedado. Usando essa opção, os membros não precisarão usar suas senhas mestres para descriptografar os dados", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescLink": { @@ -5440,7 +5440,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescEnd": { - "message": "are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.", + "message": "são necessários para configurar a descriptografia do conector chave. Contacte o suporte do Bitwarden para configurar a assistência.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "keyConnectorPolicyRestriction": { @@ -5637,7 +5637,7 @@ } }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Itens do cofre pessoal e itens de outras organizações não serão incluídos.", "placeholders": { "organization": { "content": "$1", @@ -6707,10 +6707,10 @@ "message": "Convidar membro" }, "needsConfirmation": { - "message": "Needs confirmation" + "message": "Precisa de confirmação" }, "memberRole": { - "message": "Member role" + "message": "Função de membro" }, "moreFromBitwarden": { "message": "Mais do Bitwarden" @@ -6719,7 +6719,7 @@ "message": "Trocar Produtos" }, "freeOrgInvLimitReachedManageBilling": { - "message": "Free organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.", + "message": "Organizações gratuitas podem ter até $SEATCOUNT$ membros. Faça upgrade para um plano pago para convidar mais membros.", "placeholders": { "seatcount": { "content": "$1", @@ -6728,7 +6728,7 @@ } }, "freeOrgInvLimitReachedNoManageBilling": { - "message": "Free organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.", + "message": "Organizações gratuitas podem ter até $SEATCOUNT$ membros. Entre em contato com o proprietário da sua organização para fazer upgrade.", "placeholders": { "seatcount": { "content": "$1", @@ -6737,7 +6737,7 @@ } }, "teamsStarterPlanInvLimitReachedManageBilling": { - "message": "Teams Starter plans may have up to $SEATCOUNT$ members. Upgrade to your plan to invite more members.", + "message": "Planos de Times Starter podem ter até $SEATCOUNT$ membros. Atualize para o seu plano para convidar mais membros.", "placeholders": { "seatcount": { "content": "$1", @@ -6755,7 +6755,7 @@ } }, "freeOrgMaxCollectionReachedManageBilling": { - "message": "Free organizations may have up to $COLLECTIONCOUNT$ collections. Upgrade to a paid plan to add more collections.", + "message": "Organizações gratuitas podem ter até $COLLECTIONCOUNT$ coleções. Faça o upgrade para um plano pago para adicionar mais coleções.", "placeholders": { "COLLECTIONCOUNT": { "content": "$1", @@ -6764,7 +6764,7 @@ } }, "freeOrgMaxCollectionReachedNoManageBilling": { - "message": "Free organizations may have up to $COLLECTIONCOUNT$ collections. Contact your organization owner to upgrade.", + "message": "Organizações gratuitas podem ter até $COLLECTIONCOUNT$ membros. Entre em contato com o proprietário da sua organização para fazer upgrade.", "placeholders": { "COLLECTIONCOUNT": { "content": "$1", @@ -6779,10 +6779,10 @@ "message": "Exportar dados" }, "exportingOrganizationSecretDataTitle": { - "message": "Exporting Organization Secret Data" + "message": "Exportando dados secretos da organização" }, "exportingOrganizationSecretDataDescription": { - "message": "Only the Secrets Manager data associated with $ORGANIZATION$ will be exported. Items in other products or from other organizations will not be included.", + "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Itens do cofre pessoal e itens de outras organizações não serão incluídos.", "placeholders": { "ORGANIZATION": { "content": "$1", @@ -6812,7 +6812,7 @@ "message": "Upload manual" }, "manualUploadDesc": { - "message": "If you do not want to opt into billing sync, manually upload your license here." + "message": "Se você não deseja optar pela sincronização de cobrança, carregue sua licença manualmente aqui." }, "syncLicense": { "message": "Sincronizar licença" @@ -6827,13 +6827,13 @@ "message": "Última sincronização de licença" }, "billingSyncHelp": { - "message": "Billing Sync help" + "message": "Ajuda da Sincronização de faturamento" }, "licensePaidFeaturesHelp": { - "message": "License paid features help" + "message": "Ajuda dos recursos de licença paga" }, "selfHostGracePeriodHelp": { - "message": "After your subscription expires, you have 60 days to apply an updated license file to your organization. Grace period ends $GRACE_PERIOD_END_DATE$.", + "message": "Após a expiração da assinatura, você tem 60 dias para aplicar um arquivo de licença atualizado à sua organização. Fim do período de carência $GRACE_PERIOD_END_DATE$.", "placeholders": { "GRACE_PERIOD_END_DATE": { "content": "$1", @@ -6845,28 +6845,28 @@ "message": "Enviar licença" }, "projectPeopleDescription": { - "message": "Grant groups or people access to this project." + "message": "Conceder acesso a este projeto ou grupos de pessoas." }, "projectPeopleSelectHint": { - "message": "Type or select people or groups" + "message": "Digite ou selecione pessoas ou grupos" }, "projectServiceAccountsDescription": { - "message": "Grant service accounts access to this project." + "message": "Conceder acesso a contas de serviço a este projeto." }, "projectServiceAccountsSelectHint": { - "message": "Type or select service accounts" + "message": "Digite ou selecione contas de serviço" }, "projectEmptyPeopleAccessPolicies": { - "message": "Add people or groups to start collaborating" + "message": "Adicione pessoas ou grupos para começar a colaborar" }, "projectEmptyServiceAccountAccessPolicies": { "message": "Adicione contas de serviço para conceder acesso" }, "serviceAccountPeopleDescription": { - "message": "Grant groups or people access to this service account." + "message": "Conceder acesso a esta conta de serviço a grupos ou pessoas." }, "serviceAccountProjectsDescription": { - "message": "Assign projects to this service account. " + "message": "Atribuir projetos para esta conta de serviço. " }, "serviceAccountEmptyProjectAccesspolicies": { "message": "Adicionar projetos para conceder acesso" @@ -6878,13 +6878,13 @@ "message": "Grupo/Usuário" }, "lowKdfIterations": { - "message": "Low KDF Iterations" + "message": "Iterações KDF baixas" }, "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." + "message": "Atualize suas configurações de criptografia para atender às novas recomendações de segurança e melhorar a proteção da conta." }, "changeKdfLoggedOutWarning": { - "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login setup. We recommend exporting your vault before changing your encryption settings to prevent data loss." + "message": "O processo desconectará você de todas as sessões ativas. Você precisará iniciar a sessão novamente e concluir a configuração de login em duas etapas. Recomendamos exportar seu cofre antes de alterar suas configurações de criptografia para evitar perda de dados." }, "secretsManager": { "message": "Gerenciador de Segredos" @@ -6924,7 +6924,7 @@ "message": "Ocorreu um erro ao tentar ler o arquivo de importação" }, "accessedSecret": { - "message": "Accessed secret $SECRET_ID$.", + "message": "$SECRET_ID$ secreto acessado.", "placeholders": { "secret_id": { "content": "$1", @@ -6996,10 +6996,10 @@ "message": "Seleção é necessária." }, "saPeopleWarningTitle": { - "message": "Access tokens still available" + "message": "Tokens de acesso ainda disponíveis" }, "saPeopleWarningMessage": { - "message": "Removing people from a service account does not remove the access tokens they created. For security best practice, it is recommended to revoke access tokens created by people removed from a service account." + "message": "Remover pessoas de uma conta de serviço não remove os tokens de acesso que criaram. Para a melhor prática de segurança, é recomendável revogar os tokens de acesso criados por pessoas removidas de uma conta de serviço." }, "smAccessRemovalWarningProjectTitle": { "message": "Remover acesso para esse projeto" @@ -7008,10 +7008,10 @@ "message": "Esta ação removerá seu acesso ao projeto." }, "smAccessRemovalWarningSaTitle": { - "message": "Remove access to this service account" + "message": "Remover acesso a essa conta de serviço" }, "smAccessRemovalWarningSaMessage": { - "message": "This action will remove your access to the service account." + "message": "Essa ação removerá seu acesso à conta de serviço." }, "removeAccess": { "message": "Remover acesso" @@ -7023,16 +7023,16 @@ "message": "Senha mestre exposta" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "A senha foi encontrada em violação de dados. Use uma senha única para proteger sua conta. Tem certeza de que deseja usar uma senha exposta?" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "Senha Mestra Fraca e Exposta" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" }, "characterMinimum": { - "message": "$LENGTH$ character minimum", + "message": "Mínimo de $LENGTH$ caracteres", "placeholders": { "length": { "content": "$1", @@ -7041,7 +7041,7 @@ } }, "masterPasswordMinimumlength": { - "message": "Master password must be at least $LENGTH$ characters long.", + "message": "A senha mestra deve ter pelo menos $LENGTH$ caracteres.", "placeholders": { "length": { "content": "$1", @@ -7057,14 +7057,14 @@ "message": "Descartar" }, "notAvailableForFreeOrganization": { - "message": "This feature is not available for free organizations. Contact your organization owner to upgrade." + "message": "Este recurso não está disponível para organizações gratuitas. Entre em contato com o seu proprietário para atualizar." }, "smProjectSecretsNoItemsNoAccess": { - "message": "Contact your organization's admin to manage secrets for this project.", + "message": "Entre em contato com o administrador da sua organização para gerenciar segredos para este projeto.", "description": "The message shown to the user under a project's secrets tab when the user only has read access to the project." }, "enforceOnLoginDesc": { - "message": "Require existing members to change their passwords" + "message": "Exigir que os membros existentes alterem suas senhas" }, "smProjectDeleteAccessRestricted": { "message": "Você não tem permissão para excluir este projeto", @@ -7081,16 +7081,16 @@ "message": "Sessão iniciada" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "Aprovação do dispositivo necessária. Selecione uma opção de aprovação abaixo:" }, "rememberThisDevice": { "message": "Lembrar deste dispositivo" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "Desmarque se estiver usando um dispositivo público" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "Aprovar do seu outro dispositivo" }, "requestAdminApproval": { "message": "Solicitar aprovação do administrador" @@ -7099,17 +7099,17 @@ "message": "Aprovar com a senha mestre" }, "trustedDeviceEncryption": { - "message": "Trusted device encryption" + "message": "Criptografia de dispositivo confiável" }, "trustedDevices": { "message": "Dispositivos confiáveis" }, "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", + "message": "Uma vez autenticados, os membros descriptografarão os dados do cofre usando uma chave armazenada no seu dispositivo", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "single organization", + "message": "organização única", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionPartTwo": { @@ -7117,7 +7117,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO required", + "message": "Necessário SSO", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionPartThree": { @@ -7125,11 +7125,11 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "account recovery administration", + "message": "gerenciar recuperação de conta", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", + "message": "política com inscrição automática será ativada quando esta opção for utilizada.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { @@ -7157,7 +7157,7 @@ "message": "Recuperar conta" }, "updatedTempPassword": { - "message": "User updated a password issued through account recovery." + "message": "O usuário atualizou uma senha emitida através da recuperação de conta." }, "activatedAccessToSecretsManager": { "message": "Acesso ativado ao Gerenciador de Segredos", @@ -7232,7 +7232,7 @@ "message": "Remover membros que não têm senhas mestres sem definir uma para eles pode restringir o acesso à sua conta completa." }, "approvedAuthRequest": { - "message": "Approved device for $ID$.", + "message": "Dispositivo aprovado para $ID$.", "placeholders": { "id": { "content": "$1", @@ -7241,7 +7241,7 @@ } }, "rejectedAuthRequest": { - "message": "Denied device for $ID$.", + "message": "Dispositivo negado para $ID$.", "placeholders": { "id": { "content": "$1", @@ -7250,7 +7250,7 @@ } }, "requestedDeviceApproval": { - "message": "Requested device approval." + "message": "Aprovação do dispositivo solicitada." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Comece o seu período de teste gratuito de 7 dias do Bitwarden para $ORG$", @@ -7262,7 +7262,7 @@ } }, "startYour7DayFreeTrialOfBitwardenSecretsManagerFor": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for $ORG$", + "message": "Inicie o seu período de teste gratuito de 7 dias do Bitwarden Secrets Manager para $ORG$", "placeholders": { "org": { "content": "$1", @@ -7280,19 +7280,19 @@ "message": "Sinalização de região selecionada" }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "Conta criada com sucesso!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "Aprovação do administrador necessária" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." + "message": "Seu pedido foi enviado para seu administrador." }, "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "Será notificado assim que for aprovado." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Problemas em efetuar login?" }, "loginApproved": { "message": "Sessão aprovada" @@ -7324,10 +7324,10 @@ } }, "secretsManagerForPlanDesc": { - "message": "For engineering and DevOps teams to manage secrets throughout the software development lifecycle." + "message": "Para equipes de engenharia e DevOps gerenciar segredos durante todo o ciclo de vida do desenvolvimento de software." }, "free2PersonOrganization": { - "message": "Free 2-person Organizations" + "message": "Organizações gratuitas com 2 pessoas" }, "unlimitedSecrets": { "message": "Segredos ilimitados" @@ -7366,7 +7366,7 @@ "message": "Assine o Gerenciador de Segredos" }, "addSecretsManagerUpgradeDesc": { - "message": "Add Secrets Manager to your upgraded plan to maintain access to any secrets created with your previous plan." + "message": "Adicione o Gerenciador de Segredos ao seu plano atualizado para manter o acesso a todos os segredos criados com seu plano anterior." }, "additionalServiceAccounts": { "message": "Contas de serviço adicionais" @@ -7420,13 +7420,13 @@ "message": "Limitar contas de serviço (opcional)" }, "limitServiceAccountsDesc": { - "message": "Set a limit for your service accounts. Once this limit is reached, you will not be able to create new service accounts." + "message": "Defina um limite para suas contas de máquina. Quando este limite for atingido, você não poderá criar novas contas de máquina." }, "serviceAccountLimit": { "message": "Limite de contas de serviço (opcional)" }, "maxServiceAccountCost": { - "message": "Max potential service account cost" + "message": "Custo máximo da conta de serviço potencial" }, "loggedInExclamation": { "message": "Conectado!" @@ -7459,10 +7459,10 @@ "message": "Chave de acesso" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "A senha não será copiada" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "A senha não será copiada para o item clonado. Deseja continuar clonando este item?" }, "modifiedCollectionManagement": { "message": "Definição de gerenciamento da coleção $ID$ modificada.", @@ -7484,7 +7484,7 @@ "message": "Use a extensão para salvar rapidamente credenciais e formulários de autopreenchimento sem abrir o aplicativo web." }, "projectAccessUpdated": { - "message": "Project access updated" + "message": "Acesso ao projeto atualizado" }, "unexpectedErrorSend": { "message": "Ocorreu um erro inesperado ao carregar este Envio. Tente novamente mais tarde." @@ -7505,7 +7505,7 @@ "message": "Você não tem acesso para gerenciar esta coleção." }, "grantCollectionAccess": { - "message": "Grant groups or members access to this collection." + "message": "Conceder acesso de grupos ou membros a esta coleção." }, "grantCollectionAccessMembersOnly": { "message": "Conceder acesso a essa coleção." @@ -8042,12 +8042,12 @@ "message": "This collection is only accessible from the admin console" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Alternar Menu da Organização" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Selecionar item do cofre" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Selecionar item da coleção" } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 67b23b0e89..213a9d1965 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -8042,12 +8042,12 @@ "message": "Эта коллекция доступна только из консоли администратора" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Переключить меню организации" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Выбрать элемент хранилища" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Выбрать элемент коллекции" } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 756723129f..652b1b08fd 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -8042,12 +8042,12 @@ "message": "Táto zbierka je dostupná iba z administrátorskej konzoly" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Prepnúť menu organizácie" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Vyberte položku z trezora" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Vyberte položku zo zbierky" } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index f927113fad..de563afe50 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -7607,7 +7607,7 @@ "message": "Портал провајдера" }, "success": { - "message": "Success" + "message": "Успех" }, "viewCollection": { "message": "Преглед колекције" @@ -7904,16 +7904,16 @@ "message": "Приступ налога машине ажуриран" }, "restrictedGroupAccessDesc": { - "message": "You cannot add yourself to a group." + "message": "Не можете да се додате у групу." }, "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "message": "Обавештење: 2. маја 2024. недодељене ставке организације више неће бити видљиве у вашем приказу Сви сефови на свим уређајима и биће им доступне само преко Админ конзоле. Доделите ове ставке колекцији са Админ конзолом да бисте их учинили видљивим." }, "unassignedItemsBannerNotice": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + "message": "Напомена: Недодељене ставке организације више нису видљиве у вашем приказу Сви сефови на свим уређајима и сада су доступне само преко Админ конзоле." }, "unassignedItemsBannerSelfHostNotice": { - "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + "message": "Обавештење: 16. маја 2024. недодељене ставке организације више неће бити видљиве у вашем приказу Сви сефови на свим уређајима и биће им доступне само преко Админ конзоле." }, "unassignedItemsBannerCTAPartOne": { "message": "Assign these items to a collection from the", @@ -7924,13 +7924,13 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "deleteProvider": { - "message": "Delete provider" + "message": "Избриши провајдера" }, "deleteProviderConfirmation": { - "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + "message": "Брисање провајдера је трајно и неповратно. Унесите своју главну лозинку да бисте потврдили брисање провајдера и свих повезаних података." }, "deleteProviderName": { - "message": "Cannot delete $ID$", + "message": "Не може да се избрише $ID$", "placeholders": { "id": { "content": "$1", @@ -7939,7 +7939,7 @@ } }, "deleteProviderWarningDesc": { - "message": "You must unlink all clients before you can delete $ID$", + "message": "Морате прекинути везу са свим клијентима да бисте могли да избришете $ID$", "placeholders": { "id": { "content": "$1", @@ -7948,106 +7948,106 @@ } }, "providerDeleted": { - "message": "Provider deleted" + "message": "Провајдер је избрисан" }, "providerDeletedDesc": { - "message": "The Provider and all associated data has been deleted." + "message": "Провајдер и сви повезани подаци су избрисани." }, "deleteProviderRecoverConfirmDesc": { - "message": "You have requested to delete this Provider. Use the button below to confirm." + "message": "Захтевали сте брисање овог провајдера. Користите дугме испод да потврдите." }, "deleteProviderWarning": { - "message": "Deleting your provider is permanent. It cannot be undone." + "message": "Брисање провајдера је трајно. Не може се поништити." }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Грешка при додељивању циљне колекције." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Грешка при додељивању циљне фасцикле." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "Интеграције & SDK", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Интеграције" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Аутоматски синхронизујте тајне од Bitwarden Secrets Manager са сервисима треће стране." }, "sdks": { - "message": "SDKs" + "message": "SDK" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "Употребите Bitwarden Secrets Manager SDK на следећим програмским језицима да направите сопствене апликације." }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "Подесити акције GitHub-а" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "Подесити GitLab CI/CD" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Подесити Ansible" }, "cSharpSDKRepo": { - "message": "View C# repository" + "message": "Преглед C# спремишта" }, "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "message": "Преглед C++ спремишта" }, "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "message": "Преглед JS WebAssembly спремишта" }, "javaSDKRepo": { - "message": "View Java repository" + "message": "Преглед Java спремишта" }, "pythonSDKRepo": { - "message": "View Python repository" + "message": "Преглед Python спремишта" }, "phpSDKRepo": { - "message": "View php repository" + "message": "Преглед php спремишта" }, "rubySDKRepo": { - "message": "View Ruby repository" + "message": "Преглед Ruby спремишта" }, "goSDKRepo": { - "message": "View Go repository" + "message": "Преглед Go спремишта" }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "Креирајте нову клијентску организацију којом ћете управљати као добављач. Додатна места ће се одразити у следећем обрачунском циклусу." }, "selectAPlan": { - "message": "Select a plan" + "message": "Изаберите пакет" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "Попуст од 35%" }, "monthPerMember": { - "message": "month per member" + "message": "месечно по члану" }, "seats": { - "message": "Seats" + "message": "Места" }, "addOrganization": { - "message": "Add organization" + "message": "Додај организацију" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "Нови клијент је успешно креиран" }, "noAccess": { - "message": "No access" + "message": "Немате приступ" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "Овој колекцији се може приступити само са администраторске конзоле" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Укључи мени Организација" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Изаберите ставку сефа" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Изаберите ставку колекције" } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index c9434d72d6..e7b9722c9d 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -8042,12 +8042,12 @@ "message": "Ця збірка доступна тільки з консолі адміністратора" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Перемкнути меню організації" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Вибрати елемент сховища" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Вибрати елемент збірки" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index a9a0d85457..292041624c 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -7907,20 +7907,20 @@ "message": "您不能将自己添加到群组。" }, "unassignedItemsBannerSelfHost": { - "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." + "message": "注意:从 2024 年 5 月 2 日起,未分配的组织项目在您所有设备的「所有密码库」视图中将不再可见,只能通过管理控制台访问。通过管理控制台将这些项目分配给集合以使其可见。" }, "unassignedItemsBannerNotice": { - "message": "Notice: Unassigned organization items are no longer visible in your All Vaults view across devices and are now only accessible via the Admin Console." + "message": "注意:未分配的组织项目在您所有设备的「所有密码库」视图中不再可见,现在只能通过管理控制台访问。" }, "unassignedItemsBannerSelfHostNotice": { - "message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console." + "message": "注意:从 2024 年 5 月 2 日起,未分配的组织项目在您所有设备的「所有密码库」视图中将不再可见,只能通过管理控制台访问。" }, "unassignedItemsBannerCTAPartOne": { "message": "Assign these items to a collection from the", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "unassignedItemsBannerCTAPartTwo": { - "message": "to make them visible.", + "message": "以使其可见。", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "deleteProvider": { @@ -7966,88 +7966,88 @@ "message": "分配目标文件夹时出错。" }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "集成和 SDK", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "集成" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "通过 Bitwarden 机密管理器将机密自动同步到第三方服务。" }, "sdks": { - "message": "SDKs" + "message": "SDK" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "使用以下编程语言的 Bitwarden 机密管理器 SDK 来构建您自己的应用程序。" }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "设置 Github Actions" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "设置 GitLab CI/CD" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "设置 Ansible" }, "cSharpSDKRepo": { - "message": "View C# repository" + "message": "查看 C# 存储库" }, "cPlusPlusSDKRepo": { - "message": "View C++ repository" + "message": "查看 C++ 存储库" }, "jsWebAssemblySDKRepo": { - "message": "View JS WebAssembly repository" + "message": "查看 JS WebAssembly 存储库" }, "javaSDKRepo": { - "message": "View Java repository" + "message": "查看 Java 存储库" }, "pythonSDKRepo": { - "message": "View Python repository" + "message": "查看 Python 存储库" }, "phpSDKRepo": { - "message": "View php repository" + "message": "查看 php 存储库" }, "rubySDKRepo": { - "message": "View Ruby repository" + "message": "查看 Ruby 存储库" }, "goSDKRepo": { - "message": "View Go repository" + "message": "查看 Go 存储库" }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "创建一个新的客户组织作为提供商来管理。附加席位将反映在下一个计费周期中。" }, "selectAPlan": { "message": "选择套餐" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "35% 折扣" }, "monthPerMember": { "message": "month per member" }, "seats": { - "message": "Seats" + "message": "席位" }, "addOrganization": { - "message": "Add organization" + "message": "添加组织" }, "createdNewClient": { "message": "Successfully created new client" }, "noAccess": { - "message": "No access" + "message": "暂无权限" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "此集合只能从管理控制台访问" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "切换组织菜单" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "选择密码库项目" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "选择集合项目" } } From 7f207d25590e3363de5063d5f763977b98d09445 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:30:11 +0000 Subject: [PATCH 74/86] Autosync the updated translations (#8879) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 3 + apps/desktop/src/locales/ar/messages.json | 3 + apps/desktop/src/locales/az/messages.json | 3 + apps/desktop/src/locales/be/messages.json | 3 + apps/desktop/src/locales/bg/messages.json | 3 + apps/desktop/src/locales/bn/messages.json | 3 + apps/desktop/src/locales/bs/messages.json | 3 + apps/desktop/src/locales/ca/messages.json | 3 + apps/desktop/src/locales/cs/messages.json | 3 + apps/desktop/src/locales/cy/messages.json | 3 + apps/desktop/src/locales/da/messages.json | 3 + apps/desktop/src/locales/de/messages.json | 7 +- apps/desktop/src/locales/el/messages.json | 3 + apps/desktop/src/locales/en_GB/messages.json | 3 + apps/desktop/src/locales/en_IN/messages.json | 3 + apps/desktop/src/locales/eo/messages.json | 3 + apps/desktop/src/locales/es/messages.json | 3 + apps/desktop/src/locales/et/messages.json | 3 + apps/desktop/src/locales/eu/messages.json | 3 + apps/desktop/src/locales/fa/messages.json | 3 + apps/desktop/src/locales/fi/messages.json | 3 + apps/desktop/src/locales/fil/messages.json | 3 + apps/desktop/src/locales/fr/messages.json | 3 + apps/desktop/src/locales/gl/messages.json | 3 + apps/desktop/src/locales/he/messages.json | 3 + apps/desktop/src/locales/hi/messages.json | 3 + apps/desktop/src/locales/hr/messages.json | 3 + apps/desktop/src/locales/hu/messages.json | 3 + apps/desktop/src/locales/id/messages.json | 3 + apps/desktop/src/locales/it/messages.json | 3 + apps/desktop/src/locales/ja/messages.json | 3 + apps/desktop/src/locales/ka/messages.json | 3 + apps/desktop/src/locales/km/messages.json | 3 + apps/desktop/src/locales/kn/messages.json | 3 + apps/desktop/src/locales/ko/messages.json | 3 + apps/desktop/src/locales/lt/messages.json | 3 + apps/desktop/src/locales/lv/messages.json | 3 + apps/desktop/src/locales/me/messages.json | 3 + apps/desktop/src/locales/ml/messages.json | 3 + apps/desktop/src/locales/mr/messages.json | 3 + apps/desktop/src/locales/my/messages.json | 3 + apps/desktop/src/locales/nb/messages.json | 3 + apps/desktop/src/locales/ne/messages.json | 3 + apps/desktop/src/locales/nl/messages.json | 3 + apps/desktop/src/locales/nn/messages.json | 3 + apps/desktop/src/locales/or/messages.json | 3 + apps/desktop/src/locales/pl/messages.json | 3 + apps/desktop/src/locales/pt_BR/messages.json | 73 ++++++++++---------- apps/desktop/src/locales/pt_PT/messages.json | 3 + apps/desktop/src/locales/ro/messages.json | 3 + apps/desktop/src/locales/ru/messages.json | 3 + apps/desktop/src/locales/si/messages.json | 3 + apps/desktop/src/locales/sk/messages.json | 3 + apps/desktop/src/locales/sl/messages.json | 3 + apps/desktop/src/locales/sr/messages.json | 15 ++-- apps/desktop/src/locales/sv/messages.json | 3 + apps/desktop/src/locales/te/messages.json | 3 + apps/desktop/src/locales/th/messages.json | 3 + apps/desktop/src/locales/tr/messages.json | 3 + apps/desktop/src/locales/uk/messages.json | 3 + apps/desktop/src/locales/vi/messages.json | 3 + apps/desktop/src/locales/zh_CN/messages.json | 7 +- apps/desktop/src/locales/zh_TW/messages.json | 3 + 63 files changed, 234 insertions(+), 45 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index afdfc90d76..97067b788a 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 7869b0894b..2d25269fff 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -2697,6 +2697,9 @@ "message": "تنسيقات مشتركة", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index d4cea4f06e..cf664abf41 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -2697,6 +2697,9 @@ "message": "Ortaq formatlar", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Problemlərin aradan qaldırılması" }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 53e3ec2d12..e0133e5a74 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index f4886c420f..0c5dc25742 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -2697,6 +2697,9 @@ "message": "Често използвани формати", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Отстраняване на проблеми" }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index abd2c1cfae..626734ebff 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 825bd6344e..9d5685cca9 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 6c48d6cb0b..d8c0f32948 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -2697,6 +2697,9 @@ "message": "Formats comuns", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Resolució de problemes" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 550b10a31c..328ebe15ec 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -2697,6 +2697,9 @@ "message": "Společné formáty", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Řešení problémů" }, diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index b1cc9e63d3..62f2e608bb 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index f2a84a3c29..95a054a7fb 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -2697,6 +2697,9 @@ "message": "Almindelige formater", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Fejlfinding" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index e5e3945abc..6518a56b45 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -1633,10 +1633,10 @@ "message": "Browser-Integration wird nicht unterstützt" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Fehler beim Aktivieren der Browser-Integration" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Beim Aktivieren der Browser-Integration ist ein Fehler aufgetreten." }, "browserIntegrationMasOnlyDesc": { "message": "Leider wird die Browser-Integration derzeit nur in der Mac App Store Version unterstützt." @@ -2697,6 +2697,9 @@ "message": "Gängigste Formate", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Problembehandlung" }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 41e6a62a2f..87360c33ce 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -2697,6 +2697,9 @@ "message": "Κοινές μορφές", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Αντιμετώπιση Προβλημάτων" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 2658610df3..5c8c32b7c1 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 0542da9ddc..abfa0b1c0d 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 1c4cc4f0be..427f08f805 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index ec5da44293..f7df93bdd7 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 3850cc1d85..02cd737baa 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index b21108b6ad..2067b2dcc2 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 08356d410d..ef34f8222a 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -2697,6 +2697,9 @@ "message": "فرمت‌های رایج", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index fca24c197a..33c593e3aa 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -2697,6 +2697,9 @@ "message": "Yleiset muodot", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Vianetsintä" }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 6d5f85fca8..d28a4b568c 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 1097624b14..86550b736f 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -2697,6 +2697,9 @@ "message": "Formats communs", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Résolution de problèmes" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 90648699c0..889a2beeee 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 73599c012e..3b155ffdf3 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -2697,6 +2697,9 @@ "message": "תסדירים נפוצים", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 4f8bb9b4bb..af28c66681 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 220c8bfab2..01983d5891 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 149d48284e..5c91fb4b94 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -2697,6 +2697,9 @@ "message": "Általános formátumok", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Hibaelhárítás" }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 3194b0f7d3..2173224f54 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 08ae2d9da8..93882cf698 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -2697,6 +2697,9 @@ "message": "Formati comuni", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Successo" + }, "troubleshooting": { "message": "Risoluzione problemi" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index b07eab20cc..ab6c0be95f 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -2697,6 +2697,9 @@ "message": "一般的な形式", "description": "Label indicating the most common import formats" }, + "success": { + "message": "成功" + }, "troubleshooting": { "message": "トラブルシューティング" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 90648699c0..889a2beeee 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 90648699c0..889a2beeee 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 162cba3a75..eb0cbcf6be 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 09b1767af0..8e50ade96c 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index de77aa8fbf..e9de697005 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -2697,6 +2697,9 @@ "message": "Dažni formatai", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 521a0afcf2..aa057f54ab 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -2697,6 +2697,9 @@ "message": "Izplatīti veidoli", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Sarežģījumu novēršana" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index ed458379b8..1f49961b46 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index b94b9d1b79..96811b9dba 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 90648699c0..889a2beeee 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 5142c8e61f..0ee0db69ef 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index e190cfc236..7bf132bdac 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -2697,6 +2697,9 @@ "message": "Vanlige formater", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index bd58a18b0d..13e1466805 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 3ca0730710..b5f2a413d6 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -2697,6 +2697,9 @@ "message": "Veelvoorkomende formaten", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Probleemoplossing" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 12e11b32c1..35e7173d74 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 7363558551..cd83d2ea69 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index df7a158a3a..250c557309 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -2697,6 +2697,9 @@ "message": "Popularne formaty", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Sukces" + }, "troubleshooting": { "message": "Rozwiązywanie problemów" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index f651e0b060..12db01d8cd 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -561,10 +561,10 @@ "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Você logou na sua conta com sucesso" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Você pode fechar esta janela" }, "masterPassSent": { "message": "Enviamos um e-mail com a dica da sua senha mestra." @@ -801,10 +801,10 @@ "message": "Alterar Senha Mestra" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Continuar no aplicativo web?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Você pode alterar a sua senha mestra no aplicativo web Bitwarden." }, "fingerprintPhrase": { "message": "Frase biométrica", @@ -1402,7 +1402,7 @@ "message": "Código PIN inválido." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "Muitas tentativas de entrada de PIN inválidas. Desconectando." }, "unlockWithWindowsHello": { "message": "Desbloquear com o Windows Hello" @@ -1557,7 +1557,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "verificationRequired": { - "message": "Verification required", + "message": "Verificação necessária", "description": "Default title for the user verification dialog." }, "currentMasterPass": { @@ -1633,10 +1633,10 @@ "message": "Integração com o navegador não suportado" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Erro ao ativar a integração do navegador" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Ocorreu um erro ao permitir a integração do navegador." }, "browserIntegrationMasOnlyDesc": { "message": "Infelizmente, por ora, a integração do navegador só é suportada na versão da Mac App Store." @@ -1654,10 +1654,10 @@ "message": "Ative uma camada adicional de segurança, exigindo validação de frase de impressão digital ao estabelecer uma ligação entre o computador e o navegador. Quando ativado, isto requer intervenção do usuário e verificação cada vez que uma conexão é estabelecida." }, "enableHardwareAcceleration": { - "message": "Use hardware acceleration" + "message": "Utilizar aceleração de hardware" }, "enableHardwareAccelerationDesc": { - "message": "By default this setting is ON. Turn OFF only if you experience graphical issues. Restart is required." + "message": "Por padrão esta configuração está ativada. Desligar apenas se tiver problemas gráficos. Reiniciar é necessário." }, "approve": { "message": "Aprovar" @@ -1898,40 +1898,40 @@ "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." }, "tryAgain": { - "message": "Try again" + "message": "Tentar novamente" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "Verificação necessária para esta ação. Defina um PIN para continuar." }, "setPin": { - "message": "Set PIN" + "message": "Definir PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "Verificiar com biometria" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "Aguardando confirmação" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "Não foi possível completar a biometria." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "Precisa de um método diferente?" }, "useMasterPassword": { - "message": "Use master password" + "message": "Usar a senha mestra" }, "usePin": { - "message": "Use PIN" + "message": "Usar PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "Usar biometria" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "Digite o código de verificação que foi enviado para o seu e-mail." }, "resendCode": { - "message": "Resend code" + "message": "Reenviar código" }, "hours": { "message": "Horas" @@ -2541,13 +2541,13 @@ } }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "Inicie o Duo e siga os passos para finalizar o login." }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "A autenticação em duas etapas do Duo é necessária para sua conta." }, "launchDuo": { - "message": "Launch Duo in Browser" + "message": "Iniciar o Duo no navegador" }, "importFormatError": { "message": "Os dados não estão formatados corretamente. Por favor, verifique o seu arquivo de importação e tente novamente." @@ -2630,13 +2630,13 @@ "message": "Nome de usuário ou senha incorretos" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "Senha incorreta" }, "incorrectCode": { - "message": "Incorrect code" + "message": "Código incorreto" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "PIN incorreto" }, "multifactorAuthenticationFailed": { "message": "Falha na autenticação de múltiplos fatores" @@ -2697,25 +2697,28 @@ "message": "Formatos comuns", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { - "message": "Troubleshooting" + "message": "Solução de problemas" }, "disableHardwareAccelerationRestart": { - "message": "Disable hardware acceleration and restart" + "message": "Desativar aceleração de hardware e reiniciar" }, "enableHardwareAccelerationRestart": { - "message": "Enable hardware acceleration and restart" + "message": "Ativar aceleração de hardware e reiniciar" }, "removePasskey": { - "message": "Remove passkey" + "message": "Remover senha" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Chave de acesso removida" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Erro ao atribuir coleção de destino." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Erro ao atribuir pasta de destino." } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 4e58dad2cf..97ce9a8b88 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -2697,6 +2697,9 @@ "message": "Formatos comuns", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Resolução de problemas" }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 3fe73f28a7..978f57eb9b 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index cc182812a6..c9b3b95b39 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -2697,6 +2697,9 @@ "message": "Основные форматы", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Успешно" + }, "troubleshooting": { "message": "Устранение проблем" }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 261ae1c9b8..3d43997144 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 6ef52a83ee..13d720dbfb 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -2697,6 +2697,9 @@ "message": "Bežné formáty", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Riešenie problémov" }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 8c9c158c87..8cb06dcf0c 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index edafdb55a2..04b7e4cf29 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -801,10 +801,10 @@ "message": "Промени главну лозинку" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Ићи на веб апликацију?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Можете променити главну лозинку на Bitwarden веб апликацији." }, "fingerprintPhrase": { "message": "Сигурносна Фраза Сефа", @@ -1633,10 +1633,10 @@ "message": "Интеграција са претраживачем није подржана" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Грешка при омогућавању интеграције прегледача" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Дошло је до грешке при омогућавању интеграције прегледача." }, "browserIntegrationMasOnlyDesc": { "message": "Нажалост, интеграција прегледача за сада је подржана само у верзији Mac App Store." @@ -2697,6 +2697,9 @@ "message": "Уобичајени формати", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Решавање проблема" }, @@ -2713,9 +2716,9 @@ "message": "Приступачни кључ је уклоњен" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Грешка при додељивању циљне колекције." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Грешка при додељивању циљне фасцикле." } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 6342424fd4..bd21c0f328 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -2697,6 +2697,9 @@ "message": "Vanliga format", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Felsökning" }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 90648699c0..889a2beeee 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 5b6a1b9d0b..f1cd5351f7 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 42a8f207c7..3e7229c41b 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Sorun giderme" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index ac7b7c3243..9ee7652093 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -2697,6 +2697,9 @@ "message": "Поширені формати", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Успішно" + }, "troubleshooting": { "message": "Усунення проблем" }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index aac9995db1..0c0e6f6df7 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -2697,6 +2697,9 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "Troubleshooting" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 0560466cf8..9837be29e3 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -801,10 +801,10 @@ "message": "修改主密码" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "前往网页 App 吗?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "您可以在 Bitwarden 网页应用上更改您的主密码。" }, "fingerprintPhrase": { "message": "指纹短语", @@ -2697,6 +2697,9 @@ "message": "常规格式", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "故障排除" }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 9eb12e23cf..5f768b0a43 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -2697,6 +2697,9 @@ "message": "常見格式", "description": "Label indicating the most common import formats" }, + "success": { + "message": "Success" + }, "troubleshooting": { "message": "疑難排解" }, From 242ee306cf32674d968ba2b1639126d5d2c45486 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Tue, 23 Apr 2024 20:34:02 +0200 Subject: [PATCH 75/86] Shorten extension description to 112 characters as that is a limit setup by Apple (#8884) Safari extension description is limited to 112 chars Add that restriction within the description Co-authored-by: Daniel James Smith --- apps/browser/src/_locales/en/messages.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 7e6e333689..1c0b178895 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." From bc43f3f78f41c1b171522d54665cfbfaedbb13e4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 19:22:48 +0000 Subject: [PATCH 76/86] Autosync the updated translations (#8886) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 4 ++-- apps/browser/src/_locales/az/messages.json | 4 ++-- apps/browser/src/_locales/be/messages.json | 4 ++-- apps/browser/src/_locales/bg/messages.json | 4 ++-- apps/browser/src/_locales/bn/messages.json | 4 ++-- apps/browser/src/_locales/bs/messages.json | 4 ++-- apps/browser/src/_locales/ca/messages.json | 4 ++-- apps/browser/src/_locales/cs/messages.json | 4 ++-- apps/browser/src/_locales/cy/messages.json | 4 ++-- apps/browser/src/_locales/da/messages.json | 4 ++-- apps/browser/src/_locales/de/messages.json | 4 ++-- apps/browser/src/_locales/el/messages.json | 4 ++-- apps/browser/src/_locales/en_GB/messages.json | 4 ++-- apps/browser/src/_locales/en_IN/messages.json | 4 ++-- apps/browser/src/_locales/es/messages.json | 4 ++-- apps/browser/src/_locales/et/messages.json | 4 ++-- apps/browser/src/_locales/eu/messages.json | 4 ++-- apps/browser/src/_locales/fa/messages.json | 4 ++-- apps/browser/src/_locales/fi/messages.json | 4 ++-- apps/browser/src/_locales/fil/messages.json | 4 ++-- apps/browser/src/_locales/fr/messages.json | 4 ++-- apps/browser/src/_locales/gl/messages.json | 4 ++-- apps/browser/src/_locales/he/messages.json | 4 ++-- apps/browser/src/_locales/hi/messages.json | 4 ++-- apps/browser/src/_locales/hr/messages.json | 4 ++-- apps/browser/src/_locales/hu/messages.json | 4 ++-- apps/browser/src/_locales/id/messages.json | 4 ++-- apps/browser/src/_locales/it/messages.json | 4 ++-- apps/browser/src/_locales/ja/messages.json | 4 ++-- apps/browser/src/_locales/ka/messages.json | 4 ++-- apps/browser/src/_locales/km/messages.json | 4 ++-- apps/browser/src/_locales/kn/messages.json | 4 ++-- apps/browser/src/_locales/ko/messages.json | 2 +- apps/browser/src/_locales/lt/messages.json | 4 ++-- apps/browser/src/_locales/lv/messages.json | 4 ++-- apps/browser/src/_locales/ml/messages.json | 4 ++-- apps/browser/src/_locales/mr/messages.json | 4 ++-- apps/browser/src/_locales/my/messages.json | 4 ++-- apps/browser/src/_locales/nb/messages.json | 4 ++-- apps/browser/src/_locales/ne/messages.json | 4 ++-- apps/browser/src/_locales/nl/messages.json | 4 ++-- apps/browser/src/_locales/nn/messages.json | 4 ++-- apps/browser/src/_locales/or/messages.json | 4 ++-- apps/browser/src/_locales/pl/messages.json | 4 ++-- apps/browser/src/_locales/pt_BR/messages.json | 4 ++-- apps/browser/src/_locales/pt_PT/messages.json | 4 ++-- apps/browser/src/_locales/ro/messages.json | 4 ++-- apps/browser/src/_locales/ru/messages.json | 4 ++-- apps/browser/src/_locales/si/messages.json | 4 ++-- apps/browser/src/_locales/sk/messages.json | 4 ++-- apps/browser/src/_locales/sl/messages.json | 4 ++-- apps/browser/src/_locales/sr/messages.json | 4 ++-- apps/browser/src/_locales/sv/messages.json | 4 ++-- apps/browser/src/_locales/te/messages.json | 4 ++-- apps/browser/src/_locales/th/messages.json | 4 ++-- apps/browser/src/_locales/tr/messages.json | 4 ++-- apps/browser/src/_locales/uk/messages.json | 4 ++-- apps/browser/src/_locales/vi/messages.json | 4 ++-- apps/browser/src/_locales/zh_CN/messages.json | 2 +- apps/browser/src/_locales/zh_TW/messages.json | 4 ++-- 60 files changed, 118 insertions(+), 118 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index e08894be0b..996142b5ad 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "قم بالتسجيل أو إنشاء حساب جديد للوصول إلى خزنتك الآمنة." diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 1e5062d8c6..a58ada8eb1 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Güvənli anbarınıza müraciət etmək üçün giriş edin və ya yeni bir hesab yaradın." diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 91ff397b3a..82fd4fa5d4 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Увайдзіце або стварыце новы ўліковы запіс для доступу да бяспечнага сховішча." diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 33be2608b4..b6d41cb622 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Впишете се или създайте нов абонамент, за да достъпите защитен трезор." diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index a12308648a..dec1bc6cfa 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "আপনার সুরক্ষিত ভল্টে প্রবেশ করতে লগ ইন করুন অথবা একটি নতুন অ্যাকাউন্ট তৈরি করুন।" diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 7f406fabee..9d3113e3f6 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Prijavite se ili napravite novi račun da biste pristupili svom sigurnom trezoru." diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 7c8bd63aea..8063ba79d8 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Inicieu sessió o creeu un compte nou per accedir a la caixa forta." diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index bd3c6882df..ee58f3d263 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Pro přístup do Vašeho bezpečného trezoru se přihlaste nebo si vytvořte nový účet." diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 2be868872c..a80dca5f92 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Mewngofnodwch neu crëwch gyfrif newydd i gael mynediad i'ch cell ddiogel." diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 777c3b484f..215d79eb21 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log ind eller opret en ny konto for at få adgang til din sikre boks." diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index d55d499b3c..fbc193dbae 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -7,8 +7,8 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Zu Hause, am Arbeitsplatz oder unterwegs schützt Bitwarden einfach alle deine Passwörter, Passkeys und vertraulichen Informationen.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Melde dich an oder erstelle ein neues Konto, um auf deinen Tresor zuzugreifen." diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 8c65e61e53..5c85aeff58 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Συνδεθείτε ή δημιουργήστε ένα νέο λογαριασμό για να αποκτήσετε πρόσβαση στο ασφαλές vault σας." diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index e4d90adf1a..087cd3faa8 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 7cc17240d2..f370af7f36 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 3e488bce4c..9e89f453df 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Identifícate o crea una nueva cuenta para acceder a tu caja fuerte." diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 785a3e4986..5705a5a0d2 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Logi oma olemasolevasse kontosse sisse või loo uus konto." diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 9a07b9d9ae..ee3b5f1329 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Saioa hasi edo sortu kontu berri bat zure kutxa gotorrera sartzeko." diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index c68dc43ef4..e2f0e96c86 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "وارد شوید یا یک حساب کاربری بسازید تا به گاوصندوق امن‌تان دسترسی یابید." diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 2cdb6a2379..746f4f45be 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -7,8 +7,8 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Kotona, töissä tai reissussa, Bitwarden suojaa helposti kaikki salasanasi, avainkoodisi ja arkaluonteiset tietosi.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Käytä salattua holviasi kirjautumalla sisään tai luo uusi tili." diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 0dfb4a39c9..abb999d032 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Maglog-in o gumawa ng bagong account para ma-access ang iyong ligtas na kahadeyero." diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 742e31ee58..de35f71832 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Identifiez-vous ou créez un nouveau compte pour accéder à votre coffre sécurisé." diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index b4c151eeb0..3dd737f0a8 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 61482da54a..5d343ae807 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "צור חשבון חדש או התחבר כדי לגשת לכספת המאובטחת שלך." diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index b76405eed8..fa4051d3e9 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "अपनी सुरक्षित तिजोरी में प्रवेश करने के लिए नया खाता बनाएं या लॉग इन करें।" diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 2dc500bc1e..c9b8741509 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Prijavi se ili stvori novi račun za pristup svojem sigurnom trezoru." diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index e47f2cda1f..5d5b174435 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Bejelentkezés vagy új fiók létrehozása a biztonsági széf eléréséhez." diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index d4399d8e15..b54e854d27 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Masuk atau buat akun baru untuk mengakses brankas Anda." diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 93ae682190..91d10253a0 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Accedi o crea un nuovo account per accedere alla tua cassaforte." diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 52ff21727a..967dc222e5 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "安全なデータ保管庫へアクセスするためにログインまたはアカウントを作成してください。" diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 2c18502eca..c73c366195 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 5d1b024c60..b6384bb840 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 047270808e..178cd7c45f 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "ನಿಮ್ಮ ಸುರಕ್ಷಿತ ವಾಲ್ಟ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಲಾಗ್ ಇನ್ ಮಾಡಿ ಅಥವಾ ಹೊಸ ಖಾತೆಯನ್ನು ರಚಿಸಿ." diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 1724225b0e..95a7727b83 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -8,7 +8,7 @@ }, "extDesc": { "message": "집에서도, 직장에서도, 이동 중에도 Bitwarden은 비밀번호, 패스키, 민감 정보를 쉽게 보호합니다.", - "description": "Extension description" + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "안전 보관함에 접근하려면 로그인하거나 새 계정을 만드세요." diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index b1a2c857e0..a01c5069e8 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Prisijunkite arba sukurkite naują paskyrą, kad galėtumėte pasiekti saugyklą." diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 4055693486..f24f0a93fc 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Jāpiesakās vai jāizveido jauns konts, lai piekļūtu drošajai glabātavai." diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index d9703137fe..334027b407 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "നിങ്ങളുടെ സുരക്ഷിത വാൾട്ടിലേക്കു പ്രവേശിക്കാൻ ലോഗിൻ ചെയ്യുക അല്ലെങ്കിൽ ഒരു പുതിയ അക്കൗണ്ട് സൃഷ്ടിക്കുക." diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index f67f617d3b..b0e9f8abc1 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "तुमच्या सुरक्षित तिजोरीत पोहचण्यासाठी लॉग इन करा किंवा नवीन खाते उघडा." diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 5d1b024c60..b6384bb840 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 649163a8dc..163154b2f2 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Logg på eller opprett en ny konto for å få tilgang til ditt sikre hvelv." diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 5d1b024c60..b6384bb840 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index f1424df0b9..cd76fc9684 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in of maak een nieuw account aan om toegang te krijgen tot je beveiligde kluis." diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 5d1b024c60..b6384bb840 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 5d1b024c60..b6384bb840 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 9b3e8f20fc..d3d9106c15 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -7,8 +7,8 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "W domu, w pracy, lub w ruchu, Bitwarden z łatwością zabezpiecza wszystkie Twoje hasła, passkeys i poufne informacje.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Zaloguj się lub utwórz nowe konto, aby uzyskać dostęp do Twojego bezpiecznego sejfu." diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index ef2b6f2dca..417bc977eb 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -7,8 +7,8 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Inicie a sessão ou crie uma nova conta para acessar seu cofre seguro." diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 06ba8eed26..6d6fd70276 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Inicie sessão ou crie uma nova conta para aceder ao seu cofre seguro." diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index b3e0a2066f..780bf69b93 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "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." diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index e594dbdce2..927095a3f6 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Войдите или создайте новый аккаунт для доступа к вашему защищенному хранилищу." diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 05e2dc3edd..33b03f574b 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "ඔබගේ ආරක්ෂිත සුරක්ෂිතාගාරය වෙත පිවිසීමට හෝ නව ගිණුමක් නිර්මාණය කරන්න." diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index eab1d105eb..c84cfbb778 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Prihláste sa, alebo vytvorte nový účet pre prístup k vášmu bezpečnému trezoru." diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 935678efc8..4a6b7cd214 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Prijavite se ali ustvarite nov račun za dostop do svojega varnega trezorja." diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index acac4d14c6..a04a7ecd70 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Пријавите се или креирајте нови налог за приступ сефу." diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index d96e86b8d3..2b9ec59ec2 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Logga in eller skapa ett nytt konto för att komma åt ditt säkra valv." diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 5d1b024c60..b6384bb840 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 794d0e6c22..7e1dda99be 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "ล็อกอิน หรือ สร้างบัญชีใหม่ เพื่อใช้งานตู้นิรภัยของคุณ" diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 8408253b86..8a8bb6ea60 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Güvenli kasanıza ulaşmak için giriş yapın veya yeni bir hesap oluşturun." diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index b590b92041..27293fc992 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Для доступу до сховища увійдіть в обліковий запис, або створіть новий." diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index ab1d0d515b..4eba4ffaea 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Đăng nhập hoặc tạo tài khoản mới để truy cập kho lưu trữ của bạn." diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index fa4dab6a8f..3cf2f96da1 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -8,7 +8,7 @@ }, "extDesc": { "message": "无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。", - "description": "Extension description" + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "登录或者创建一个账户来访问您的安全密码库。" diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 1ecfdfc50e..eb35cd08c7 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -7,8 +7,8 @@ "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.", - "description": "Extension description" + "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "登入或建立帳戶以存取您的安全密碼庫。" From 6b0628b81e33ae158231ccbd3d5efc3d09794167 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 19:27:46 +0000 Subject: [PATCH 77/86] Autosync the updated translations (#8885) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/az/messages.json | 2 +- apps/desktop/src/locales/bg/messages.json | 2 +- apps/desktop/src/locales/cs/messages.json | 2 +- apps/desktop/src/locales/da/messages.json | 2 +- apps/desktop/src/locales/de/messages.json | 2 +- apps/desktop/src/locales/fi/messages.json | 2 +- apps/desktop/src/locales/pt_PT/messages.json | 2 +- apps/desktop/src/locales/sk/messages.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index cf664abf41..1ecd18eee7 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -2698,7 +2698,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Uğurlu" }, "troubleshooting": { "message": "Problemlərin aradan qaldırılması" diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 0c5dc25742..d53034d61c 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -2698,7 +2698,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Успех" }, "troubleshooting": { "message": "Отстраняване на проблеми" diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 328ebe15ec..e68fe8fffc 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -2698,7 +2698,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Úspěch" }, "troubleshooting": { "message": "Řešení problémů" diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index 95a054a7fb..0e578a6f66 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -2698,7 +2698,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Gennemført" }, "troubleshooting": { "message": "Fejlfinding" diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 6518a56b45..d04c2795f3 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -2698,7 +2698,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Erfolg" }, "troubleshooting": { "message": "Problembehandlung" diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 33c593e3aa..517b437d03 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -2698,7 +2698,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Onnistui" }, "troubleshooting": { "message": "Vianetsintä" diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 97ce9a8b88..14f0ec5d2f 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -2698,7 +2698,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Com sucesso" }, "troubleshooting": { "message": "Resolução de problemas" diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 13d720dbfb..6499486b9d 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -2698,7 +2698,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Úspech" }, "troubleshooting": { "message": "Riešenie problémov" From 790c9a614141e91debfadfeff1607bfcd50ff8f0 Mon Sep 17 00:00:00 2001 From: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:00:47 -0400 Subject: [PATCH 78/86] Fixed race condition where this.canAccessPremium would be undefined before the sync could complete (#8887) --- .../angular/src/vault/components/view.component.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 42349737f0..27d6e14b11 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -9,7 +9,7 @@ import { OnInit, Output, } from "@angular/core"; -import { firstValueFrom, Subject, takeUntil } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; @@ -69,7 +69,6 @@ export class ViewComponent implements OnDestroy, OnInit { private totpInterval: any; private previousCipherId: string; private passwordReprompted = false; - private directiveIsDestroyed$ = new Subject(); get fido2CredentialCreationDateValue(): string { const dateCreated = this.i18nService.t("dateCreated"); @@ -119,19 +118,11 @@ export class ViewComponent implements OnDestroy, OnInit { } }); }); - - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntil(this.directiveIsDestroyed$)) - .subscribe((canAccessPremium: boolean) => { - this.canAccessPremium = canAccessPremium; - }); } ngOnDestroy() { this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); this.cleanUp(); - this.directiveIsDestroyed$.next(true); - this.directiveIsDestroyed$.complete(); } async load() { @@ -141,6 +132,9 @@ export class ViewComponent implements OnDestroy, OnInit { this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher), ); + this.canAccessPremium = await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$, + ); this.showPremiumRequiredTotp = this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp; From 8ef5340635ac641d702f104f001baf40cd960b0c Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:57:19 -0400 Subject: [PATCH 79/86] Trust our own copy of authenticatedAccounts until all accounts are initialized (#8888) --- .../src/platform/services/state.service.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index f660cd7a34..412176e235 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -115,14 +115,19 @@ export class StateService< return; } + // Get all likely authenticated accounts + const authenticatedAccounts = ( + (await this.storageService.get(keys.authenticatedAccounts)) ?? [] + ).filter((account) => account != null); + await this.updateState(async (state) => { - state.authenticatedAccounts = - (await this.storageService.get(keys.authenticatedAccounts)) ?? []; - for (const i in state.authenticatedAccounts) { - if (i != null) { - state = await this.syncAccountFromDisk(state.authenticatedAccounts[i]); - } + for (const i in authenticatedAccounts) { + state = await this.syncAccountFromDisk(authenticatedAccounts[i]); } + + // After all individual accounts have been added + state.authenticatedAccounts = authenticatedAccounts; + const storedActiveUser = await this.storageService.get(keys.activeUserId); if (storedActiveUser != null) { state.activeUserId = storedActiveUser; From 1520d95bbc862d875b75764695262a45a85c300e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:21:25 -0700 Subject: [PATCH 80/86] [deps] Auth: Update @types/node-ipc to v9.2.3 (#7248) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 10 ++++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index e2961eb9ee..747d8ec981 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -20,15 +20,17 @@ "devDependencies": { "@tsconfig/node16": "1.0.4", "@types/node": "18.19.29", - "@types/node-ipc": "9.2.0", + "@types/node-ipc": "9.2.3", "typescript": "4.7.4" } }, "../../../libs/common": { + "name": "@bitwarden/common", "version": "0.0.0", "license": "GPL-3.0" }, "../../../libs/node": { + "name": "@bitwarden/node", "version": "0.0.0", "license": "GPL-3.0", "dependencies": { @@ -105,9 +107,9 @@ } }, "node_modules/@types/node-ipc": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@types/node-ipc/-/node-ipc-9.2.0.tgz", - "integrity": "sha512-0v1oucUgINvWPhknecSBE5xkz74sVgeZgiL/LkWXNTSzFaGspEToA4oR56hjza0Jkk6DsS2EiNU3M2R2KQza9A==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@types/node-ipc/-/node-ipc-9.2.3.tgz", + "integrity": "sha512-/MvSiF71fYf3+zwqkh/zkVkZj1hl1Uobre9EMFy08mqfJNAmpR0vmPgOUdEIDVgifxHj6G1vYMPLSBLLxoDACQ==", "dev": true, "dependencies": { "@types/node": "*" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index c572613119..72b2587a4a 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -25,7 +25,7 @@ "devDependencies": { "@tsconfig/node16": "1.0.4", "@types/node": "18.19.29", - "@types/node-ipc": "9.2.0", + "@types/node-ipc": "9.2.3", "typescript": "4.7.4" }, "_moduleAliases": { diff --git a/package-lock.json b/package-lock.json index f5932a25b8..ad27ada66b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,7 +111,7 @@ "@types/node": "18.19.29", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", - "@types/node-ipc": "9.2.0", + "@types/node-ipc": "9.2.3", "@types/papaparse": "5.3.14", "@types/proper-lockfile": "4.1.4", "@types/react": "16.14.57", @@ -10544,9 +10544,9 @@ } }, "node_modules/@types/node-ipc": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@types/node-ipc/-/node-ipc-9.2.0.tgz", - "integrity": "sha512-0v1oucUgINvWPhknecSBE5xkz74sVgeZgiL/LkWXNTSzFaGspEToA4oR56hjza0Jkk6DsS2EiNU3M2R2KQza9A==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@types/node-ipc/-/node-ipc-9.2.3.tgz", + "integrity": "sha512-/MvSiF71fYf3+zwqkh/zkVkZj1hl1Uobre9EMFy08mqfJNAmpR0vmPgOUdEIDVgifxHj6G1vYMPLSBLLxoDACQ==", "dev": true, "dependencies": { "@types/node": "*" diff --git a/package.json b/package.json index 057e737903..09065b234e 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@types/node": "18.19.29", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", - "@types/node-ipc": "9.2.0", + "@types/node-ipc": "9.2.3", "@types/papaparse": "5.3.14", "@types/proper-lockfile": "4.1.4", "@types/react": "16.14.57", From 423d8c71b520a578169a49577779f2cc79ac710d Mon Sep 17 00:00:00 2001 From: watsondm <129207532+watsondm@users.noreply.github.com> Date: Wed, 24 Apr 2024 11:01:51 -0400 Subject: [PATCH 81/86] CLOUDOPS-1592 Remove artifacts R2 steps from desktop release workflows (#8897) * CLOUDOPS-1592 Remove artifacts R2 steps from desktop release workflows * CLOUDOPS-1592 Remove artifacts R2 steps from staged rollout workflow --- .github/workflows/release-desktop-beta.yml | 20 +---------- .github/workflows/release-desktop.yml | 21 +---------- .github/workflows/staged-rollout-desktop.yml | 37 ++++---------------- 3 files changed, 8 insertions(+), 70 deletions(-) diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index b9e2d7a8c8..46f4ffad57 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -955,11 +955,7 @@ jobs: keyvault: "bitwarden-ci" secrets: "aws-electron-access-id, aws-electron-access-key, - aws-electron-bucket-name, - r2-electron-access-id, - r2-electron-access-key, - r2-electron-bucket-name, - cf-prod-account" + aws-electron-bucket-name" - name: Download all artifacts uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 @@ -985,20 +981,6 @@ jobs: --recursive \ --quiet - - name: Publish artifacts to R2 - env: - AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }} - AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }} - AWS_DEFAULT_REGION: 'us-east-1' - AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }} - CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }} - working-directory: apps/desktop/artifacts - run: | - aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \ - --recursive \ - --quiet \ - --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com - - name: Update deployment status to Success if: ${{ success() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index cf857d7177..dc6957d00d 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -115,11 +115,7 @@ jobs: keyvault: "bitwarden-ci" secrets: "aws-electron-access-id, aws-electron-access-key, - aws-electron-bucket-name, - r2-electron-access-id, - r2-electron-access-key, - r2-electron-bucket-name, - cf-prod-account" + aws-electron-bucket-name" - name: Download all artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} @@ -169,21 +165,6 @@ jobs: --recursive \ --quiet - - name: Publish artifacts to R2 - if: ${{ github.event.inputs.release_type != 'Dry Run' && github.event.inputs.electron_publish == 'true' }} - env: - AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }} - AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }} - AWS_DEFAULT_REGION: 'us-east-1' - AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }} - CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }} - working-directory: apps/desktop/artifacts - run: | - aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \ - --recursive \ - --quiet \ - --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com - - name: Get checksum files uses: bitwarden/gh-actions/get-checksum@main with: diff --git a/.github/workflows/staged-rollout-desktop.yml b/.github/workflows/staged-rollout-desktop.yml index a5b5fc69b1..a6ca2f1e31 100644 --- a/.github/workflows/staged-rollout-desktop.yml +++ b/.github/workflows/staged-rollout-desktop.yml @@ -31,29 +31,21 @@ jobs: keyvault: "bitwarden-ci" secrets: "aws-electron-access-id, aws-electron-access-key, - aws-electron-bucket-name, - r2-electron-access-id, - r2-electron-access-key, - r2-electron-bucket-name, - cf-prod-account" + aws-electron-bucket-name" - - name: Download channel update info files from R2 + - name: Download channel update info files from S3 env: - AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }} - AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }} - AWS_DEFAULT_REGION: 'us-east-1' - AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }} - CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }} + 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 }} run: | aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest.yml . \ --quiet \ - --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-linux.yml . \ --quiet \ - --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-mac.yml . \ --quiet \ - --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com - name: Check new rollout percentage env: @@ -95,20 +87,3 @@ jobs: aws s3 cp latest-mac.yml $AWS_S3_BUCKET_NAME/desktop/ \ --acl "public-read" - - - name: Publish channel update info files to R2 - env: - AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }} - AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }} - AWS_DEFAULT_REGION: 'us-east-1' - AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.r2-electron-bucket-name }} - CF_ACCOUNT: ${{ steps.retrieve-secrets.outputs.cf-prod-account }} - run: | - aws s3 cp latest.yml $AWS_S3_BUCKET_NAME/desktop/ \ - --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com - - aws s3 cp latest-linux.yml $AWS_S3_BUCKET_NAME/desktop/ \ - --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com - - aws s3 cp latest-mac.yml $AWS_S3_BUCKET_NAME/desktop/ \ - --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com From 493b79b8881b44b34c2a19ded009308960b16fac Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Wed, 24 Apr 2024 11:14:53 -0400 Subject: [PATCH 82/86] Only Run Migrations in True Background (#8548) * Only Run Migrations in True Background * Use `isPrivateMode` * Use `popupOnlyContext` --- apps/browser/src/background/main.background.ts | 3 ++- apps/browser/src/popup/services/init.service.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index a64ee2b8a0..6069e9b5e9 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1095,7 +1095,8 @@ export default class MainBackground { async bootstrap() { this.containerService.attachToGlobal(self); - await this.stateService.init({ runMigrations: !this.isPrivateMode }); + // Only the "true" background should run migrations + await this.stateService.init({ runMigrations: !this.popupOnlyContext }); // This is here instead of in in the InitService b/c we don't plan for // side effects to run in the Browser InitService. diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index c9e6d66c2a..ee842565d7 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -22,7 +22,7 @@ export class InitService { init() { return async () => { - await this.stateService.init(); + await this.stateService.init({ runMigrations: false }); // Browser background is responsible for migrations await this.i18nService.init(); if (!BrowserPopupUtils.inPopup(window)) { From b7957d6e28f7826b042e41063c2d4642aa09ece5 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Wed, 24 Apr 2024 11:19:10 -0400 Subject: [PATCH 83/86] set keypair before creating hub connection for admin requests (#8898) --- .../src/auth/components/login-via-auth-request.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 5a1180cd38..3b827669a5 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 @@ -221,7 +221,8 @@ export class LoginViaAuthRequestComponent } // Request still pending response from admin - // So, create hub connection so that any approvals will be received via push notification + // set keypair and create hub connection so that any approvals will be received via push notification + this.authRequestKeyPair = { privateKey: adminAuthReqStorable.privateKey, publicKey: null }; await this.anonymousHubService.createHubConnection(adminAuthReqStorable.id); } From 94fe9bd053b24211032430258416b4d7116b93f4 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Wed, 24 Apr 2024 11:20:13 -0400 Subject: [PATCH 84/86] Remove `StateService` `useAccountCache` (#8882) * Remove Account Cache from StateService * Remove Extra Change * Fix Desktop Build --- .../state-service.factory.ts | 2 - .../services/browser-state.service.spec.ts | 4 -- .../services/default-browser-state.service.ts | 40 ------------------- .../src/app/services/services.module.ts | 2 - apps/desktop/src/main.ts | 1 - apps/web/src/app/core/core.module.ts | 5 --- apps/web/src/app/core/state/state.service.ts | 3 -- libs/angular/src/services/injection-tokens.ts | 1 - .../src/services/jslib-services.module.ts | 6 --- .../src/platform/services/state.service.ts | 31 -------------- 10 files changed, 95 deletions(-) diff --git a/apps/browser/src/platform/background/service-factories/state-service.factory.ts b/apps/browser/src/platform/background/service-factories/state-service.factory.ts index 5567e00990..026a29668e 100644 --- a/apps/browser/src/platform/background/service-factories/state-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/state-service.factory.ts @@ -30,7 +30,6 @@ import { type StateServiceFactoryOptions = FactoryOptions & { stateServiceOptions: { - useAccountCache?: boolean; stateFactory: StateFactory; }; }; @@ -64,7 +63,6 @@ export async function stateServiceFactory( await environmentServiceFactory(cache, opts), await tokenServiceFactory(cache, opts), await migrationRunnerFactory(cache, opts), - opts.stateServiceOptions.useAccountCache, ), ); // TODO: If we run migration through a chrome installed/updated event we can turn off running migrations diff --git a/apps/browser/src/platform/services/browser-state.service.spec.ts b/apps/browser/src/platform/services/browser-state.service.spec.ts index 8f43998321..f06126dcf5 100644 --- a/apps/browser/src/platform/services/browser-state.service.spec.ts +++ b/apps/browser/src/platform/services/browser-state.service.spec.ts @@ -27,7 +27,6 @@ describe("Browser State Service", () => { let diskStorageService: MockProxy; let logService: MockProxy; let stateFactory: MockProxy>; - let useAccountCache: boolean; let environmentService: MockProxy; let tokenService: MockProxy; let migrationRunner: MockProxy; @@ -46,8 +45,6 @@ describe("Browser State Service", () => { environmentService = mock(); tokenService = mock(); migrationRunner = mock(); - // turn off account cache for tests - useAccountCache = false; state = new State(new GlobalState()); state.accounts[userId] = new Account({ @@ -78,7 +75,6 @@ describe("Browser State Service", () => { environmentService, tokenService, migrationRunner, - useAccountCache, ); }); diff --git a/apps/browser/src/platform/services/default-browser-state.service.ts b/apps/browser/src/platform/services/default-browser-state.service.ts index f1f306dbc0..b9cd219076 100644 --- a/apps/browser/src/platform/services/default-browser-state.service.ts +++ b/apps/browser/src/platform/services/default-browser-state.service.ts @@ -15,7 +15,6 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service"; import { Account } from "../../models/account"; -import { BrowserApi } from "../browser/browser-api"; import { browserSession, sessionSync } from "../decorators/session-sync-observable"; import { BrowserStateService } from "./abstractions/browser-state.service"; @@ -45,7 +44,6 @@ export class DefaultBrowserStateService environmentService: EnvironmentService, tokenService: TokenService, migrationRunner: MigrationRunner, - useAccountCache = true, ) { super( storageService, @@ -57,45 +55,7 @@ export class DefaultBrowserStateService environmentService, tokenService, migrationRunner, - useAccountCache, ); - - // TODO: This is a hack to fix having a disk cache on both the popup and - // the background page that can get out of sync. We need to work out the - // best way to handle caching with multiple instances of the state service. - if (useAccountCache) { - BrowserApi.storageChangeListener((changes, namespace) => { - if (namespace === "local") { - for (const key of Object.keys(changes)) { - if (key !== "accountActivity" && this.accountDiskCache.value[key]) { - this.deleteDiskCache(key); - } - } - } - }); - - BrowserApi.addListener( - chrome.runtime.onMessage, - (message: { command: string }, _, respond) => { - if (message.command === "initializeDiskCache") { - respond(JSON.stringify(this.accountDiskCache.value)); - } - }, - ); - } - } - - override async initAccountState(): Promise { - if (this.isRecoveredSession && this.useAccountCache) { - // request cache initialization - - const response = await BrowserApi.sendMessageWithResponse("initializeDiskCache"); - this.accountDiskCache.next(JSON.parse(response)); - - return; - } - - await super.initAccountState(); } async addAccount(account: Account) { diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index d1d51c0f1c..c15743ba5c 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -4,7 +4,6 @@ import { Subject, merge } from "rxjs"; import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; import { SECURE_STORAGE, - STATE_SERVICE_USE_CACHE, LOCALES_DIRECTORY, SYSTEM_LANGUAGE, MEMORY_STORAGE, @@ -205,7 +204,6 @@ const safeProviders: SafeProvider[] = [ EnvironmentService, TokenService, MigrationRunner, - STATE_SERVICE_USE_CACHE, ], }), safeProvider({ diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index bffd2002ff..da4c14b4aa 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -205,7 +205,6 @@ export class Main { this.environmentService, this.tokenService, this.migrationRunner, - false, // Do not use disk caching because this will get out of sync with the renderer service ); this.desktopSettingsService = new DesktopSettingsService(stateProvider); diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index a274764756..7a95650039 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -5,7 +5,6 @@ import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/sa import { SECURE_STORAGE, STATE_FACTORY, - STATE_SERVICE_USE_CACHE, LOCALES_DIRECTORY, SYSTEM_LANGUAGE, MEMORY_STORAGE, @@ -78,10 +77,6 @@ const safeProviders: SafeProvider[] = [ provide: STATE_FACTORY, useValue: new StateFactory(GlobalState, Account), }), - safeProvider({ - provide: STATE_SERVICE_USE_CACHE, - useValue: false, - }), safeProvider({ provide: I18nServiceAbstraction, useClass: I18nService, diff --git a/apps/web/src/app/core/state/state.service.ts b/apps/web/src/app/core/state/state.service.ts index 1ae62d8591..185509e150 100644 --- a/apps/web/src/app/core/state/state.service.ts +++ b/apps/web/src/app/core/state/state.service.ts @@ -4,7 +4,6 @@ import { MEMORY_STORAGE, SECURE_STORAGE, STATE_FACTORY, - STATE_SERVICE_USE_CACHE, } from "@bitwarden/angular/services/injection-tokens"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; @@ -34,7 +33,6 @@ export class StateService extends BaseStateService { environmentService: EnvironmentService, tokenService: TokenService, migrationRunner: MigrationRunner, - @Inject(STATE_SERVICE_USE_CACHE) useAccountCache = true, ) { super( storageService, @@ -46,7 +44,6 @@ export class StateService extends BaseStateService { environmentService, tokenService, migrationRunner, - useAccountCache, ); } diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 6fffe722fb..413fc5b530 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -36,7 +36,6 @@ export const MEMORY_STORAGE = new SafeInjectionToken("SECURE_STORAGE"); export const STATE_FACTORY = new SafeInjectionToken("STATE_FACTORY"); -export const STATE_SERVICE_USE_CACHE = new SafeInjectionToken("STATE_SERVICE_USE_CACHE"); export const LOGOUT_CALLBACK = new SafeInjectionToken< (expired: boolean, userId?: string) => Promise >("LOGOUT_CALLBACK"); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index f31bcb1c51..a63f4f0f7d 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -269,7 +269,6 @@ import { SafeInjectionToken, SECURE_STORAGE, STATE_FACTORY, - STATE_SERVICE_USE_CACHE, SUPPORTS_SECURE_STORAGE, SYSTEM_LANGUAGE, SYSTEM_THEME_OBSERVABLE, @@ -313,10 +312,6 @@ const safeProviders: SafeProvider[] = [ provide: STATE_FACTORY, useValue: new StateFactory(GlobalState, Account), }), - safeProvider({ - provide: STATE_SERVICE_USE_CACHE, - useValue: true, - }), safeProvider({ provide: LOGOUT_CALLBACK, useFactory: @@ -690,7 +685,6 @@ const safeProviders: SafeProvider[] = [ EnvironmentService, TokenServiceAbstraction, MigrationRunner, - STATE_SERVICE_USE_CACHE, ], }), safeProvider({ diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 412176e235..8758f6d200 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -65,8 +65,6 @@ export class StateService< private hasBeenInited = false; protected isRecoveredSession = false; - protected accountDiskCache = new BehaviorSubject>({}); - // default account serializer, must be overridden by child class protected accountDeserializer = Account.fromJSON as (json: Jsonify) => TAccount; @@ -80,7 +78,6 @@ export class StateService< protected environmentService: EnvironmentService, protected tokenService: TokenService, private migrationRunner: MigrationRunner, - protected useAccountCache: boolean = true, ) {} async init(initOptions: InitOptions = {}): Promise { @@ -995,13 +992,6 @@ export class StateService< return null; } - if (this.useAccountCache) { - const cachedAccount = this.accountDiskCache.value[options.userId]; - if (cachedAccount != null) { - return cachedAccount; - } - } - const account = options?.useSecureStorage ? (await this.secureStorageService.get(options.userId, options)) ?? (await this.storageService.get( @@ -1009,8 +999,6 @@ export class StateService< this.reconcileOptions(options, { htmlStorageLocation: HtmlStorageLocation.Local }), )) : await this.storageService.get(options.userId, options); - - this.setDiskCache(options.userId, account); return account; } @@ -1040,8 +1028,6 @@ export class StateService< : this.storageService; await storageLocation.save(`${options.userId}`, account, options); - - this.deleteDiskCache(options.userId); } protected async saveAccountToMemory(account: TAccount): Promise { @@ -1241,9 +1227,6 @@ export class StateService< await this.updateState(async (state) => { userId = userId ?? state.activeUserId; delete state.accounts[userId]; - - this.deleteDiskCache(userId); - return state; }); } @@ -1357,20 +1340,6 @@ export class StateService< return await this.setState(updatedState); }); } - - private setDiskCache(key: string, value: TAccount, options?: StorageOptions) { - if (this.useAccountCache) { - this.accountDiskCache.value[key] = value; - this.accountDiskCache.next(this.accountDiskCache.value); - } - } - - protected deleteDiskCache(key: string) { - if (this.useAccountCache) { - delete this.accountDiskCache.value[key]; - this.accountDiskCache.next(this.accountDiskCache.value); - } - } } function withPrototypeForArrayMembers( From a12c140792c708073623356b45f0dfef58566cb0 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:37:19 -0400 Subject: [PATCH 85/86] =?UTF-8?q?Revert=20"Revert=20"Auth/PM-6689=20-=20Mi?= =?UTF-8?q?grate=20Security=20Stamp=20to=20Token=20Service=20and=20St?= =?UTF-8?q?=E2=80=A6"=20(#8889)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 100b43dd8f7ac23cb888b0f031353aa68beffb82. --- .../browser/src/background/main.background.ts | 1 + apps/cli/src/bw.ts | 1 + .../src/services/jslib-services.module.ts | 1 + .../login-strategies/login.strategy.spec.ts | 4 - .../common/login-strategies/login.strategy.ts | 9 +-- .../src/auth/abstractions/token.service.ts | 6 ++ .../src/auth/services/token.service.spec.ts | 79 +++++++++++++++++++ .../common/src/auth/services/token.service.ts | 25 ++++++ .../src/auth/services/token.state.spec.ts | 2 + libs/common/src/auth/services/token.state.ts | 5 ++ .../platform/abstractions/state.service.ts | 2 - .../models/domain/account-tokens.spec.ts | 9 --- .../platform/models/domain/account.spec.ts | 4 +- .../src/platform/models/domain/account.ts | 18 ----- .../src/platform/services/state.service.ts | 17 ---- .../src/vault/services/sync/sync.service.ts | 6 +- 16 files changed, 126 insertions(+), 63 deletions(-) delete mode 100644 libs/common/src/platform/models/domain/account-tokens.spec.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 6069e9b5e9..bee102be46 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -813,6 +813,7 @@ export default class MainBackground { this.avatarService, logoutCallback, this.billingAccountProfileStateService, + this.tokenService, ); this.eventUploadService = new EventUploadService( this.apiService, diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 58329128b8..437f807bc6 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -631,6 +631,7 @@ export class Main { this.avatarService, async (expired: boolean) => await this.logout(), this.billingAccountProfileStateService, + this.tokenService, ); this.totpService = new TotpService(this.cryptoFunctionService, this.logService); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index a63f4f0f7d..45f11befa6 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -623,6 +623,7 @@ const safeProviders: SafeProvider[] = [ AvatarServiceAbstraction, LOGOUT_CALLBACK, BillingAccountProfileStateService, + TokenServiceAbstraction, ], }), safeProvider({ diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index 431f736e94..e0833342ce 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -27,7 +27,6 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Account, AccountProfile, - AccountTokens, AccountKeys, } from "@bitwarden/common/platform/models/domain/account"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -213,9 +212,6 @@ describe("LoginStrategy", () => { kdfType: kdf, }, }, - tokens: { - ...new AccountTokens(), - }, keys: new AccountKeys(), }), ); diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index a6dc193183..a73c32e120 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -27,11 +27,7 @@ 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 { - Account, - AccountProfile, - AccountTokens, -} from "@bitwarden/common/platform/models/domain/account"; +import { Account, AccountProfile } from "@bitwarden/common/platform/models/domain/account"; import { UserId } from "@bitwarden/common/types/guid"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; @@ -192,9 +188,6 @@ export abstract class LoginStrategy { kdfType: tokenResponse.kdf, }, }, - tokens: { - ...new AccountTokens(), - }, }), ); diff --git a/libs/common/src/auth/abstractions/token.service.ts b/libs/common/src/auth/abstractions/token.service.ts index 75bb383882..fc3bd317f4 100644 --- a/libs/common/src/auth/abstractions/token.service.ts +++ b/libs/common/src/auth/abstractions/token.service.ts @@ -213,4 +213,10 @@ export abstract class TokenService { * @returns A promise that resolves with a boolean representing the user's external authN status. */ getIsExternal: () => Promise; + + /** Gets the active or passed in user's security stamp */ + getSecurityStamp: (userId?: UserId) => Promise; + + /** Sets the security stamp for the active or passed in user */ + setSecurityStamp: (securityStamp: string, userId?: UserId) => Promise; } diff --git a/libs/common/src/auth/services/token.service.spec.ts b/libs/common/src/auth/services/token.service.spec.ts index d32c4d8e1c..3e92053d2f 100644 --- a/libs/common/src/auth/services/token.service.spec.ts +++ b/libs/common/src/auth/services/token.service.spec.ts @@ -23,6 +23,7 @@ import { EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL, REFRESH_TOKEN_DISK, REFRESH_TOKEN_MEMORY, + SECURITY_STAMP_MEMORY, } from "./token.state"; describe("TokenService", () => { @@ -2191,6 +2192,84 @@ describe("TokenService", () => { }); }); + describe("Security Stamp methods", () => { + const mockSecurityStamp = "securityStamp"; + + describe("setSecurityStamp", () => { + it("should throw an error if no user id is provided and there is no active user in global state", async () => { + // Act + // note: don't await here because we want to test the error + const result = tokenService.setSecurityStamp(mockSecurityStamp); + // Assert + await expect(result).rejects.toThrow("User id not found. Cannot set security stamp."); + }); + + it("should set the security stamp in memory when there is an active user in global state", async () => { + // Arrange + globalStateProvider + .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) + .stateSubject.next(userIdFromAccessToken); + + // Act + await tokenService.setSecurityStamp(mockSecurityStamp); + + // Assert + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY).nextMock, + ).toHaveBeenCalledWith(mockSecurityStamp); + }); + + it("should set the security stamp in memory for the specified user id", async () => { + // Act + await tokenService.setSecurityStamp(mockSecurityStamp, userIdFromAccessToken); + + // Assert + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY).nextMock, + ).toHaveBeenCalledWith(mockSecurityStamp); + }); + }); + + describe("getSecurityStamp", () => { + it("should throw an error if no user id is provided and there is no active user in global state", async () => { + // Act + // note: don't await here because we want to test the error + const result = tokenService.getSecurityStamp(); + // Assert + await expect(result).rejects.toThrow("User id not found. Cannot get security stamp."); + }); + + it("should return the security stamp from memory with no user id specified (uses global active user)", async () => { + // Arrange + globalStateProvider + .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) + .stateSubject.next(userIdFromAccessToken); + + singleUserStateProvider + .getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY) + .stateSubject.next([userIdFromAccessToken, mockSecurityStamp]); + + // Act + const result = await tokenService.getSecurityStamp(); + + // Assert + expect(result).toEqual(mockSecurityStamp); + }); + + it("should return the security stamp from memory for the specified user id", async () => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY) + .stateSubject.next([userIdFromAccessToken, mockSecurityStamp]); + + // Act + const result = await tokenService.getSecurityStamp(userIdFromAccessToken); + // Assert + expect(result).toEqual(mockSecurityStamp); + }); + }); + }); + // Helpers function createTokenService(supportsSecureStorage: boolean) { return new TokenService( diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index c24a2c186b..40036a8453 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -32,6 +32,7 @@ import { EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL, REFRESH_TOKEN_DISK, REFRESH_TOKEN_MEMORY, + SECURITY_STAMP_MEMORY, } from "./token.state"; export enum TokenStorageLocation { @@ -850,6 +851,30 @@ export class TokenService implements TokenServiceAbstraction { return Array.isArray(decoded.amr) && decoded.amr.includes("external"); } + async getSecurityStamp(userId?: UserId): Promise { + userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$); + + if (!userId) { + throw new Error("User id not found. Cannot get security stamp."); + } + + const securityStamp = await this.getStateValueByUserIdAndKeyDef(userId, SECURITY_STAMP_MEMORY); + + return securityStamp; + } + + async setSecurityStamp(securityStamp: string, userId?: UserId): Promise { + userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$); + + if (!userId) { + throw new Error("User id not found. Cannot set security stamp."); + } + + await this.singleUserStateProvider + .get(userId, SECURITY_STAMP_MEMORY) + .update((_) => securityStamp); + } + private async getStateValueByUserIdAndKeyDef( userId: UserId, storageLocation: UserKeyDefinition, diff --git a/libs/common/src/auth/services/token.state.spec.ts b/libs/common/src/auth/services/token.state.spec.ts index dc00fec383..bb82410fac 100644 --- a/libs/common/src/auth/services/token.state.spec.ts +++ b/libs/common/src/auth/services/token.state.spec.ts @@ -10,6 +10,7 @@ import { EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL, REFRESH_TOKEN_DISK, REFRESH_TOKEN_MEMORY, + SECURITY_STAMP_MEMORY, } from "./token.state"; describe.each([ @@ -22,6 +23,7 @@ describe.each([ [API_KEY_CLIENT_ID_MEMORY, "apiKeyClientIdMemory"], [API_KEY_CLIENT_SECRET_DISK, "apiKeyClientSecretDisk"], [API_KEY_CLIENT_SECRET_MEMORY, "apiKeyClientSecretMemory"], + [SECURITY_STAMP_MEMORY, "securityStamp"], ])( "deserializes state key definitions", ( diff --git a/libs/common/src/auth/services/token.state.ts b/libs/common/src/auth/services/token.state.ts index 458d6846c1..57d85f2a55 100644 --- a/libs/common/src/auth/services/token.state.ts +++ b/libs/common/src/auth/services/token.state.ts @@ -69,3 +69,8 @@ export const API_KEY_CLIENT_SECRET_MEMORY = new UserKeyDefinition( clearOn: [], // Manually handled }, ); + +export const SECURITY_STAMP_MEMORY = new UserKeyDefinition(TOKEN_MEMORY, "securityStamp", { + deserializer: (securityStamp) => securityStamp, + clearOn: ["logout"], +}); diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 051604f0ae..f1d4b3848e 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -181,8 +181,6 @@ export abstract class StateService { * Sets the user's Pin, encrypted by the user key */ setProtectedPin: (value: string, options?: StorageOptions) => Promise; - getSecurityStamp: (options?: StorageOptions) => Promise; - setSecurityStamp: (value: string, options?: StorageOptions) => Promise; getUserId: (options?: StorageOptions) => Promise; getVaultTimeout: (options?: StorageOptions) => Promise; setVaultTimeout: (value: number, options?: StorageOptions) => Promise; diff --git a/libs/common/src/platform/models/domain/account-tokens.spec.ts b/libs/common/src/platform/models/domain/account-tokens.spec.ts deleted file mode 100644 index 733b3908e9..0000000000 --- a/libs/common/src/platform/models/domain/account-tokens.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AccountTokens } from "./account"; - -describe("AccountTokens", () => { - describe("fromJSON", () => { - it("should deserialize to an instance of itself", () => { - expect(AccountTokens.fromJSON({})).toBeInstanceOf(AccountTokens); - }); - }); -}); diff --git a/libs/common/src/platform/models/domain/account.spec.ts b/libs/common/src/platform/models/domain/account.spec.ts index 0c76c16cc2..77c242b6ff 100644 --- a/libs/common/src/platform/models/domain/account.spec.ts +++ b/libs/common/src/platform/models/domain/account.spec.ts @@ -1,4 +1,4 @@ -import { Account, AccountKeys, AccountProfile, AccountSettings, AccountTokens } from "./account"; +import { Account, AccountKeys, AccountProfile, AccountSettings } from "./account"; describe("Account", () => { describe("fromJSON", () => { @@ -10,14 +10,12 @@ describe("Account", () => { const keysSpy = jest.spyOn(AccountKeys, "fromJSON"); const profileSpy = jest.spyOn(AccountProfile, "fromJSON"); const settingsSpy = jest.spyOn(AccountSettings, "fromJSON"); - const tokensSpy = jest.spyOn(AccountTokens, "fromJSON"); Account.fromJSON({}); expect(keysSpy).toHaveBeenCalled(); expect(profileSpy).toHaveBeenCalled(); expect(settingsSpy).toHaveBeenCalled(); - expect(tokensSpy).toHaveBeenCalled(); }); }); }); diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index 5a9a764696..cd416ec1f9 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -171,24 +171,11 @@ export class AccountSettings { } } -export class AccountTokens { - securityStamp?: string; - - static fromJSON(obj: Jsonify): AccountTokens { - if (obj == null) { - return null; - } - - return Object.assign(new AccountTokens(), obj); - } -} - export class Account { data?: AccountData = new AccountData(); keys?: AccountKeys = new AccountKeys(); profile?: AccountProfile = new AccountProfile(); settings?: AccountSettings = new AccountSettings(); - tokens?: AccountTokens = new AccountTokens(); constructor(init: Partial) { Object.assign(this, { @@ -208,10 +195,6 @@ export class Account { ...new AccountSettings(), ...init?.settings, }, - tokens: { - ...new AccountTokens(), - ...init?.tokens, - }, }); } @@ -225,7 +208,6 @@ export class Account { data: AccountData.fromJSON(json?.data), profile: AccountProfile.fromJSON(json?.profile), settings: AccountSettings.fromJSON(json?.settings), - tokens: AccountTokens.fromJSON(json?.tokens), }); } } diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 8758f6d200..0c7cdd22d2 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -841,23 +841,6 @@ export class StateService< ); } - async getSecurityStamp(options?: StorageOptions): Promise { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) - )?.tokens?.securityStamp; - } - - async setSecurityStamp(value: string, options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - account.tokens.securityStamp = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - } - async getUserId(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) diff --git a/libs/common/src/vault/services/sync/sync.service.ts b/libs/common/src/vault/services/sync/sync.service.ts index ff8e9f1f4f..73869ff488 100644 --- a/libs/common/src/vault/services/sync/sync.service.ts +++ b/libs/common/src/vault/services/sync/sync.service.ts @@ -15,6 +15,7 @@ import { AccountService } from "../../../auth/abstractions/account.service"; import { AvatarService } from "../../../auth/abstractions/avatar.service"; import { KeyConnectorService } from "../../../auth/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "../../../auth/abstractions/master-password.service.abstraction"; +import { TokenService } from "../../../auth/abstractions/token.service"; import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; import { BillingAccountProfileStateService } from "../../../billing/abstractions/account/billing-account-profile-state.service"; @@ -73,6 +74,7 @@ export class SyncService implements SyncServiceAbstraction { private avatarService: AvatarService, private logoutCallback: (expired: boolean) => Promise, private billingAccountProfileStateService: BillingAccountProfileStateService, + private tokenService: TokenService, ) {} async getLastSync(): Promise { @@ -309,7 +311,7 @@ export class SyncService implements SyncServiceAbstraction { } private async syncProfile(response: ProfileResponse) { - const stamp = await this.stateService.getSecurityStamp(); + const stamp = await this.tokenService.getSecurityStamp(response.id as UserId); if (stamp != null && stamp !== response.securityStamp) { if (this.logoutCallback != null) { await this.logoutCallback(true); @@ -323,7 +325,7 @@ export class SyncService implements SyncServiceAbstraction { await this.cryptoService.setProviderKeys(response.providers); await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations); await this.avatarService.setSyncAvatarColor(response.id as UserId, response.avatarColor); - await this.stateService.setSecurityStamp(response.securityStamp); + await this.tokenService.setSecurityStamp(response.securityStamp, response.id as UserId); await this.stateService.setEmailVerified(response.emailVerified); await this.billingAccountProfileStateService.setHasPremium( From 5dc83cd34c9a5e93f1fce7a614af6af11b3e383e Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:54:54 -0400 Subject: [PATCH 86/86] PM-6787 - Rename DeviceTrustCryptoService to DeviceTrustService (#8819) --- ...ory.ts => device-trust-service.factory.ts} | 20 ++-- .../login-strategy-service.factory.ts | 10 +- apps/browser/src/auth/popup/lock.component.ts | 6 +- .../popup/login-via-auth-request.component.ts | 6 +- .../browser/src/background/main.background.ts | 10 +- .../src/popup/services/services.module.ts | 6 +- apps/cli/src/bw.ts | 10 +- apps/desktop/src/auth/lock.component.spec.ts | 6 +- apps/desktop/src/auth/lock.component.ts | 6 +- .../login/login-via-auth-request.component.ts | 6 +- .../user-key-rotation.service.spec.ts | 8 +- .../key-rotation/user-key-rotation.service.ts | 6 +- ...base-login-decryption-options.component.ts | 12 +- .../src/auth/components/lock.component.ts | 6 +- .../login-via-auth-request.component.ts | 6 +- libs/angular/src/auth/guards/lock.guard.ts | 6 +- .../angular/src/auth/guards/redirect.guard.ts | 6 +- .../guards/tde-decryption-required.guard.ts | 6 +- .../src/services/jslib-services.module.ts | 10 +- .../auth-request-login.strategy.spec.ts | 12 +- .../auth-request-login.strategy.ts | 6 +- .../sso-login.strategy.spec.ts | 38 +++--- .../login-strategies/sso-login.strategy.ts | 10 +- .../login-strategy.service.spec.ts | 8 +- .../login-strategy.service.ts | 8 +- ...ts => device-trust.service.abstraction.ts} | 5 +- ...=> device-trust.service.implementation.ts} | 4 +- ...e.spec.ts => device-trust.service.spec.ts} | 111 ++++++++---------- libs/common/src/state-migrations/migrate.ts | 4 +- ...vice-trust-svc-to-state-providers.spec.ts} | 12 +- ...te-device-trust-svc-to-state-providers.ts} | 4 +- ...resh-token-migrated-state-provider-flag.ts | 2 +- 32 files changed, 182 insertions(+), 194 deletions(-) rename apps/browser/src/auth/background/service-factories/{device-trust-crypto-service.factory.ts => device-trust-service.factory.ts} (79%) rename libs/common/src/auth/abstractions/{device-trust-crypto.service.abstraction.ts => device-trust.service.abstraction.ts} (89%) rename libs/common/src/auth/services/{device-trust-crypto.service.implementation.ts => device-trust.service.implementation.ts} (98%) rename libs/common/src/auth/services/{device-trust-crypto.service.spec.ts => device-trust.service.spec.ts} (86%) rename libs/common/src/state-migrations/migrations/{53-migrate-device-trust-crypto-svc-to-state-providers.spec.ts => 53-migrate-device-trust-svc-to-state-providers.spec.ts} (92%) rename libs/common/src/state-migrations/migrations/{53-migrate-device-trust-crypto-svc-to-state-providers.ts => 53-migrate-device-trust-svc-to-state-providers.ts} (94%) diff --git a/apps/browser/src/auth/background/service-factories/device-trust-crypto-service.factory.ts b/apps/browser/src/auth/background/service-factories/device-trust-service.factory.ts similarity index 79% rename from apps/browser/src/auth/background/service-factories/device-trust-crypto-service.factory.ts rename to apps/browser/src/auth/background/service-factories/device-trust-service.factory.ts index cac6f9bbe8..106bcbcf72 100644 --- a/apps/browser/src/auth/background/service-factories/device-trust-crypto-service.factory.ts +++ b/apps/browser/src/auth/background/service-factories/device-trust-service.factory.ts @@ -1,5 +1,5 @@ -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; -import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; +import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DevicesApiServiceInitOptions, @@ -52,9 +52,9 @@ import { userDecryptionOptionsServiceFactory, } from "./user-decryption-options-service.factory"; -type DeviceTrustCryptoServiceFactoryOptions = FactoryOptions; +type DeviceTrustServiceFactoryOptions = FactoryOptions; -export type DeviceTrustCryptoServiceInitOptions = DeviceTrustCryptoServiceFactoryOptions & +export type DeviceTrustServiceInitOptions = DeviceTrustServiceFactoryOptions & KeyGenerationServiceInitOptions & CryptoFunctionServiceInitOptions & CryptoServiceInitOptions & @@ -67,16 +67,16 @@ export type DeviceTrustCryptoServiceInitOptions = DeviceTrustCryptoServiceFactor SecureStorageServiceInitOptions & UserDecryptionOptionsServiceInitOptions; -export function deviceTrustCryptoServiceFactory( - cache: { deviceTrustCryptoService?: DeviceTrustCryptoServiceAbstraction } & CachedServices, - opts: DeviceTrustCryptoServiceInitOptions, -): Promise { +export function deviceTrustServiceFactory( + cache: { deviceTrustService?: DeviceTrustServiceAbstraction } & CachedServices, + opts: DeviceTrustServiceInitOptions, +): Promise { return factory( cache, - "deviceTrustCryptoService", + "deviceTrustService", opts, async () => - new DeviceTrustCryptoService( + new DeviceTrustService( await keyGenerationServiceFactory(cache, opts), await cryptoFunctionServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts), diff --git a/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts b/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts index f184072cce..075ba614b7 100644 --- a/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts +++ b/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts @@ -65,9 +65,9 @@ import { AuthRequestServiceInitOptions, } from "./auth-request-service.factory"; import { - deviceTrustCryptoServiceFactory, - DeviceTrustCryptoServiceInitOptions, -} from "./device-trust-crypto-service.factory"; + deviceTrustServiceFactory, + DeviceTrustServiceInitOptions, +} from "./device-trust-service.factory"; import { keyConnectorServiceFactory, KeyConnectorServiceInitOptions, @@ -102,7 +102,7 @@ export type LoginStrategyServiceInitOptions = LoginStrategyServiceFactoryOptions EncryptServiceInitOptions & PolicyServiceInitOptions & PasswordStrengthServiceInitOptions & - DeviceTrustCryptoServiceInitOptions & + DeviceTrustServiceInitOptions & AuthRequestServiceInitOptions & UserDecryptionOptionsServiceInitOptions & GlobalStateProviderInitOptions & @@ -135,7 +135,7 @@ export function loginStrategyServiceFactory( await encryptServiceFactory(cache, opts), await passwordStrengthServiceFactory(cache, opts), await policyServiceFactory(cache, opts), - await deviceTrustCryptoServiceFactory(cache, opts), + await deviceTrustServiceFactory(cache, opts), await authRequestServiceFactory(cache, opts), await internalUserDecryptionOptionServiceFactory(cache, opts), await globalStateProviderFactory(cache, opts), diff --git a/apps/browser/src/auth/popup/lock.component.ts b/apps/browser/src/auth/popup/lock.component.ts index 16c32337cf..78039d793f 100644 --- a/apps/browser/src/auth/popup/lock.component.ts +++ b/apps/browser/src/auth/popup/lock.component.ts @@ -11,7 +11,7 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; 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"; @@ -60,7 +60,7 @@ export class LockComponent extends BaseLockComponent { passwordStrengthService: PasswordStrengthServiceAbstraction, private authService: AuthService, dialogService: DialogService, - deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + deviceTrustService: DeviceTrustServiceAbstraction, userVerificationService: UserVerificationService, pinCryptoService: PinCryptoServiceAbstraction, private routerService: BrowserRouterService, @@ -85,7 +85,7 @@ export class LockComponent extends BaseLockComponent { policyService, passwordStrengthService, dialogService, - deviceTrustCryptoService, + deviceTrustService, userVerificationService, pinCryptoService, biometricStateService, diff --git a/apps/browser/src/auth/popup/login-via-auth-request.component.ts b/apps/browser/src/auth/popup/login-via-auth-request.component.ts index 52f311ce7b..158296058e 100644 --- a/apps/browser/src/auth/popup/login-via-auth-request.component.ts +++ b/apps/browser/src/auth/popup/login-via-auth-request.component.ts @@ -12,7 +12,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -47,7 +47,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent { stateService: StateService, loginEmailService: LoginEmailServiceAbstraction, syncService: SyncService, - deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + deviceTrustService: DeviceTrustServiceAbstraction, authRequestService: AuthRequestServiceAbstraction, loginStrategyService: LoginStrategyServiceAbstraction, accountService: AccountService, @@ -69,7 +69,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent { validationService, stateService, loginEmailService, - deviceTrustCryptoService, + deviceTrustService, authRequestService, loginStrategyService, accountService, diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index bee102be46..dc93de7803 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -30,7 +30,7 @@ import { ProviderService } from "@bitwarden/common/admin-console/services/provid import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; @@ -45,7 +45,7 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; -import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation"; +import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; @@ -318,7 +318,7 @@ export default class MainBackground { configApiService: ConfigApiServiceAbstraction; devicesApiService: DevicesApiServiceAbstraction; devicesService: DevicesServiceAbstraction; - deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction; + deviceTrustService: DeviceTrustServiceAbstraction; authRequestService: AuthRequestServiceAbstraction; accountService: AccountServiceAbstraction; globalStateProvider: GlobalStateProvider; @@ -612,7 +612,7 @@ export default class MainBackground { this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); - this.deviceTrustCryptoService = new DeviceTrustCryptoService( + this.deviceTrustService = new DeviceTrustService( this.keyGenerationService, this.cryptoFunctionService, this.cryptoService, @@ -670,7 +670,7 @@ export default class MainBackground { this.encryptService, this.passwordStrengthService, this.policyService, - this.deviceTrustCryptoService, + this.deviceTrustService, this.authRequestService, this.userDecryptionOptionsService, this.globalStateProvider, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index a7da6b7612..38068d1849 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -28,7 +28,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; @@ -250,8 +250,8 @@ const safeProviders: SafeProvider[] = [ deps: [], }), safeProvider({ - provide: DeviceTrustCryptoServiceAbstraction, - useFactory: getBgService("deviceTrustCryptoService"), + provide: DeviceTrustServiceAbstraction, + useFactory: getBgService("deviceTrustService"), deps: [], }), safeProvider({ diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 437f807bc6..8163aa2945 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -28,13 +28,13 @@ import { ProviderApiService } from "@bitwarden/common/admin-console/services/pro import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; -import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation"; +import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service"; @@ -217,7 +217,7 @@ export class Main { syncNotifierService: SyncNotifierService; sendApiService: SendApiService; devicesApiService: DevicesApiServiceAbstraction; - deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction; + deviceTrustService: DeviceTrustServiceAbstraction; authRequestService: AuthRequestService; configApiService: ConfigApiServiceAbstraction; configService: ConfigService; @@ -460,7 +460,7 @@ export class Main { this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); - this.deviceTrustCryptoService = new DeviceTrustCryptoService( + this.deviceTrustService = new DeviceTrustService( this.keyGenerationService, this.cryptoFunctionService, this.cryptoService, @@ -505,7 +505,7 @@ export class Main { this.encryptService, this.passwordStrengthService, this.policyService, - this.deviceTrustCryptoService, + this.deviceTrustService, this.authRequestService, this.userDecryptionOptionsService, this.globalStateProvider, diff --git a/apps/desktop/src/auth/lock.component.spec.ts b/apps/desktop/src/auth/lock.component.spec.ts index c125eba022..480e443eab 100644 --- a/apps/desktop/src/auth/lock.component.spec.ts +++ b/apps/desktop/src/auth/lock.component.spec.ts @@ -13,7 +13,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeou import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; 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 { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; @@ -145,8 +145,8 @@ describe("LockComponent", () => { useValue: mock(), }, { - provide: DeviceTrustCryptoServiceAbstraction, - useValue: mock(), + provide: DeviceTrustServiceAbstraction, + useValue: mock(), }, { provide: UserVerificationService, diff --git a/apps/desktop/src/auth/lock.component.ts b/apps/desktop/src/auth/lock.component.ts index 16b58c5bbe..b8feef4ab5 100644 --- a/apps/desktop/src/auth/lock.component.ts +++ b/apps/desktop/src/auth/lock.component.ts @@ -10,7 +10,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeou import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; 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 { DeviceType } from "@bitwarden/common/enums"; @@ -58,7 +58,7 @@ export class LockComponent extends BaseLockComponent { passwordStrengthService: PasswordStrengthServiceAbstraction, logService: LogService, dialogService: DialogService, - deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + deviceTrustService: DeviceTrustServiceAbstraction, userVerificationService: UserVerificationService, pinCryptoService: PinCryptoServiceAbstraction, biometricStateService: BiometricStateService, @@ -82,7 +82,7 @@ export class LockComponent extends BaseLockComponent { policyService, passwordStrengthService, dialogService, - deviceTrustCryptoService, + deviceTrustService, userVerificationService, pinCryptoService, biometricStateService, diff --git a/apps/desktop/src/auth/login/login-via-auth-request.component.ts b/apps/desktop/src/auth/login/login-via-auth-request.component.ts index 0a339030ba..2d0f560205 100644 --- a/apps/desktop/src/auth/login/login-via-auth-request.component.ts +++ b/apps/desktop/src/auth/login/login-via-auth-request.component.ts @@ -13,7 +13,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -55,7 +55,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent { syncService: SyncService, stateService: StateService, loginEmailService: LoginEmailServiceAbstraction, - deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + deviceTrustService: DeviceTrustServiceAbstraction, authRequestService: AuthRequestServiceAbstraction, loginStrategyService: LoginStrategyServiceAbstraction, accountService: AccountService, @@ -77,7 +77,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent { validationService, stateService, loginEmailService, - deviceTrustCryptoService, + deviceTrustService, authRequestService, loginStrategyService, accountService, diff --git a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts index 0997f18864..ed665fe773 100644 --- a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts @@ -1,7 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -42,7 +42,7 @@ describe("KeyRotationService", () => { let mockSendService: MockProxy; let mockEmergencyAccessService: MockProxy; let mockResetPasswordService: MockProxy; - let mockDeviceTrustCryptoService: MockProxy; + let mockDeviceTrustService: MockProxy; let mockCryptoService: MockProxy; let mockEncryptService: MockProxy; let mockStateService: MockProxy; @@ -60,7 +60,7 @@ describe("KeyRotationService", () => { mockSendService = mock(); mockEmergencyAccessService = mock(); mockResetPasswordService = mock(); - mockDeviceTrustCryptoService = mock(); + mockDeviceTrustService = mock(); mockCryptoService = mock(); mockEncryptService = mock(); mockStateService = mock(); @@ -74,7 +74,7 @@ describe("KeyRotationService", () => { mockSendService, mockEmergencyAccessService, mockResetPasswordService, - mockDeviceTrustCryptoService, + mockDeviceTrustService, mockCryptoService, mockEncryptService, mockStateService, diff --git a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts index f5812d341a..2ff48809a0 100644 --- a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts @@ -2,7 +2,7 @@ import { Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -33,7 +33,7 @@ export class UserKeyRotationService { private sendService: SendService, private emergencyAccessService: EmergencyAccessService, private resetPasswordService: OrganizationUserResetPasswordService, - private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + private deviceTrustService: DeviceTrustServiceAbstraction, private cryptoService: CryptoService, private encryptService: EncryptService, private stateService: StateService, @@ -96,7 +96,7 @@ export class UserKeyRotationService { } const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - await this.deviceTrustCryptoService.rotateDevicesTrust( + await this.deviceTrustService.rotateDevicesTrust( activeAccount.id, newUserKey, masterPasswordHash, diff --git a/libs/angular/src/auth/components/base-login-decryption-options.component.ts b/libs/angular/src/auth/components/base-login-decryption-options.component.ts index 8345bb9939..0e58c03a54 100644 --- a/libs/angular/src/auth/components/base-login-decryption-options.component.ts +++ b/libs/angular/src/auth/components/base-login-decryption-options.component.ts @@ -23,7 +23,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; @@ -93,7 +93,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy { protected apiService: ApiService, protected i18nService: I18nService, protected validationService: ValidationService, - protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + protected deviceTrustService: DeviceTrustServiceAbstraction, protected platformUtilsService: PlatformUtilsService, protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, protected passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction, @@ -156,7 +156,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy { } private async setRememberDeviceDefaultValue() { - const rememberDeviceFromState = await this.deviceTrustCryptoService.getShouldTrustDevice( + const rememberDeviceFromState = await this.deviceTrustService.getShouldTrustDevice( this.activeAccountId, ); @@ -169,9 +169,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy { this.rememberDevice.valueChanges .pipe( switchMap((value) => - defer(() => - this.deviceTrustCryptoService.setShouldTrustDevice(this.activeAccountId, value), - ), + defer(() => this.deviceTrustService.setShouldTrustDevice(this.activeAccountId, value)), ), takeUntil(this.destroy$), ) @@ -288,7 +286,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy { await this.passwordResetEnrollmentService.enroll(this.data.organizationId); if (this.rememberDeviceForm.value.rememberDevice) { - await this.deviceTrustCryptoService.trustDevice(this.activeAccountId); + await this.deviceTrustService.trustDevice(this.activeAccountId); } } catch (error) { this.validationService.showError(error); diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts index 9c2ed55357..927fbb27b1 100644 --- a/libs/angular/src/auth/components/lock.component.ts +++ b/libs/angular/src/auth/components/lock.component.ts @@ -11,7 +11,7 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; 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 { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -74,7 +74,7 @@ export class LockComponent implements OnInit, OnDestroy { protected policyService: InternalPolicyService, protected passwordStrengthService: PasswordStrengthServiceAbstraction, protected dialogService: DialogService, - protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + protected deviceTrustService: DeviceTrustServiceAbstraction, protected userVerificationService: UserVerificationService, protected pinCryptoService: PinCryptoServiceAbstraction, protected biometricStateService: BiometricStateService, @@ -277,7 +277,7 @@ export class LockComponent implements OnInit, OnDestroy { // Now that we have a decrypted user key in memory, we can check if we // need to establish trust on the current device const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - await this.deviceTrustCryptoService.trustDeviceIfRequired(activeAccount.id); + await this.deviceTrustService.trustDeviceIfRequired(activeAccount.id); await this.doContinue(evaluatePasswordAfterUnlock); } 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 3b827669a5..a60468e244 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 @@ -12,7 +12,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; @@ -86,7 +86,7 @@ export class LoginViaAuthRequestComponent private validationService: ValidationService, private stateService: StateService, private loginEmailService: LoginEmailServiceAbstraction, - private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + private deviceTrustService: DeviceTrustServiceAbstraction, private authRequestService: AuthRequestServiceAbstraction, private loginStrategyService: LoginStrategyServiceAbstraction, private accountService: AccountService, @@ -402,7 +402,7 @@ export class LoginViaAuthRequestComponent // Now that we have a decrypted user key in memory, we can check if we // need to establish trust on the current device const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - await this.deviceTrustCryptoService.trustDeviceIfRequired(activeAccount.id); + await this.deviceTrustService.trustDeviceIfRequired(activeAccount.id); // TODO: don't forget to use auto enrollment service everywhere we trust device diff --git a/libs/angular/src/auth/guards/lock.guard.ts b/libs/angular/src/auth/guards/lock.guard.ts index 6f71d77a63..8cd5290ebc 100644 --- a/libs/angular/src/auth/guards/lock.guard.ts +++ b/libs/angular/src/auth/guards/lock.guard.ts @@ -8,7 +8,7 @@ import { import { firstValueFrom } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +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"; @@ -30,7 +30,7 @@ export function lockGuard(): CanActivateFn { ) => { const authService = inject(AuthService); const cryptoService = inject(CryptoService); - const deviceTrustCryptoService = inject(DeviceTrustCryptoServiceAbstraction); + const deviceTrustService = inject(DeviceTrustServiceAbstraction); const platformUtilService = inject(PlatformUtilsService); const messagingService = inject(MessagingService); const router = inject(Router); @@ -53,7 +53,7 @@ export function lockGuard(): CanActivateFn { // User is authN and in locked state. - const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$); + const tdeEnabled = await firstValueFrom(deviceTrustService.supportsDeviceTrust$); // Create special exception which allows users to go from the login-initiated page to the lock page for the approve w/ MP flow // The MP check is necessary to prevent direct manual navigation from other locked state pages for users who don't have a MP diff --git a/libs/angular/src/auth/guards/redirect.guard.ts b/libs/angular/src/auth/guards/redirect.guard.ts index ca9152186d..0c43673c34 100644 --- a/libs/angular/src/auth/guards/redirect.guard.ts +++ b/libs/angular/src/auth/guards/redirect.guard.ts @@ -3,7 +3,7 @@ import { CanActivateFn, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -31,7 +31,7 @@ export function redirectGuard(overrides: Partial = {}): CanActiv return async (route) => { const authService = inject(AuthService); const cryptoService = inject(CryptoService); - const deviceTrustCryptoService = inject(DeviceTrustCryptoServiceAbstraction); + const deviceTrustService = inject(DeviceTrustServiceAbstraction); const router = inject(Router); const authStatus = await authService.getAuthStatus(); @@ -46,7 +46,7 @@ export function redirectGuard(overrides: Partial = {}): CanActiv // If locked, TDE is enabled, and the user hasn't decrypted yet, then redirect to the // login decryption options component. - const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$); + const tdeEnabled = await firstValueFrom(deviceTrustService.supportsDeviceTrust$); const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$); if (authStatus === AuthenticationStatus.Locked && tdeEnabled && !everHadUserKey) { return router.createUrlTree([routes.notDecrypted], { queryParams: route.queryParams }); diff --git a/libs/angular/src/auth/guards/tde-decryption-required.guard.ts b/libs/angular/src/auth/guards/tde-decryption-required.guard.ts index 146c6e19a2..524ce7dce5 100644 --- a/libs/angular/src/auth/guards/tde-decryption-required.guard.ts +++ b/libs/angular/src/auth/guards/tde-decryption-required.guard.ts @@ -8,7 +8,7 @@ import { import { firstValueFrom } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -22,11 +22,11 @@ export function tdeDecryptionRequiredGuard(): CanActivateFn { return async (_: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { const authService = inject(AuthService); const cryptoService = inject(CryptoService); - const deviceTrustCryptoService = inject(DeviceTrustCryptoServiceAbstraction); + const deviceTrustService = inject(DeviceTrustServiceAbstraction); const router = inject(Router); const authStatus = await authService.getAuthStatus(); - const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$); + const tdeEnabled = await firstValueFrom(deviceTrustService.supportsDeviceTrust$); const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$); if (authStatus !== AuthenticationStatus.Locked || !tdeEnabled || everHadUserKey) { return router.createUrlTree(["/"]); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 45f11befa6..aabf823c0b 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -60,7 +60,7 @@ import { import { AnonymousHubService as AnonymousHubServiceAbstraction } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; @@ -82,7 +82,7 @@ import { AccountServiceImplementation } from "@bitwarden/common/auth/services/ac import { AnonymousHubService } from "@bitwarden/common/auth/services/anonymous-hub.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; -import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation"; +import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; @@ -385,7 +385,7 @@ const safeProviders: SafeProvider[] = [ EncryptService, PasswordStrengthServiceAbstraction, PolicyServiceAbstraction, - DeviceTrustCryptoServiceAbstraction, + DeviceTrustServiceAbstraction, AuthRequestServiceAbstraction, InternalUserDecryptionOptionsServiceAbstraction, GlobalStateProvider, @@ -949,8 +949,8 @@ const safeProviders: SafeProvider[] = [ deps: [DevicesApiServiceAbstraction], }), safeProvider({ - provide: DeviceTrustCryptoServiceAbstraction, - useClass: DeviceTrustCryptoService, + provide: DeviceTrustServiceAbstraction, + useClass: DeviceTrustService, deps: [ KeyGenerationServiceAbstraction, CryptoFunctionServiceAbstraction, 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 0ce6c9fed7..4e0b1ac3ac 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 @@ -1,7 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; @@ -42,7 +42,7 @@ describe("AuthRequestLoginStrategy", () => { let stateService: MockProxy; let twoFactorService: MockProxy; let userDecryptionOptions: MockProxy; - let deviceTrustCryptoService: MockProxy; + let deviceTrustService: MockProxy; let billingAccountProfileStateService: MockProxy; const mockUserId = Utils.newGuid() as UserId; @@ -75,7 +75,7 @@ describe("AuthRequestLoginStrategy", () => { stateService = mock(); twoFactorService = mock(); userDecryptionOptions = mock(); - deviceTrustCryptoService = mock(); + deviceTrustService = mock(); billingAccountProfileStateService = mock(); accountService = mockAccountServiceWith(mockUserId); @@ -99,7 +99,7 @@ describe("AuthRequestLoginStrategy", () => { stateService, twoFactorService, userDecryptionOptions, - deviceTrustCryptoService, + deviceTrustService, billingAccountProfileStateService, ); @@ -132,7 +132,7 @@ describe("AuthRequestLoginStrategy", () => { ); expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key); expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey); - expect(deviceTrustCryptoService.trustDeviceIfRequired).toHaveBeenCalled(); + expect(deviceTrustService.trustDeviceIfRequired).toHaveBeenCalled(); expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey); }); @@ -160,6 +160,6 @@ describe("AuthRequestLoginStrategy", () => { expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey); // trustDeviceIfRequired should be called - expect(deviceTrustCryptoService.trustDeviceIfRequired).not.toHaveBeenCalled(); + expect(deviceTrustService.trustDeviceIfRequired).not.toHaveBeenCalled(); }); }); 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 4035a7be58..5220e432de 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts @@ -3,7 +3,6 @@ import { Jsonify } from "type-fest"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; @@ -18,6 +17,7 @@ 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 { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; @@ -61,7 +61,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy { stateService: StateService, twoFactorService: TwoFactorService, userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, - private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + private deviceTrustService: DeviceTrustServiceAbstraction, billingAccountProfileStateService: BillingAccountProfileStateService, ) { super( @@ -147,7 +147,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy { await this.trySetUserKeyWithMasterKey(); // Establish trust if required after setting user key - await this.deviceTrustCryptoService.trustDeviceIfRequired(userId); + await this.deviceTrustService.trustDeviceIfRequired(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 b78ad6dea6..df33415247 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 @@ -1,7 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; @@ -50,7 +50,7 @@ describe("SsoLoginStrategy", () => { let twoFactorService: MockProxy; let userDecryptionOptionsService: MockProxy; let keyConnectorService: MockProxy; - let deviceTrustCryptoService: MockProxy; + let deviceTrustService: MockProxy; let authRequestService: MockProxy; let i18nService: MockProxy; let billingAccountProfileStateService: MockProxy; @@ -82,7 +82,7 @@ describe("SsoLoginStrategy", () => { twoFactorService = mock(); userDecryptionOptionsService = mock(); keyConnectorService = mock(); - deviceTrustCryptoService = mock(); + deviceTrustService = mock(); authRequestService = mock(); i18nService = mock(); billingAccountProfileStateService = mock(); @@ -106,7 +106,7 @@ describe("SsoLoginStrategy", () => { twoFactorService, userDecryptionOptionsService, keyConnectorService, - deviceTrustCryptoService, + deviceTrustService, authRequestService, i18nService, billingAccountProfileStateService, @@ -209,8 +209,8 @@ describe("SsoLoginStrategy", () => { ); apiService.postIdentityToken.mockResolvedValue(idTokenResponse); - deviceTrustCryptoService.getDeviceKey.mockResolvedValue(mockDeviceKey); - deviceTrustCryptoService.decryptUserKeyWithDeviceKey.mockResolvedValue(mockUserKey); + deviceTrustService.getDeviceKey.mockResolvedValue(mockDeviceKey); + deviceTrustService.decryptUserKeyWithDeviceKey.mockResolvedValue(mockUserKey); const cryptoSvcSetUserKeySpy = jest.spyOn(cryptoService, "setUserKey"); @@ -218,8 +218,8 @@ describe("SsoLoginStrategy", () => { await ssoLoginStrategy.logIn(credentials); // Assert - expect(deviceTrustCryptoService.getDeviceKey).toHaveBeenCalledTimes(1); - expect(deviceTrustCryptoService.decryptUserKeyWithDeviceKey).toHaveBeenCalledTimes(1); + expect(deviceTrustService.getDeviceKey).toHaveBeenCalledTimes(1); + expect(deviceTrustService.decryptUserKeyWithDeviceKey).toHaveBeenCalledTimes(1); expect(cryptoSvcSetUserKeySpy).toHaveBeenCalledTimes(1); expect(cryptoSvcSetUserKeySpy).toHaveBeenCalledWith(mockUserKey); }); @@ -232,8 +232,8 @@ describe("SsoLoginStrategy", () => { ); apiService.postIdentityToken.mockResolvedValue(idTokenResponse); // Set deviceKey to be null - deviceTrustCryptoService.getDeviceKey.mockResolvedValue(null); - deviceTrustCryptoService.decryptUserKeyWithDeviceKey.mockResolvedValue(mockUserKey); + deviceTrustService.getDeviceKey.mockResolvedValue(null); + deviceTrustService.decryptUserKeyWithDeviceKey.mockResolvedValue(mockUserKey); // Act await ssoLoginStrategy.logIn(credentials); @@ -254,7 +254,7 @@ describe("SsoLoginStrategy", () => { // Arrange const idTokenResponse = mockIdTokenResponseWithModifiedTrustedDeviceOption(valueName, null); apiService.postIdentityToken.mockResolvedValue(idTokenResponse); - deviceTrustCryptoService.getDeviceKey.mockResolvedValue(mockDeviceKey); + deviceTrustService.getDeviceKey.mockResolvedValue(mockDeviceKey); // Act await ssoLoginStrategy.logIn(credentials); @@ -271,9 +271,9 @@ describe("SsoLoginStrategy", () => { userDecryptionOptsServerResponseWithTdeOption, ); apiService.postIdentityToken.mockResolvedValue(idTokenResponse); - deviceTrustCryptoService.getDeviceKey.mockResolvedValue(mockDeviceKey); + deviceTrustService.getDeviceKey.mockResolvedValue(mockDeviceKey); // Set userKey to be null - deviceTrustCryptoService.decryptUserKeyWithDeviceKey.mockResolvedValue(null); + deviceTrustService.decryptUserKeyWithDeviceKey.mockResolvedValue(null); // Act await ssoLoginStrategy.logIn(credentials); @@ -321,7 +321,7 @@ describe("SsoLoginStrategy", () => { await ssoLoginStrategy.logIn(credentials); expect(authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash).toHaveBeenCalled(); - expect(deviceTrustCryptoService.decryptUserKeyWithDeviceKey).not.toHaveBeenCalled(); + expect(deviceTrustService.decryptUserKeyWithDeviceKey).not.toHaveBeenCalled(); }); it("sets the user key from approved admin request if exists", async () => { @@ -338,7 +338,7 @@ describe("SsoLoginStrategy", () => { await ssoLoginStrategy.logIn(credentials); expect(authRequestService.setUserKeyAfterDecryptingSharedUserKey).toHaveBeenCalled(); - expect(deviceTrustCryptoService.decryptUserKeyWithDeviceKey).not.toHaveBeenCalled(); + expect(deviceTrustService.decryptUserKeyWithDeviceKey).not.toHaveBeenCalled(); }); it("attempts to establish a trusted device if successful", async () => { @@ -355,7 +355,7 @@ describe("SsoLoginStrategy", () => { await ssoLoginStrategy.logIn(credentials); expect(authRequestService.setUserKeyAfterDecryptingSharedUserKey).toHaveBeenCalled(); - expect(deviceTrustCryptoService.trustDeviceIfRequired).toHaveBeenCalled(); + expect(deviceTrustService.trustDeviceIfRequired).toHaveBeenCalled(); }); it("clears the admin auth request if server returns a 404, meaning it was deleted", async () => { @@ -369,7 +369,7 @@ describe("SsoLoginStrategy", () => { authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash, ).not.toHaveBeenCalled(); expect(authRequestService.setUserKeyAfterDecryptingSharedUserKey).not.toHaveBeenCalled(); - expect(deviceTrustCryptoService.trustDeviceIfRequired).not.toHaveBeenCalled(); + expect(deviceTrustService.trustDeviceIfRequired).not.toHaveBeenCalled(); }); it("attempts to login with a trusted device if admin auth request isn't successful", async () => { @@ -382,11 +382,11 @@ describe("SsoLoginStrategy", () => { }; apiService.getAuthRequest.mockResolvedValue(adminAuthResponse as AuthRequestResponse); cryptoService.hasUserKey.mockResolvedValue(false); - deviceTrustCryptoService.getDeviceKey.mockResolvedValue("DEVICE_KEY" as any); + deviceTrustService.getDeviceKey.mockResolvedValue("DEVICE_KEY" as any); await ssoLoginStrategy.logIn(credentials); - expect(deviceTrustCryptoService.decryptUserKeyWithDeviceKey).toHaveBeenCalled(); + expect(deviceTrustService.decryptUserKeyWithDeviceKey).toHaveBeenCalled(); }); }); }); 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 d8efd78984..dc63f0fae1 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.ts @@ -3,7 +3,6 @@ import { Jsonify } from "type-fest"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; @@ -22,6 +21,7 @@ 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 { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; import { @@ -94,7 +94,7 @@ export class SsoLoginStrategy extends LoginStrategy { twoFactorService: TwoFactorService, userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, private keyConnectorService: KeyConnectorService, - private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + private deviceTrustService: DeviceTrustServiceAbstraction, private authRequestService: AuthRequestServiceAbstraction, private i18nService: I18nService, billingAccountProfileStateService: BillingAccountProfileStateService, @@ -298,7 +298,7 @@ export class SsoLoginStrategy extends LoginStrategy { if (await this.cryptoService.hasUserKey()) { // Now that we have a decrypted user key in memory, we can check if we // need to establish trust on the current device - await this.deviceTrustCryptoService.trustDeviceIfRequired(userId); + await this.deviceTrustService.trustDeviceIfRequired(userId); // if we successfully decrypted the user key, we can delete the admin auth request out of state // TODO: eventually we post and clean up DB as well once consumed on client @@ -314,7 +314,7 @@ export class SsoLoginStrategy extends LoginStrategy { const userId = (await this.stateService.getUserId()) as UserId; - const deviceKey = await this.deviceTrustCryptoService.getDeviceKey(userId); + const deviceKey = await this.deviceTrustService.getDeviceKey(userId); const encDevicePrivateKey = trustedDeviceOption?.encryptedPrivateKey; const encUserKey = trustedDeviceOption?.encryptedUserKey; @@ -322,7 +322,7 @@ export class SsoLoginStrategy extends LoginStrategy { return; } - const userKey = await this.deviceTrustCryptoService.decryptUserKeyWithDeviceKey( + const userKey = await this.deviceTrustService.decryptUserKeyWithDeviceKey( userId, encDevicePrivateKey, encUserKey, diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts index fcc0220d0a..33708885e2 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts @@ -2,7 +2,7 @@ import { MockProxy, mock } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; @@ -62,7 +62,7 @@ describe("LoginStrategyService", () => { let encryptService: MockProxy; let passwordStrengthService: MockProxy; let policyService: MockProxy; - let deviceTrustCryptoService: MockProxy; + let deviceTrustService: MockProxy; let authRequestService: MockProxy; let userDecryptionOptionsService: MockProxy; let billingAccountProfileStateService: MockProxy; @@ -90,7 +90,7 @@ describe("LoginStrategyService", () => { encryptService = mock(); passwordStrengthService = mock(); policyService = mock(); - deviceTrustCryptoService = mock(); + deviceTrustService = mock(); authRequestService = mock(); userDecryptionOptionsService = mock(); billingAccountProfileStateService = mock(); @@ -114,7 +114,7 @@ describe("LoginStrategyService", () => { encryptService, passwordStrengthService, policyService, - deviceTrustCryptoService, + deviceTrustService, authRequestService, userDecryptionOptionsService, stateProvider, diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index a8bd7bc2ff..aee74e6607 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -10,7 +10,6 @@ import { import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; @@ -36,6 +35,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { KdfType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { MasterKey } from "@bitwarden/common/types/key"; @@ -100,7 +100,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { protected encryptService: EncryptService, protected passwordStrengthService: PasswordStrengthServiceAbstraction, protected policyService: PolicyService, - protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, + protected deviceTrustService: DeviceTrustServiceAbstraction, protected authRequestService: AuthRequestServiceAbstraction, protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, protected stateProvider: GlobalStateProvider, @@ -371,7 +371,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.twoFactorService, this.userDecryptionOptionsService, this.keyConnectorService, - this.deviceTrustCryptoService, + this.deviceTrustService, this.authRequestService, this.i18nService, this.billingAccountProfileStateService, @@ -410,7 +410,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.stateService, this.twoFactorService, this.userDecryptionOptionsService, - this.deviceTrustCryptoService, + this.deviceTrustService, this.billingAccountProfileStateService, ); case AuthenticationType.WebAuthn: diff --git a/libs/common/src/auth/abstractions/device-trust-crypto.service.abstraction.ts b/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts similarity index 89% rename from libs/common/src/auth/abstractions/device-trust-crypto.service.abstraction.ts rename to libs/common/src/auth/abstractions/device-trust.service.abstraction.ts index 53fe214035..123f710338 100644 --- a/libs/common/src/auth/abstractions/device-trust-crypto.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts @@ -3,9 +3,10 @@ import { Observable } from "rxjs"; import { EncString } from "../../platform/models/domain/enc-string"; import { UserId } from "../../types/guid"; import { DeviceKey, UserKey } from "../../types/key"; -import { DeviceResponse } from "../abstractions/devices/responses/device.response"; -export abstract class DeviceTrustCryptoServiceAbstraction { +import { DeviceResponse } from "./devices/responses/device.response"; + +export abstract class DeviceTrustServiceAbstraction { supportsDeviceTrust$: Observable; /** * @description Retrieves the users choice to trust the device which can only happen after decryption diff --git a/libs/common/src/auth/services/device-trust-crypto.service.implementation.ts b/libs/common/src/auth/services/device-trust.service.implementation.ts similarity index 98% rename from libs/common/src/auth/services/device-trust-crypto.service.implementation.ts rename to libs/common/src/auth/services/device-trust.service.implementation.ts index 6fb58eab28..ccf87acaf8 100644 --- a/libs/common/src/auth/services/device-trust-crypto.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust.service.implementation.ts @@ -17,7 +17,7 @@ import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypt import { DEVICE_TRUST_DISK_LOCAL, StateProvider, UserKeyDefinition } from "../../platform/state"; import { UserId } from "../../types/guid"; import { UserKey, DeviceKey } from "../../types/key"; -import { DeviceTrustCryptoServiceAbstraction } from "../abstractions/device-trust-crypto.service.abstraction"; +import { DeviceTrustServiceAbstraction } from "../abstractions/device-trust.service.abstraction"; import { DeviceResponse } from "../abstractions/devices/responses/device.response"; import { DevicesApiServiceAbstraction } from "../abstractions/devices-api.service.abstraction"; import { SecretVerificationRequest } from "../models/request/secret-verification.request"; @@ -42,7 +42,7 @@ export const SHOULD_TRUST_DEVICE = new UserKeyDefinition( }, ); -export class DeviceTrustCryptoService implements DeviceTrustCryptoServiceAbstraction { +export class DeviceTrustService implements DeviceTrustServiceAbstraction { private readonly platformSupportsSecureStorage = this.platformUtilsService.supportsSecureStorage(); private readonly deviceKeySecureStorageKey: string = "_deviceKey"; diff --git a/libs/common/src/auth/services/device-trust-crypto.service.spec.ts b/libs/common/src/auth/services/device-trust.service.spec.ts similarity index 86% rename from libs/common/src/auth/services/device-trust-crypto.service.spec.ts rename to libs/common/src/auth/services/device-trust.service.spec.ts index af147b3481..12b8cf2eaa 100644 --- a/libs/common/src/auth/services/device-trust-crypto.service.spec.ts +++ b/libs/common/src/auth/services/device-trust.service.spec.ts @@ -33,11 +33,11 @@ import { ProtectedDeviceResponse } from "../models/response/protected-device.res import { SHOULD_TRUST_DEVICE, DEVICE_KEY, - DeviceTrustCryptoService, -} from "./device-trust-crypto.service.implementation"; + DeviceTrustService, +} from "./device-trust.service.implementation"; -describe("deviceTrustCryptoService", () => { - let deviceTrustCryptoService: DeviceTrustCryptoService; +describe("deviceTrustService", () => { + let deviceTrustService: DeviceTrustService; const keyGenerationService = mock(); const cryptoFunctionService = mock(); @@ -70,11 +70,11 @@ describe("deviceTrustCryptoService", () => { jest.clearAllMocks(); const supportsSecureStorage = false; // default to false; tests will override as needed // By default all the tests will have a mocked active user in state provider. - deviceTrustCryptoService = createDeviceTrustCryptoService(mockUserId, supportsSecureStorage); + deviceTrustService = createDeviceTrustService(mockUserId, supportsSecureStorage); }); it("instantiates", () => { - expect(deviceTrustCryptoService).not.toBeFalsy(); + expect(deviceTrustService).not.toBeFalsy(); }); describe("User Trust Device Choice For Decryption", () => { @@ -84,7 +84,7 @@ describe("deviceTrustCryptoService", () => { await stateProvider.setUserState(SHOULD_TRUST_DEVICE, newValue, mockUserId); - const result = await deviceTrustCryptoService.getShouldTrustDevice(mockUserId); + const result = await deviceTrustService.getShouldTrustDevice(mockUserId); expect(result).toEqual(newValue); }); @@ -95,9 +95,9 @@ describe("deviceTrustCryptoService", () => { await stateProvider.setUserState(SHOULD_TRUST_DEVICE, false, mockUserId); const newValue = true; - await deviceTrustCryptoService.setShouldTrustDevice(mockUserId, newValue); + await deviceTrustService.setShouldTrustDevice(mockUserId, newValue); - const result = await deviceTrustCryptoService.getShouldTrustDevice(mockUserId); + const result = await deviceTrustService.getShouldTrustDevice(mockUserId); expect(result).toEqual(newValue); }); }); @@ -105,25 +105,25 @@ describe("deviceTrustCryptoService", () => { describe("trustDeviceIfRequired", () => { it("should trust device and reset when getShouldTrustDevice returns true", async () => { - jest.spyOn(deviceTrustCryptoService, "getShouldTrustDevice").mockResolvedValue(true); - jest.spyOn(deviceTrustCryptoService, "trustDevice").mockResolvedValue({} as DeviceResponse); - jest.spyOn(deviceTrustCryptoService, "setShouldTrustDevice").mockResolvedValue(); + jest.spyOn(deviceTrustService, "getShouldTrustDevice").mockResolvedValue(true); + jest.spyOn(deviceTrustService, "trustDevice").mockResolvedValue({} as DeviceResponse); + jest.spyOn(deviceTrustService, "setShouldTrustDevice").mockResolvedValue(); - await deviceTrustCryptoService.trustDeviceIfRequired(mockUserId); + await deviceTrustService.trustDeviceIfRequired(mockUserId); - expect(deviceTrustCryptoService.getShouldTrustDevice).toHaveBeenCalledTimes(1); - expect(deviceTrustCryptoService.trustDevice).toHaveBeenCalledTimes(1); - expect(deviceTrustCryptoService.setShouldTrustDevice).toHaveBeenCalledWith(mockUserId, false); + expect(deviceTrustService.getShouldTrustDevice).toHaveBeenCalledTimes(1); + expect(deviceTrustService.trustDevice).toHaveBeenCalledTimes(1); + expect(deviceTrustService.setShouldTrustDevice).toHaveBeenCalledWith(mockUserId, false); }); it("should not trust device nor reset when getShouldTrustDevice returns false", async () => { const getShouldTrustDeviceSpy = jest - .spyOn(deviceTrustCryptoService, "getShouldTrustDevice") + .spyOn(deviceTrustService, "getShouldTrustDevice") .mockResolvedValue(false); - const trustDeviceSpy = jest.spyOn(deviceTrustCryptoService, "trustDevice"); - const setShouldTrustDeviceSpy = jest.spyOn(deviceTrustCryptoService, "setShouldTrustDevice"); + const trustDeviceSpy = jest.spyOn(deviceTrustService, "trustDevice"); + const setShouldTrustDeviceSpy = jest.spyOn(deviceTrustService, "setShouldTrustDevice"); - await deviceTrustCryptoService.trustDeviceIfRequired(mockUserId); + await deviceTrustService.trustDeviceIfRequired(mockUserId); expect(getShouldTrustDeviceSpy).toHaveBeenCalledTimes(1); expect(trustDeviceSpy).not.toHaveBeenCalled(); @@ -151,7 +151,7 @@ describe("deviceTrustCryptoService", () => { it("returns null when there is not an existing device key", async () => { await stateProvider.setUserState(DEVICE_KEY, null, mockUserId); - const deviceKey = await deviceTrustCryptoService.getDeviceKey(mockUserId); + const deviceKey = await deviceTrustService.getDeviceKey(mockUserId); expect(deviceKey).toBeNull(); expect(secureStorageService.get).not.toHaveBeenCalled(); @@ -160,7 +160,7 @@ describe("deviceTrustCryptoService", () => { it("returns the device key when there is an existing device key", async () => { await stateProvider.setUserState(DEVICE_KEY, existingDeviceKey, mockUserId); - const deviceKey = await deviceTrustCryptoService.getDeviceKey(mockUserId); + const deviceKey = await deviceTrustService.getDeviceKey(mockUserId); expect(deviceKey).not.toBeNull(); expect(deviceKey).toBeInstanceOf(SymmetricCryptoKey); @@ -172,17 +172,14 @@ describe("deviceTrustCryptoService", () => { describe("Secure Storage supported", () => { beforeEach(() => { const supportsSecureStorage = true; - deviceTrustCryptoService = createDeviceTrustCryptoService( - mockUserId, - supportsSecureStorage, - ); + deviceTrustService = createDeviceTrustService(mockUserId, supportsSecureStorage); }); it("returns null when there is not an existing device key for the passed in user id", async () => { secureStorageService.get.mockResolvedValue(null); // Act - const deviceKey = await deviceTrustCryptoService.getDeviceKey(mockUserId); + const deviceKey = await deviceTrustService.getDeviceKey(mockUserId); // Assert expect(deviceKey).toBeNull(); @@ -193,7 +190,7 @@ describe("deviceTrustCryptoService", () => { secureStorageService.get.mockResolvedValue(existingDeviceKeyB64); // Act - const deviceKey = await deviceTrustCryptoService.getDeviceKey(mockUserId); + const deviceKey = await deviceTrustService.getDeviceKey(mockUserId); // Assert expect(deviceKey).not.toBeNull(); @@ -203,7 +200,7 @@ describe("deviceTrustCryptoService", () => { }); it("throws an error when no user id is passed in", async () => { - await expect(deviceTrustCryptoService.getDeviceKey(null)).rejects.toThrow( + await expect(deviceTrustService.getDeviceKey(null)).rejects.toThrow( "UserId is required. Cannot get device key.", ); }); @@ -220,7 +217,7 @@ describe("deviceTrustCryptoService", () => { // TypeScript will allow calling private methods if the object is of type 'any' // This is a hacky workaround, but it allows for cleaner tests - await (deviceTrustCryptoService as any).setDeviceKey(mockUserId, newDeviceKey); + await (deviceTrustService as any).setDeviceKey(mockUserId, newDeviceKey); expect(stateProvider.mock.setUserState).toHaveBeenLastCalledWith( DEVICE_KEY, @@ -232,10 +229,7 @@ describe("deviceTrustCryptoService", () => { describe("Secure Storage supported", () => { beforeEach(() => { const supportsSecureStorage = true; - deviceTrustCryptoService = createDeviceTrustCryptoService( - mockUserId, - supportsSecureStorage, - ); + deviceTrustService = createDeviceTrustService(mockUserId, supportsSecureStorage); }); it("successfully sets the device key in secure storage", async () => { @@ -251,7 +245,7 @@ describe("deviceTrustCryptoService", () => { // Act // TypeScript will allow calling private methods if the object is of type 'any' // This is a hacky workaround, but it allows for cleaner tests - await (deviceTrustCryptoService as any).setDeviceKey(mockUserId, newDeviceKey); + await (deviceTrustService as any).setDeviceKey(mockUserId, newDeviceKey); // Assert expect(stateProvider.mock.setUserState).not.toHaveBeenCalledTimes(2); @@ -268,9 +262,9 @@ describe("deviceTrustCryptoService", () => { new Uint8Array(deviceKeyBytesLength) as CsprngArray, ) as DeviceKey; - await expect( - (deviceTrustCryptoService as any).setDeviceKey(null, newDeviceKey), - ).rejects.toThrow("UserId is required. Cannot set device key."); + await expect((deviceTrustService as any).setDeviceKey(null, newDeviceKey)).rejects.toThrow( + "UserId is required. Cannot set device key.", + ); }); }); @@ -285,7 +279,7 @@ describe("deviceTrustCryptoService", () => { // TypeScript will allow calling private methods if the object is of type 'any' // This is a hacky workaround, but it allows for cleaner tests - const deviceKey = await (deviceTrustCryptoService as any).makeDeviceKey(); + const deviceKey = await (deviceTrustService as any).makeDeviceKey(); expect(keyGenSvcGenerateKeySpy).toHaveBeenCalledTimes(1); expect(keyGenSvcGenerateKeySpy).toHaveBeenCalledWith(deviceKeyBytesLength * 8); @@ -362,7 +356,7 @@ describe("deviceTrustCryptoService", () => { // TypeScript will allow calling private methods if the object is of type 'any' makeDeviceKeySpy = jest - .spyOn(deviceTrustCryptoService as any, "makeDeviceKey") + .spyOn(deviceTrustService as any, "makeDeviceKey") .mockResolvedValue(mockDeviceKey); rsaGenerateKeyPairSpy = jest @@ -398,7 +392,7 @@ describe("deviceTrustCryptoService", () => { }); it("calls the required methods with the correct arguments and returns a DeviceResponse", async () => { - const response = await deviceTrustCryptoService.trustDevice(mockUserId); + const response = await deviceTrustService.trustDevice(mockUserId); expect(makeDeviceKeySpy).toHaveBeenCalledTimes(1); expect(rsaGenerateKeyPairSpy).toHaveBeenCalledTimes(1); @@ -429,7 +423,7 @@ describe("deviceTrustCryptoService", () => { // setup the spy to return null cryptoSvcGetUserKeySpy.mockResolvedValue(null); // check if the expected error is thrown - await expect(deviceTrustCryptoService.trustDevice(mockUserId)).rejects.toThrow( + await expect(deviceTrustService.trustDevice(mockUserId)).rejects.toThrow( "User symmetric key not found", ); @@ -439,7 +433,7 @@ describe("deviceTrustCryptoService", () => { // setup the spy to return undefined cryptoSvcGetUserKeySpy.mockResolvedValue(undefined); // check if the expected error is thrown - await expect(deviceTrustCryptoService.trustDevice(mockUserId)).rejects.toThrow( + await expect(deviceTrustService.trustDevice(mockUserId)).rejects.toThrow( "User symmetric key not found", ); }); @@ -479,9 +473,7 @@ describe("deviceTrustCryptoService", () => { it(`throws an error if ${method} fails`, async () => { const methodSpy = spy(); methodSpy.mockRejectedValue(new Error(errorText)); - await expect(deviceTrustCryptoService.trustDevice(mockUserId)).rejects.toThrow( - errorText, - ); + await expect(deviceTrustService.trustDevice(mockUserId)).rejects.toThrow(errorText); }); test.each([null, undefined])( @@ -489,14 +481,14 @@ describe("deviceTrustCryptoService", () => { async (invalidValue) => { const methodSpy = spy(); methodSpy.mockResolvedValue(invalidValue); - await expect(deviceTrustCryptoService.trustDevice(mockUserId)).rejects.toThrow(); + await expect(deviceTrustService.trustDevice(mockUserId)).rejects.toThrow(); }, ); }, ); it("throws an error when a null user id is passed in", async () => { - await expect(deviceTrustCryptoService.trustDevice(null)).rejects.toThrow( + await expect(deviceTrustService.trustDevice(null)).rejects.toThrow( "UserId is required. Cannot trust device.", ); }); @@ -530,7 +522,7 @@ describe("deviceTrustCryptoService", () => { it("throws an error when a null user id is passed in", async () => { await expect( - deviceTrustCryptoService.decryptUserKeyWithDeviceKey( + deviceTrustService.decryptUserKeyWithDeviceKey( null, mockEncryptedDevicePrivateKey, mockEncryptedUserKey, @@ -540,7 +532,7 @@ describe("deviceTrustCryptoService", () => { }); it("returns null when device key isn't provided", async () => { - const result = await deviceTrustCryptoService.decryptUserKeyWithDeviceKey( + const result = await deviceTrustService.decryptUserKeyWithDeviceKey( mockUserId, mockEncryptedDevicePrivateKey, mockEncryptedUserKey, @@ -558,7 +550,7 @@ describe("deviceTrustCryptoService", () => { .spyOn(cryptoService, "rsaDecrypt") .mockResolvedValue(new Uint8Array(userKeyBytesLength)); - const result = await deviceTrustCryptoService.decryptUserKeyWithDeviceKey( + const result = await deviceTrustService.decryptUserKeyWithDeviceKey( mockUserId, mockEncryptedDevicePrivateKey, mockEncryptedUserKey, @@ -574,9 +566,9 @@ describe("deviceTrustCryptoService", () => { const decryptToBytesSpy = jest .spyOn(encryptService, "decryptToBytes") .mockRejectedValue(new Error("Decryption error")); - const setDeviceKeySpy = jest.spyOn(deviceTrustCryptoService as any, "setDeviceKey"); + const setDeviceKeySpy = jest.spyOn(deviceTrustService as any, "setDeviceKey"); - const result = await deviceTrustCryptoService.decryptUserKeyWithDeviceKey( + const result = await deviceTrustService.decryptUserKeyWithDeviceKey( mockUserId, mockEncryptedDevicePrivateKey, mockEncryptedUserKey, @@ -606,7 +598,7 @@ describe("deviceTrustCryptoService", () => { it("throws an error when a null user id is passed in", async () => { await expect( - deviceTrustCryptoService.rotateDevicesTrust(null, fakeNewUserKey, ""), + deviceTrustService.rotateDevicesTrust(null, fakeNewUserKey, ""), ).rejects.toThrow("UserId is required. Cannot rotate device's trust."); }); @@ -615,7 +607,7 @@ describe("deviceTrustCryptoService", () => { stateProvider.activeUser.getFake(DEVICE_KEY); deviceKeyState.nextState(null); - await deviceTrustCryptoService.rotateDevicesTrust(mockUserId, fakeNewUserKey, ""); + await deviceTrustService.rotateDevicesTrust(mockUserId, fakeNewUserKey, ""); expect(devicesApiService.updateTrust).not.toHaveBeenCalled(); }); @@ -691,7 +683,7 @@ describe("deviceTrustCryptoService", () => { ); }); - await deviceTrustCryptoService.rotateDevicesTrust( + await deviceTrustService.rotateDevicesTrust( mockUserId, fakeNewUserKey, "my_password_hash", @@ -713,10 +705,7 @@ describe("deviceTrustCryptoService", () => { }); // Helpers - function createDeviceTrustCryptoService( - mockUserId: UserId | null, - supportsSecureStorage: boolean, - ) { + function createDeviceTrustService(mockUserId: UserId | null, supportsSecureStorage: boolean) { accountService = mockAccountServiceWith(mockUserId); stateProvider = new FakeStateProvider(accountService); @@ -725,7 +714,7 @@ describe("deviceTrustCryptoService", () => { decryptionOptions.next({} as any); userDecryptionOptionsService.userDecryptionOptions$ = decryptionOptions; - return new DeviceTrustCryptoService( + return new DeviceTrustService( keyGenerationService, cryptoFunctionService, cryptoService, diff --git a/libs/common/src/state-migrations/migrate.ts b/libs/common/src/state-migrations/migrate.ts index f9a8734731..2d8ef1619e 100644 --- a/libs/common/src/state-migrations/migrate.ts +++ b/libs/common/src/state-migrations/migrate.ts @@ -49,7 +49,7 @@ import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org- import { KeyConnectorMigrator } from "./migrations/50-move-key-connector-to-state-provider"; import { RememberedEmailMigrator } from "./migrations/51-move-remembered-email-to-state-providers"; import { DeleteInstalledVersion } from "./migrations/52-delete-installed-version"; -import { DeviceTrustCryptoServiceStateProviderMigrator } from "./migrations/53-migrate-device-trust-crypto-svc-to-state-providers"; +import { DeviceTrustServiceStateProviderMigrator } from "./migrations/53-migrate-device-trust-svc-to-state-providers"; import { SendMigrator } from "./migrations/54-move-encrypted-sends"; import { MoveMasterKeyStateToProviderMigrator } from "./migrations/55-move-master-key-state-to-provider"; import { AuthRequestMigrator } from "./migrations/56-move-auth-requests"; @@ -117,7 +117,7 @@ export function createMigrationBuilder() { .with(KeyConnectorMigrator, 49, 50) .with(RememberedEmailMigrator, 50, 51) .with(DeleteInstalledVersion, 51, 52) - .with(DeviceTrustCryptoServiceStateProviderMigrator, 52, 53) + .with(DeviceTrustServiceStateProviderMigrator, 52, 53) .with(SendMigrator, 53, 54) .with(MoveMasterKeyStateToProviderMigrator, 54, 55) .with(AuthRequestMigrator, 55, 56) diff --git a/libs/common/src/state-migrations/migrations/53-migrate-device-trust-crypto-svc-to-state-providers.spec.ts b/libs/common/src/state-migrations/migrations/53-migrate-device-trust-svc-to-state-providers.spec.ts similarity index 92% rename from libs/common/src/state-migrations/migrations/53-migrate-device-trust-crypto-svc-to-state-providers.spec.ts rename to libs/common/src/state-migrations/migrations/53-migrate-device-trust-svc-to-state-providers.spec.ts index 79366a4716..343fbd03d9 100644 --- a/libs/common/src/state-migrations/migrations/53-migrate-device-trust-crypto-svc-to-state-providers.spec.ts +++ b/libs/common/src/state-migrations/migrations/53-migrate-device-trust-svc-to-state-providers.spec.ts @@ -5,9 +5,9 @@ import { mockMigrationHelper } from "../migration-helper.spec"; import { DEVICE_KEY, - DeviceTrustCryptoServiceStateProviderMigrator, + DeviceTrustServiceStateProviderMigrator, SHOULD_TRUST_DEVICE, -} from "./53-migrate-device-trust-crypto-svc-to-state-providers"; +} from "./53-migrate-device-trust-svc-to-state-providers"; // Represents data in state service pre-migration function preMigrationJson() { @@ -79,14 +79,14 @@ function rollbackJSON() { }; } -describe("DeviceTrustCryptoServiceStateProviderMigrator", () => { +describe("DeviceTrustServiceStateProviderMigrator", () => { let helper: MockProxy; - let sut: DeviceTrustCryptoServiceStateProviderMigrator; + let sut: DeviceTrustServiceStateProviderMigrator; describe("migrate", () => { beforeEach(() => { helper = mockMigrationHelper(preMigrationJson(), 52); - sut = new DeviceTrustCryptoServiceStateProviderMigrator(52, 53); + sut = new DeviceTrustServiceStateProviderMigrator(52, 53); }); // it should remove deviceKey and trustDeviceChoiceForDecryption from all accounts @@ -126,7 +126,7 @@ describe("DeviceTrustCryptoServiceStateProviderMigrator", () => { describe("rollback", () => { beforeEach(() => { helper = mockMigrationHelper(rollbackJSON(), 53); - sut = new DeviceTrustCryptoServiceStateProviderMigrator(52, 53); + sut = new DeviceTrustServiceStateProviderMigrator(52, 53); }); it("should null out newly migrated entries in state provider framework", async () => { diff --git a/libs/common/src/state-migrations/migrations/53-migrate-device-trust-crypto-svc-to-state-providers.ts b/libs/common/src/state-migrations/migrations/53-migrate-device-trust-svc-to-state-providers.ts similarity index 94% rename from libs/common/src/state-migrations/migrations/53-migrate-device-trust-crypto-svc-to-state-providers.ts rename to libs/common/src/state-migrations/migrations/53-migrate-device-trust-svc-to-state-providers.ts index e19c7b3fa5..b6d2c19b15 100644 --- a/libs/common/src/state-migrations/migrations/53-migrate-device-trust-crypto-svc-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/53-migrate-device-trust-svc-to-state-providers.ts @@ -16,7 +16,7 @@ type ExpectedAccountType = { }; export const DEVICE_KEY: KeyDefinitionLike = { - key: "deviceKey", // matches KeyDefinition.key in DeviceTrustCryptoService + key: "deviceKey", // matches KeyDefinition.key in DeviceTrustService stateDefinition: { name: "deviceTrust", // matches StateDefinition.name in StateDefinitions }, @@ -29,7 +29,7 @@ export const SHOULD_TRUST_DEVICE: KeyDefinitionLike = { }, }; -export class DeviceTrustCryptoServiceStateProviderMigrator extends Migrator<52, 53> { +export class DeviceTrustServiceStateProviderMigrator extends Migrator<52, 53> { async migrate(helper: MigrationHelper): Promise { const accounts = await helper.getAccounts(); async function migrateAccount(userId: string, account: ExpectedAccountType): Promise { diff --git a/libs/common/src/state-migrations/migrations/58-remove-refresh-token-migrated-state-provider-flag.ts b/libs/common/src/state-migrations/migrations/58-remove-refresh-token-migrated-state-provider-flag.ts index 9c6d3776fe..1fb3609267 100644 --- a/libs/common/src/state-migrations/migrations/58-remove-refresh-token-migrated-state-provider-flag.ts +++ b/libs/common/src/state-migrations/migrations/58-remove-refresh-token-migrated-state-provider-flag.ts @@ -4,7 +4,7 @@ import { IRREVERSIBLE, Migrator } from "../migrator"; type ExpectedAccountType = NonNullable; export const REFRESH_TOKEN_MIGRATED_TO_SECURE_STORAGE: KeyDefinitionLike = { - key: "refreshTokenMigratedToSecureStorage", // matches KeyDefinition.key in DeviceTrustCryptoService + key: "refreshTokenMigratedToSecureStorage", // matches KeyDefinition.key stateDefinition: { name: "token", // matches StateDefinition.name in StateDefinitions },