From e972e905c8c7270732c02cd1f6f9a194bc8f9d47 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Thu, 27 Oct 2022 16:06:17 -0400 Subject: [PATCH 01/83] Remove DDG forwarder from SH (#3888) --- apps/web/src/app/tools/generator.component.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/web/src/app/tools/generator.component.ts b/apps/web/src/app/tools/generator.component.ts index 48aaeb4d5f4..6a5f00c19b0 100644 --- a/apps/web/src/app/tools/generator.component.ts +++ b/apps/web/src/app/tools/generator.component.ts @@ -46,6 +46,11 @@ export class GeneratorComponent extends BaseGeneratorComponent { this.forwardOptions.findIndex((o) => o.value === "firefoxrelay"), 1 ); + // Also cannot use Duck Duck Go on self hosted web vaults due to CORS issues + this.forwardOptions.splice( + this.forwardOptions.findIndex((o) => o.value === "duckduckgo"), + 1 + ); } } From da47992a22edce056e2670c2a10f680556901129 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Fri, 28 Oct 2022 07:38:54 +1000 Subject: [PATCH 02/83] [EC-272] Web workers using EncryptionService (#3532) * Add item decryption to encryptService * Create multithreadEncryptService subclass to handle web workers * Create encryption web worker * Refactor cipherService to use new interface * Update dependencies --- .github/whitelist-capital-letters.txt | 1 - .../browser/src/background/main.background.ts | 18 +++- .../cipher-service.factory.ts | 7 +- .../encrypt-service.factory.ts | 31 ++++--- ...ec.ts => clear-clipboard.spec.ts.disabled} | 0 apps/browser/src/listeners/update-badge.ts | 4 +- .../localBackedSessionStorage.service.spec.ts | 4 +- .../localBackedSessionStorage.service.ts | 4 +- apps/browser/tsconfig.json | 4 +- apps/cli/src/bw.ts | 13 ++- apps/desktop/config/base.json | 4 +- .../src/nativeMessageService.ts | 6 +- apps/desktop/src/app/services/init.service.ts | 4 +- .../src/app/services/services.module.ts | 4 +- apps/desktop/tsconfig.json | 4 +- apps/web/src/app/app.module.ts | 2 +- apps/web/src/app/core/index.ts | 5 +- apps/web/src/app/core/init.service.ts | 4 +- .../tests/preloaded-english-i18n.module.ts | 2 +- apps/web/tsconfig.json | 7 +- .../bit-web/src/app/app.module.ts | 2 +- .../src/services/jslib-services.module.ts | 29 +++++-- .../spec/models/domain/attachment.spec.ts | 6 +- libs/common/spec/models/domain/cipher.spec.ts | 6 ++ .../spec/models/domain/encString.spec.ts | 8 +- libs/common/spec/models/domain/send.spec.ts | 4 +- .../spec/services/cipher.service.spec.ts | 6 +- .../spec/services/crypto.service.spec.ts | 4 +- .../spec/services/encrypt.service.spec.ts | 6 +- .../spec/services/folder.service.spec.ts | 4 +- .../spec/services/policy.service.spec.ts | 2 +- .../spec/services/settings.service.spec.ts | 4 +- ...tEncrypt.service.ts => encrypt.service.ts} | 8 +- .../src/interfaces/decryptable.interface.ts | 12 +++ .../initializer-metadata.interface.ts | 11 +++ libs/common/src/misc/flags.ts | 4 +- libs/common/src/misc/utils.ts | 4 +- libs/common/src/models/domain/cipher.ts | 6 +- libs/common/src/models/view/cipher.view.ts | 6 +- libs/common/src/services/cipher.service.ts | 69 +++++++++------- libs/common/src/services/container.service.ts | 9 +- libs/common/src/services/crypto.service.ts | 4 +- .../encrypt.service.implementation.ts} | 41 ++++++---- .../services/cryptography/encrypt.worker.ts | 56 +++++++++++++ .../cryptography/get-class-initializer.ts | 22 +++++ .../services/cryptography/initializer-key.ts | 4 + ...tithread-encrypt.service.implementation.ts | 82 +++++++++++++++++++ .../src/services/electronCrypto.service.ts | 4 +- libs/shared/tsconfig.json | 2 +- tsconfig.json | 2 +- 50 files changed, 419 insertions(+), 136 deletions(-) rename apps/browser/src/clipboard/{clear-clipboard.spec.ts => clear-clipboard.spec.ts.disabled} (100%) rename libs/common/src/abstractions/{abstractEncrypt.service.ts => encrypt.service.ts} (70%) create mode 100644 libs/common/src/interfaces/decryptable.interface.ts create mode 100644 libs/common/src/interfaces/initializer-metadata.interface.ts rename libs/common/src/services/{encrypt.service.ts => cryptography/encrypt.service.implementation.ts} (80%) create mode 100644 libs/common/src/services/cryptography/encrypt.worker.ts create mode 100644 libs/common/src/services/cryptography/get-class-initializer.ts create mode 100644 libs/common/src/services/cryptography/initializer-key.ts create mode 100644 libs/common/src/services/cryptography/multithread-encrypt.service.implementation.ts diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index 772c7209e20..7f907a05948 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -235,7 +235,6 @@ ./libs/common/src/abstractions/fileDownload/fileDownloadBuilder.ts ./libs/common/src/abstractions/fileDownload/fileDownload.service.ts ./libs/common/src/abstractions/fileDownload/fileDownloadRequest.ts -./libs/common/src/abstractions/abstractEncrypt.service.ts ./libs/common/src/abstractions/passwordGeneration.service.ts ./libs/common/src/abstractions/passwordReprompt.service.ts ./libs/common/src/abstractions/formValidationErrors.service.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index c90d58ec138..cc973a8c826 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -6,6 +6,7 @@ import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/abs import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/common/abstractions/collection.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EventService as EventServiceAbstraction } from "@bitwarden/common/abstractions/event.service"; import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service"; import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/fileUpload.service"; @@ -51,7 +52,8 @@ import { CipherService } from "@bitwarden/common/services/cipher.service"; import { CollectionService } from "@bitwarden/common/services/collection.service"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { ContainerService } from "@bitwarden/common/services/container.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; +import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation"; import { EventService } from "@bitwarden/common/services/event.service"; import { ExportService } from "@bitwarden/common/services/export.service"; import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; @@ -82,6 +84,7 @@ import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFu import { BrowserApi } from "../browser/browserApi"; import { SafariApp } from "../browser/safariApp"; +import { flagEnabled } from "../flags"; import { UpdateBadge } from "../listeners/update-badge"; import { Account } from "../models/account"; import { PopupUtilsService } from "../popup/services/popup-utils.service"; @@ -215,7 +218,7 @@ export default class MainBackground { this.memoryStorageService = BrowserApi.manifestVersion === 3 ? new LocalBackedSessionStorageService( - new EncryptService(this.cryptoFunctionService, this.logService, false), + new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false), new KeyGenerationService(this.cryptoFunctionService) ) : new MemoryStorageService(); @@ -255,7 +258,13 @@ export default class MainBackground { window ); this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); - this.encryptService = new EncryptService(this.cryptoFunctionService, this.logService, true); + this.encryptService = flagEnabled("multithreadDecryption") + ? new MultithreadEncryptServiceImplementation( + this.cryptoFunctionService, + this.logService, + true + ) + : new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, true); this.cryptoService = new BrowserCryptoService( this.cryptoFunctionService, this.encryptService, @@ -283,7 +292,8 @@ export default class MainBackground { this.i18nService, () => this.searchService, this.logService, - this.stateService + this.stateService, + this.encryptService ); this.folderService = new FolderService( this.cryptoService, diff --git a/apps/browser/src/background/service_factories/cipher-service.factory.ts b/apps/browser/src/background/service_factories/cipher-service.factory.ts index 03141f2c84f..6d31ebd76f7 100644 --- a/apps/browser/src/background/service_factories/cipher-service.factory.ts +++ b/apps/browser/src/background/service_factories/cipher-service.factory.ts @@ -4,6 +4,7 @@ import { CipherService } from "@bitwarden/common/services/cipher.service"; import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory"; import { cryptoServiceFactory, CryptoServiceInitOptions } from "./crypto-service.factory"; +import { encryptServiceFactory, EncryptServiceInitOptions } from "./encrypt-service.factory"; import { CachedServices, factory, FactoryOptions } from "./factory-options"; import { FileUploadServiceInitOptions, @@ -27,7 +28,8 @@ export type CipherServiceInitOptions = CipherServiceFactoryOptions & FileUploadServiceInitOptions & I18nServiceInitOptions & LogServiceInitOptions & - StateServiceInitOptions; + StateServiceInitOptions & + EncryptServiceInitOptions; export function cipherServiceFactory( cache: { cipherService?: AbstractCipherService } & CachedServices, @@ -48,7 +50,8 @@ export function cipherServiceFactory( ? () => cache.searchService : opts.cipherServiceOptions.searchServiceFactory, await logServiceFactory(cache, opts), - await stateServiceFactory(cache, opts) + await stateServiceFactory(cache, opts), + await encryptServiceFactory(cache, opts) ) ); } diff --git a/apps/browser/src/background/service_factories/encrypt-service.factory.ts b/apps/browser/src/background/service_factories/encrypt-service.factory.ts index fffc9f9d296..6e4c104b05b 100644 --- a/apps/browser/src/background/service_factories/encrypt-service.factory.ts +++ b/apps/browser/src/background/service_factories/encrypt-service.factory.ts @@ -1,4 +1,7 @@ -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; +import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation"; + +import { flagEnabled } from "../../flags"; import { cryptoFunctionServiceFactory, @@ -18,18 +21,20 @@ export type EncryptServiceInitOptions = EncryptServiceFactoryOptions & LogServiceInitOptions; export function encryptServiceFactory( - cache: { encryptService?: EncryptService } & CachedServices, + cache: { encryptService?: EncryptServiceImplementation } & CachedServices, opts: EncryptServiceInitOptions -): Promise { - return factory( - cache, - "encryptService", - opts, - async () => - new EncryptService( - await cryptoFunctionServiceFactory(cache, opts), - await logServiceFactory(cache, opts), - opts.encryptServiceOptions.logMacFailures - ) +): Promise { + return factory(cache, "encryptService", opts, async () => + flagEnabled("multithreadDecryption") + ? new MultithreadEncryptServiceImplementation( + await cryptoFunctionServiceFactory(cache, opts), + await logServiceFactory(cache, opts), + opts.encryptServiceOptions.logMacFailures + ) + : new EncryptServiceImplementation( + await cryptoFunctionServiceFactory(cache, opts), + await logServiceFactory(cache, opts), + opts.encryptServiceOptions.logMacFailures + ) ); } diff --git a/apps/browser/src/clipboard/clear-clipboard.spec.ts b/apps/browser/src/clipboard/clear-clipboard.spec.ts.disabled similarity index 100% rename from apps/browser/src/clipboard/clear-clipboard.spec.ts rename to apps/browser/src/clipboard/clear-clipboard.spec.ts.disabled diff --git a/apps/browser/src/listeners/update-badge.ts b/apps/browser/src/listeners/update-badge.ts index 888f4ce7917..9c7c122a45a 100644 --- a/apps/browser/src/listeners/update-badge.ts +++ b/apps/browser/src/listeners/update-badge.ts @@ -1,7 +1,7 @@ -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { Utils } from "@bitwarden/common/misc/utils"; @@ -259,7 +259,7 @@ export class UpdateBadge { if (!self.bitwardenContainerService) { new ContainerService( serviceCache.cryptoService as CryptoService, - serviceCache.encryptService as AbstractEncryptService + serviceCache.encryptService as EncryptService ).attachToGlobal(self); } diff --git a/apps/browser/src/services/localBackedSessionStorage.service.spec.ts b/apps/browser/src/services/localBackedSessionStorage.service.spec.ts index 85937336081..b5f80d4fee1 100644 --- a/apps/browser/src/services/localBackedSessionStorage.service.spec.ts +++ b/apps/browser/src/services/localBackedSessionStorage.service.spec.ts @@ -4,7 +4,7 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Utils } from "@bitwarden/common/misc/utils"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; -import { EncryptService } from "@bitwarden/common/src/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import BrowserLocalStorageService from "./browserLocalStorage.service"; import BrowserMemoryStorageService from "./browserMemoryStorage.service"; @@ -12,7 +12,7 @@ import { KeyGenerationService } from "./keyGeneration.service"; import { LocalBackedSessionStorageService } from "./localBackedSessionStorage.service"; describe("Browser Session Storage Service", () => { - let encryptService: SubstituteOf; + let encryptService: SubstituteOf; let keyGenerationService: SubstituteOf; let cache: Map; diff --git a/apps/browser/src/services/localBackedSessionStorage.service.ts b/apps/browser/src/services/localBackedSessionStorage.service.ts index 5c98f4e3103..54b0537f725 100644 --- a/apps/browser/src/services/localBackedSessionStorage.service.ts +++ b/apps/browser/src/services/localBackedSessionStorage.service.ts @@ -1,6 +1,6 @@ import { Jsonify } from "type-fest"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { AbstractCachedStorageService, MemoryStorageServiceInterface, @@ -30,7 +30,7 @@ export class LocalBackedSessionStorageService private sessionStorage = new BrowserMemoryStorageService(); constructor( - private encryptService: AbstractEncryptService, + private encryptService: EncryptService, private keyGenerationService: AbstractKeyGenerationService ) { super(); diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index 5c35b45fde8..70c40f52e60 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -4,7 +4,7 @@ "noImplicitAny": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "module": "es6", + "module": "ES2020", "target": "ES2016", "allowJs": true, "sourceMap": true, @@ -17,5 +17,5 @@ "angularCompilerOptions": { "preserveWhitespaces": true }, - "include": ["src"] + "include": ["src", "../../libs/common/src/services/**/*.worker.ts"] } diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index c14fcc229a8..42738b7dfa7 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -20,7 +20,7 @@ import { CipherService } from "@bitwarden/common/services/cipher.service"; import { CollectionService } from "@bitwarden/common/services/collection.service"; import { ContainerService } from "@bitwarden/common/services/container.service"; import { CryptoService } from "@bitwarden/common/services/crypto.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import { EnvironmentService } from "@bitwarden/common/services/environment.service"; import { ExportService } from "@bitwarden/common/services/export.service"; import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; @@ -94,7 +94,7 @@ export class Main { exportService: ExportService; searchService: SearchService; cryptoFunctionService: NodeCryptoFunctionService; - encryptService: EncryptService; + encryptService: EncryptServiceImplementation; authService: AuthService; policyService: PolicyService; program: Program; @@ -140,7 +140,11 @@ export class Main { (level) => process.env.BITWARDENCLI_DEBUG !== "true" && level <= LogLevelType.Info ); this.cryptoFunctionService = new NodeCryptoFunctionService(); - this.encryptService = new EncryptService(this.cryptoFunctionService, this.logService, true); + this.encryptService = new EncryptServiceImplementation( + this.cryptoFunctionService, + this.logService, + true + ); this.storageService = new LowdbStorageService(this.logService, null, p, false, true); this.secureStorageService = new NodeEnvSecureStorageService( this.storageService, @@ -211,7 +215,8 @@ export class Main { this.i18nService, null, this.logService, - this.stateService + this.stateService, + this.encryptService ); this.broadcasterService = new BroadcasterService(); diff --git a/apps/desktop/config/base.json b/apps/desktop/config/base.json index 6df6c2cfdb1..3ae895a19cf 100644 --- a/apps/desktop/config/base.json +++ b/apps/desktop/config/base.json @@ -1,4 +1,6 @@ { "dev_flags": {}, - "flags": {} + "flags": { + "multithreadDecryption": false + } } diff --git a/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts b/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts index 0a5f4323b5f..916ec3c286e 100644 --- a/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts +++ b/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts @@ -6,7 +6,7 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service"; import { DecryptedCommandData } from "../../src/models/nativeMessaging/decryptedCommandData"; @@ -32,7 +32,7 @@ const CONFIRMATION_MESSAGE_TIMEOUT = 100 * 1000; // 100 seconds export default class NativeMessageService { private ipcService: IPCService; private nodeCryptoFunctionService: NodeCryptoFunctionService; - private encryptService: EncryptService; + private encryptService: EncryptServiceImplementation; constructor(private apiVersion: number) { console.log("Starting native messaging service"); @@ -41,7 +41,7 @@ export default class NativeMessageService { }); this.nodeCryptoFunctionService = new NodeCryptoFunctionService(); - this.encryptService = new EncryptService( + this.encryptService = new EncryptServiceImplementation( this.nodeCryptoFunctionService, new ConsoleLogService(false), false diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 7e250a1ee5e..525d593911d 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from "@angular/core"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service"; import { EventService as EventServiceAbstraction } from "@bitwarden/common/abstractions/event.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service"; @@ -36,7 +36,7 @@ export class InitService { private cryptoService: CryptoServiceAbstraction, private nativeMessagingService: NativeMessagingService, private themingService: AbstractThemingService, - private encryptService: AbstractEncryptService + private encryptService: EncryptService ) {} init() { diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 35ce7604bf7..aa4e9210899 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -11,12 +11,12 @@ import { } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/abstractions/auth.service"; import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/abstractions/broadcaster.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/abstractions/cipher.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service"; import { @@ -115,7 +115,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK"); useClass: ElectronCryptoService, deps: [ CryptoFunctionServiceAbstraction, - AbstractEncryptService, + EncryptService, PlatformUtilsServiceAbstraction, LogServiceAbstraction, StateServiceAbstraction, diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 6bca2f5739a..f2f28c73065 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -4,7 +4,7 @@ "noImplicitAny": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "module": "es6", + "module": "ES2020", "target": "ES2016", "sourceMap": true, "types": [], @@ -18,5 +18,5 @@ "angularCompilerOptions": { "preserveWhitespaces": true }, - "include": ["src"] + "include": ["src", "../../libs/common/src/services/**/*.worker.ts"] } diff --git a/apps/web/src/app/app.module.ts b/apps/web/src/app/app.module.ts index 5d1afd21224..0f29ea14fa7 100644 --- a/apps/web/src/app/app.module.ts +++ b/apps/web/src/app/app.module.ts @@ -6,7 +6,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { AppComponent } from "./app.component"; -import { CoreModule } from "./core"; +import { CoreModule } from "./core/core.module"; import { OssRoutingModule } from "./oss-routing.module"; import { OssModule } from "./oss.module"; import { WildcardRoutingModule } from "./wildcard-routing.module"; diff --git a/apps/web/src/app/core/index.ts b/apps/web/src/app/core/index.ts index 80c1a44d50f..f9a92f361cc 100644 --- a/apps/web/src/app/core/index.ts +++ b/apps/web/src/app/core/index.ts @@ -1,4 +1,7 @@ -export * from "./core.module"; +// Do not export this here or it will import MultithreadEncryptService (via JslibServicesModule) into test code. +// MultithreadEncryptService contains ES2020 features (import.meta) which are not supported in Node and Jest. +// Revisit this when Node & Jest get stable support for ESM. +// export * from "./core.module"; export * from "./event.service"; export * from "./policy-list.service"; export * from "./router.service"; diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 0f61efb6738..75089529f22 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from "@angular/core"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EnvironmentService as EnvironmentServiceAbstraction, Urls, @@ -33,7 +33,7 @@ export class InitService { private stateService: StateServiceAbstraction, private cryptoService: CryptoServiceAbstraction, private themingService: AbstractThemingService, - private encryptService: AbstractEncryptService + private encryptService: EncryptService ) {} init() { diff --git a/apps/web/src/app/tests/preloaded-english-i18n.module.ts b/apps/web/src/app/tests/preloaded-english-i18n.module.ts index 140065cd28c..e51fafc28f4 100644 --- a/apps/web/src/app/tests/preloaded-english-i18n.module.ts +++ b/apps/web/src/app/tests/preloaded-english-i18n.module.ts @@ -3,7 +3,7 @@ import { APP_INITIALIZER, NgModule } from "@angular/core"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService as BaseI18nService } from "@bitwarden/common/services/i18n.service"; -import * as eng from "../../locales/en/messages.json"; +import eng from "../../locales/en/messages.json"; class PreloadedEnglishI18nService extends BaseI18nService { constructor() { diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index aeb99ec3491..b226cd4c09a 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -15,5 +15,10 @@ "preserveWhitespaces": true }, "files": ["src/polyfills.ts", "src/main.ts", "../../bitwarden_license/bit-web/src/main.ts"], - "include": ["src/connectors/*.ts", "src/**/*.stories.ts", "src/**/*.spec.ts"] + "include": [ + "src/connectors/*.ts", + "src/**/*.stories.ts", + "src/**/*.spec.ts", + "../../libs/common/src/services/**/*.worker.ts" + ] } diff --git a/bitwarden_license/bit-web/src/app/app.module.ts b/bitwarden_license/bit-web/src/app/app.module.ts index c26b04ff309..9c8745d6b27 100644 --- a/bitwarden_license/bit-web/src/app/app.module.ts +++ b/bitwarden_license/bit-web/src/app/app.module.ts @@ -7,7 +7,7 @@ import { RouterModule } from "@angular/router"; import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { CoreModule } from "@bitwarden/web-vault/app/core"; +import { CoreModule } from "@bitwarden/web-vault/app/core/core.module"; import { OssRoutingModule } from "@bitwarden/web-vault/app/oss-routing.module"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; import { WildcardRoutingModule } from "@bitwarden/web-vault/app/wildcard-routing.module"; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index ee472c2f35d..83725838a58 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1,6 +1,5 @@ import { Injector, LOCALE_ID, NgModule } from "@angular/core"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/abstractions/account/account-api.service"; import { InternalAccountService, @@ -18,6 +17,7 @@ import { ConfigApiServiceAbstraction } from "@bitwarden/common/abstractions/conf import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service"; import { EventService as EventServiceAbstraction } from "@bitwarden/common/abstractions/event.service"; import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service"; @@ -62,6 +62,7 @@ import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/co import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; +import { flagEnabled } from "@bitwarden/common/misc/flags"; import { Account } from "@bitwarden/common/models/domain/account"; import { GlobalState } from "@bitwarden/common/models/domain/global-state"; import { AccountApiServiceImplementation } from "@bitwarden/common/services/account/account-api.service"; @@ -77,7 +78,8 @@ import { ConfigApiService } from "@bitwarden/common/services/config/config-api.s import { ConfigService } from "@bitwarden/common/services/config/config.service"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { CryptoService } from "@bitwarden/common/services/crypto.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; +import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation"; import { EnvironmentService } from "@bitwarden/common/services/environment.service"; import { EventService } from "@bitwarden/common/services/event.service"; import { ExportService } from "@bitwarden/common/services/export.service"; @@ -216,7 +218,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; i18nService: I18nServiceAbstraction, injector: Injector, logService: LogService, - stateService: StateServiceAbstraction + stateService: StateServiceAbstraction, + encryptService: EncryptService ) => new CipherService( cryptoService, @@ -226,7 +229,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; i18nService, () => injector.get(SearchServiceAbstraction), logService, - stateService + stateService, + encryptService ), deps: [ CryptoServiceAbstraction, @@ -237,6 +241,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; Injector, // TODO: Get rid of this circular dependency! LogService, StateServiceAbstraction, + EncryptService, ], }, { @@ -299,7 +304,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; useClass: CryptoService, deps: [ CryptoFunctionServiceAbstraction, - AbstractEncryptService, + EncryptService, PlatformUtilsServiceAbstraction, LogService, StateServiceAbstraction, @@ -442,8 +447,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; deps: [WINDOW], }, { - provide: AbstractEncryptService, - useClass: EncryptService, + provide: EncryptService, + useFactory: encryptServiceFactory, deps: [CryptoFunctionServiceAbstraction, LogService, LOG_MAC_FAILURES], }, { @@ -576,3 +581,13 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; ], }) export class JslibServicesModule {} + +function encryptServiceFactory( + cryptoFunctionservice: CryptoFunctionServiceAbstraction, + logService: LogService, + logMacFailures: boolean +): EncryptService { + return flagEnabled("multithreadDecryption") + ? new MultithreadEncryptServiceImplementation(cryptoFunctionservice, logService, logMacFailures) + : new EncryptServiceImplementation(cryptoFunctionservice, logService, logMacFailures); +} diff --git a/libs/common/spec/models/domain/attachment.spec.ts b/libs/common/spec/models/domain/attachment.spec.ts index 3a418a7f6b2..50e5ee7cae0 100644 --- a/libs/common/spec/models/domain/attachment.spec.ts +++ b/libs/common/spec/models/domain/attachment.spec.ts @@ -1,7 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { AttachmentData } from "@bitwarden/common/models/data/attachment.data"; import { Attachment } from "@bitwarden/common/models/domain/attachment"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; @@ -58,11 +58,11 @@ describe("Attachment", () => { describe("decrypt", () => { let cryptoService: MockProxy; - let encryptService: MockProxy; + let encryptService: MockProxy; beforeEach(() => { cryptoService = mock(); - encryptService = mock(); + encryptService = mock(); (window as any).bitwardenContainerService = new ContainerService( cryptoService, diff --git a/libs/common/spec/models/domain/cipher.spec.ts b/libs/common/spec/models/domain/cipher.spec.ts index cd149621786..cd49ea5484b 100644 --- a/libs/common/spec/models/domain/cipher.spec.ts +++ b/libs/common/spec/models/domain/cipher.spec.ts @@ -20,6 +20,7 @@ import { SecureNote } from "@bitwarden/common/models/domain/secure-note"; import { CardView } from "@bitwarden/common/models/view/card.view"; import { IdentityView } from "@bitwarden/common/models/view/identity.view"; import { LoginView } from "@bitwarden/common/models/view/login.view"; +import { InitializerKey } from "@bitwarden/common/services/cryptography/initializer-key"; import { mockEnc, mockFromJson } from "../../utils"; @@ -29,6 +30,7 @@ describe("Cipher DTO", () => { const cipher = new Cipher(data); expect(cipher).toEqual({ + initializerKey: InitializerKey.Cipher, id: null, organizationId: null, folderId: null, @@ -120,6 +122,7 @@ describe("Cipher DTO", () => { const cipher = new Cipher(cipherData); expect(cipher).toEqual({ + initializerKey: InitializerKey.Cipher, id: "id", organizationId: "orgId", folderId: "folderId", @@ -271,6 +274,7 @@ describe("Cipher DTO", () => { const cipher = new Cipher(cipherData); expect(cipher).toEqual({ + initializerKey: InitializerKey.Cipher, id: "id", organizationId: "orgId", folderId: "folderId", @@ -379,6 +383,7 @@ describe("Cipher DTO", () => { const cipher = new Cipher(cipherData); expect(cipher).toEqual({ + initializerKey: InitializerKey.Cipher, id: "id", organizationId: "orgId", folderId: "folderId", @@ -512,6 +517,7 @@ describe("Cipher DTO", () => { const cipher = new Cipher(cipherData); expect(cipher).toEqual({ + initializerKey: InitializerKey.Cipher, id: "id", organizationId: "orgId", folderId: "folderId", diff --git a/libs/common/spec/models/domain/encString.spec.ts b/libs/common/spec/models/domain/encString.spec.ts index cdc373c2a35..a7e05584aab 100644 --- a/libs/common/spec/models/domain/encString.spec.ts +++ b/libs/common/spec/models/domain/encString.spec.ts @@ -2,8 +2,8 @@ import { Substitute, Arg } from "@fluffy-spoon/substitute"; import { mock, MockProxy } from "jest-mock-extended"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EncryptionType } from "@bitwarden/common/enums/encryptionType"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; @@ -52,7 +52,7 @@ describe("EncString", () => { const cryptoService = Substitute.for(); cryptoService.getOrgKey(null).resolves(null); - const encryptService = Substitute.for(); + const encryptService = Substitute.for(); encryptService.decryptToUtf8(encString, Arg.any()).resolves("decrypted"); beforeEach(() => { @@ -157,12 +157,12 @@ describe("EncString", () => { describe("decrypt", () => { let cryptoService: MockProxy; - let encryptService: MockProxy; + let encryptService: MockProxy; let encString: EncString; beforeEach(() => { cryptoService = mock(); - encryptService = mock(); + encryptService = mock(); encString = new EncString(null); (window as any).bitwardenContainerService = new ContainerService( diff --git a/libs/common/spec/models/domain/send.spec.ts b/libs/common/spec/models/domain/send.spec.ts index 70f19857efd..b79cdee3d8d 100644 --- a/libs/common/spec/models/domain/send.spec.ts +++ b/libs/common/spec/models/domain/send.spec.ts @@ -1,8 +1,8 @@ // eslint-disable-next-line no-restricted-imports import { Substitute, Arg, SubstituteOf } from "@fluffy-spoon/substitute"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { SendType } from "@bitwarden/common/enums/sendType"; import { SendData } from "@bitwarden/common/models/data/send.data"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; @@ -112,7 +112,7 @@ describe("Send", () => { cryptoService.decryptToBytes(send.key, null).resolves(makeStaticByteArray(32)); cryptoService.makeSendKey(Arg.any()).resolves("cryptoKey" as any); - const encryptService = Substitute.for(); + const encryptService = Substitute.for(); (window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService); diff --git a/libs/common/spec/services/cipher.service.spec.ts b/libs/common/spec/services/cipher.service.spec.ts index c4e95e62176..0dda9300843 100644 --- a/libs/common/spec/services/cipher.service.spec.ts +++ b/libs/common/spec/services/cipher.service.spec.ts @@ -3,6 +3,7 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { FileUploadService } from "@bitwarden/common/abstractions/fileUpload.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; @@ -27,6 +28,7 @@ describe("Cipher Service", () => { let i18nService: SubstituteOf; let searchService: SubstituteOf; let logService: SubstituteOf; + let encryptService: SubstituteOf; let cipherService: CipherService; @@ -39,6 +41,7 @@ describe("Cipher Service", () => { i18nService = Substitute.for(); searchService = Substitute.for(); logService = Substitute.for(); + encryptService = Substitute.for(); cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES); cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT)); @@ -51,7 +54,8 @@ describe("Cipher Service", () => { i18nService, () => searchService, logService, - stateService + stateService, + encryptService ); }); diff --git a/libs/common/spec/services/crypto.service.spec.ts b/libs/common/spec/services/crypto.service.spec.ts index 40db49dfa37..64905008588 100644 --- a/libs/common/spec/services/crypto.service.spec.ts +++ b/libs/common/spec/services/crypto.service.spec.ts @@ -1,7 +1,7 @@ import { mock, mockReset } from "jest-mock-extended"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; @@ -11,7 +11,7 @@ describe("cryptoService", () => { let cryptoService: CryptoService; const cryptoFunctionService = mock(); - const encryptService = mock(); + const encryptService = mock(); const platformUtilService = mock(); const logService = mock(); const stateService = mock(); diff --git a/libs/common/spec/services/encrypt.service.spec.ts b/libs/common/spec/services/encrypt.service.spec.ts index 850731b3711..289ea65e447 100644 --- a/libs/common/spec/services/encrypt.service.spec.ts +++ b/libs/common/spec/services/encrypt.service.spec.ts @@ -6,7 +6,7 @@ import { EncryptionType } from "@bitwarden/common/enums/encryptionType"; import { EncArrayBuffer } from "@bitwarden/common/models/domain/enc-array-buffer"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import { makeStaticByteArray } from "../utils"; @@ -14,13 +14,13 @@ describe("EncryptService", () => { const cryptoFunctionService = mock(); const logService = mock(); - let encryptService: EncryptService; + let encryptService: EncryptServiceImplementation; beforeEach(() => { mockReset(cryptoFunctionService); mockReset(logService); - encryptService = new EncryptService(cryptoFunctionService, logService, true); + encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true); }); describe("encryptToBytes", () => { diff --git a/libs/common/spec/services/folder.service.spec.ts b/libs/common/spec/services/folder.service.spec.ts index b5d0491bf6f..1c1f0b2a464 100644 --- a/libs/common/spec/services/folder.service.spec.ts +++ b/libs/common/spec/services/folder.service.spec.ts @@ -2,9 +2,9 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { BehaviorSubject, firstValueFrom } from "rxjs"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { FolderData } from "@bitwarden/common/models/data/folder.data"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; @@ -17,7 +17,7 @@ describe("Folder Service", () => { let folderService: FolderService; let cryptoService: SubstituteOf; - let encryptService: SubstituteOf; + let encryptService: SubstituteOf; let i18nService: SubstituteOf; let cipherService: SubstituteOf; let stateService: SubstituteOf; diff --git a/libs/common/spec/services/policy.service.spec.ts b/libs/common/spec/services/policy.service.spec.ts index 3f52557d05f..232f971327a 100644 --- a/libs/common/spec/services/policy.service.spec.ts +++ b/libs/common/spec/services/policy.service.spec.ts @@ -3,6 +3,7 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { BehaviorSubject, firstValueFrom } from "rxjs"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { PolicyType } from "@bitwarden/common/enums/policyType"; @@ -16,7 +17,6 @@ import { ResetPasswordPolicyOptions } from "@bitwarden/common/models/domain/rese import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { PolicyResponse } from "@bitwarden/common/models/response/policy.response"; import { ContainerService } from "@bitwarden/common/services/container.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; import { PolicyService } from "@bitwarden/common/services/policy/policy.service"; import { StateService } from "@bitwarden/common/services/state.service"; diff --git a/libs/common/spec/services/settings.service.spec.ts b/libs/common/spec/services/settings.service.spec.ts index a52ef59a60b..59c4715dda6 100644 --- a/libs/common/spec/services/settings.service.spec.ts +++ b/libs/common/spec/services/settings.service.spec.ts @@ -2,8 +2,8 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { BehaviorSubject, firstValueFrom } from "rxjs"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { ContainerService } from "@bitwarden/common/services/container.service"; import { SettingsService } from "@bitwarden/common/services/settings.service"; import { StateService } from "@bitwarden/common/services/state.service"; @@ -12,7 +12,7 @@ describe("SettingsService", () => { let settingsService: SettingsService; let cryptoService: SubstituteOf; - let encryptService: SubstituteOf; + let encryptService: SubstituteOf; let stateService: SubstituteOf; let activeAccount: BehaviorSubject; let activeAccountUnlocked: BehaviorSubject; diff --git a/libs/common/src/abstractions/abstractEncrypt.service.ts b/libs/common/src/abstractions/encrypt.service.ts similarity index 70% rename from libs/common/src/abstractions/abstractEncrypt.service.ts rename to libs/common/src/abstractions/encrypt.service.ts index 63c6f041a37..17e72907c8a 100644 --- a/libs/common/src/abstractions/abstractEncrypt.service.ts +++ b/libs/common/src/abstractions/encrypt.service.ts @@ -1,9 +1,11 @@ import { IEncrypted } from "../interfaces/IEncrypted"; +import { Decryptable } from "../interfaces/decryptable.interface"; +import { InitializerMetadata } from "../interfaces/initializer-metadata.interface"; import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; import { EncString } from "../models/domain/enc-string"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; -export abstract class AbstractEncryptService { +export abstract class EncryptService { abstract encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise; abstract encryptToBytes: ( plainValue: ArrayBuffer, @@ -12,4 +14,8 @@ export abstract class AbstractEncryptService { abstract decryptToUtf8: (encString: EncString, key: SymmetricCryptoKey) => Promise; abstract decryptToBytes: (encThing: IEncrypted, key: SymmetricCryptoKey) => Promise; abstract resolveLegacyKey: (key: SymmetricCryptoKey, encThing: IEncrypted) => SymmetricCryptoKey; + abstract decryptItems: ( + items: Decryptable[], + key: SymmetricCryptoKey + ) => Promise; } diff --git a/libs/common/src/interfaces/decryptable.interface.ts b/libs/common/src/interfaces/decryptable.interface.ts new file mode 100644 index 00000000000..ae5e8ebbf82 --- /dev/null +++ b/libs/common/src/interfaces/decryptable.interface.ts @@ -0,0 +1,12 @@ +import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; + +import { InitializerMetadata } from "./initializer-metadata.interface"; + +/** + * An object that contains EncStrings and knows how to decrypt them. This is usually a domain object with the + * corresponding view object as the type argument. + * @example Cipher implements Decryptable + */ +export interface Decryptable extends InitializerMetadata { + decrypt: (key?: SymmetricCryptoKey) => Promise; +} diff --git a/libs/common/src/interfaces/initializer-metadata.interface.ts b/libs/common/src/interfaces/initializer-metadata.interface.ts new file mode 100644 index 00000000000..9bc01326dd4 --- /dev/null +++ b/libs/common/src/interfaces/initializer-metadata.interface.ts @@ -0,0 +1,11 @@ +import { InitializerKey } from "../services/cryptography/initializer-key"; + +/** + * This interface enables deserialization of arbitrary objects by recording their class name as an enum, which + * will survive serialization. The enum can then be matched to a constructor or factory method for deserialization. + * See get-class-initializer.ts for the initializer map. + */ +export interface InitializerMetadata { + initializerKey: InitializerKey; + toJSON?: () => { initializerKey: InitializerKey }; +} diff --git a/libs/common/src/misc/flags.ts b/libs/common/src/misc/flags.ts index aa425c36046..7811d3477f2 100644 --- a/libs/common/src/misc/flags.ts +++ b/libs/common/src/misc/flags.ts @@ -1,6 +1,8 @@ // required to avoid linting errors when there are no flags /* eslint-disable @typescript-eslint/ban-types */ -export type SharedFlags = {}; +export type SharedFlags = { + multithreadDecryption: boolean; +}; // required to avoid linting errors when there are no flags /* eslint-disable @typescript-eslint/ban-types */ diff --git a/libs/common/src/misc/utils.ts b/libs/common/src/misc/utils.ts index 537c8e58e12..ff21a919f1f 100644 --- a/libs/common/src/misc/utils.ts +++ b/libs/common/src/misc/utils.ts @@ -1,8 +1,8 @@ /* eslint-disable no-useless-escape */ import { getHostname, parse } from "tldts"; -import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service"; import { CryptoService } from "../abstractions/crypto.service"; +import { EncryptService } from "../abstractions/encrypt.service"; import { I18nService } from "../abstractions/i18n.service"; const nodeURL = typeof window === "undefined" ? require("url") : null; @@ -14,7 +14,7 @@ declare global { interface BitwardenContainerService { getCryptoService: () => CryptoService; - getEncryptService: () => AbstractEncryptService; + getEncryptService: () => EncryptService; } export class Utils { diff --git a/libs/common/src/models/domain/cipher.ts b/libs/common/src/models/domain/cipher.ts index 585a4395f7e..7b4a4d4b1fd 100644 --- a/libs/common/src/models/domain/cipher.ts +++ b/libs/common/src/models/domain/cipher.ts @@ -2,6 +2,8 @@ import { Jsonify } from "type-fest"; import { CipherRepromptType } from "../../enums/cipherRepromptType"; import { CipherType } from "../../enums/cipherType"; +import { Decryptable } from "../../interfaces/decryptable.interface"; +import { InitializerKey } from "../../services/cryptography/initializer-key"; import { CipherData } from "../data/cipher.data"; import { LocalData } from "../data/local.data"; import { CipherView } from "../view/cipher.view"; @@ -17,7 +19,9 @@ import { Password } from "./password"; import { SecureNote } from "./secure-note"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; -export class Cipher extends Domain { +export class Cipher extends Domain implements Decryptable { + readonly initializerKey = InitializerKey.Cipher; + id: string; organizationId: string; folderId: string; diff --git a/libs/common/src/models/view/cipher.view.ts b/libs/common/src/models/view/cipher.view.ts index 41043b7e5ee..c3c69b3b9e7 100644 --- a/libs/common/src/models/view/cipher.view.ts +++ b/libs/common/src/models/view/cipher.view.ts @@ -3,6 +3,8 @@ import { Jsonify } from "type-fest"; import { CipherRepromptType } from "../../enums/cipherRepromptType"; import { CipherType } from "../../enums/cipherType"; import { LinkedIdType } from "../../enums/linkedIdType"; +import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; +import { InitializerKey } from "../../services/cryptography/initializer-key"; import { LocalData } from "../data/local.data"; import { Cipher } from "../domain/cipher"; @@ -15,7 +17,9 @@ import { PasswordHistoryView } from "./password-history.view"; import { SecureNoteView } from "./secure-note.view"; import { View } from "./view"; -export class CipherView implements View { +export class CipherView implements View, InitializerMetadata { + readonly initializerKey = InitializerKey.CipherView; + id: string = null; organizationId: string = null; folderId: string = null; diff --git a/libs/common/src/services/cipher.service.ts b/libs/common/src/services/cipher.service.ts index b9aeff659da..a7b54942e2c 100644 --- a/libs/common/src/services/cipher.service.ts +++ b/libs/common/src/services/cipher.service.ts @@ -3,6 +3,7 @@ import { firstValueFrom } from "rxjs"; import { ApiService } from "../abstractions/api.service"; import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; import { CryptoService } from "../abstractions/crypto.service"; +import { EncryptService } from "../abstractions/encrypt.service"; import { FileUploadService } from "../abstractions/fileUpload.service"; import { I18nService } from "../abstractions/i18n.service"; import { LogService } from "../abstractions/log.service"; @@ -65,7 +66,8 @@ export class CipherService implements CipherServiceAbstraction { private i18nService: I18nService, private searchService: () => SearchService, private logService: LogService, - private stateService: StateService + private stateService: StateService, + private encryptService: EncryptService ) {} async getDecryptedCipherCache(): Promise { @@ -329,35 +331,50 @@ export class CipherService implements CipherServiceAbstraction { @sequentialize(() => "getAllDecrypted") async getAllDecrypted(): Promise { - const userId = await this.stateService.getUserId(); if ((await this.getDecryptedCipherCache()) != null) { - if ( - this.searchService != null && - (this.searchService().indexedEntityId ?? userId) !== userId - ) { - await this.searchService().indexCiphers(userId, await this.getDecryptedCipherCache()); - } + await this.reindexCiphers(); return await this.getDecryptedCipherCache(); } - const decCiphers: CipherView[] = []; const hasKey = await this.cryptoService.hasKey(); if (!hasKey) { throw new Error("No key."); } - const promises: Promise[] = []; const ciphers = await this.getAll(); - ciphers.forEach(async (cipher) => { - promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); - }); + const orgKeys = await this.cryptoService.getOrgKeys(); + const userKey = await this.cryptoService.getKeyForUserEncryption(); + + // Group ciphers by orgId or under 'null' for the user's ciphers + const grouped = ciphers.reduce((agg, c) => { + agg[c.organizationId] ??= []; + agg[c.organizationId].push(c); + return agg; + }, {} as Record); + + const decCiphers = ( + await Promise.all( + Object.entries(grouped).map(([orgId, groupedCiphers]) => + this.encryptService.decryptItems(groupedCiphers, orgKeys.get(orgId) ?? userKey) + ) + ) + ) + .flat() + .sort(this.getLocaleSortingFunction()); - await Promise.all(promises); - decCiphers.sort(this.getLocaleSortingFunction()); await this.setDecryptedCipherCache(decCiphers); return decCiphers; } + private async reindexCiphers() { + const userId = await this.stateService.getUserId(); + const reindexRequired = + this.searchService != null && (this.searchService().indexedEntityId ?? userId) !== userId; + if (reindexRequired) { + await this.searchService().indexCiphers(userId, await this.getDecryptedCipherCache()); + } + } + async getAllDecryptedForGrouping(groupingId: string, folder = true): Promise { const ciphers = await this.getAllDecrypted(); @@ -488,21 +505,17 @@ export class CipherService implements CipherServiceAbstraction { } async getAllFromApiForOrganization(organizationId: string): Promise { - const ciphers = await this.apiService.getCiphersOrganization(organizationId); - if (ciphers != null && ciphers.data != null && ciphers.data.length) { - const decCiphers: CipherView[] = []; - const promises: any[] = []; - ciphers.data.forEach((r) => { - const data = new CipherData(r); - const cipher = new Cipher(data); - promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); - }); - await Promise.all(promises); - decCiphers.sort(this.getLocaleSortingFunction()); - return decCiphers; - } else { + const response = await this.apiService.getCiphersOrganization(organizationId); + if (response?.data == null || response.data.length < 1) { return []; } + + const ciphers = response.data.map((cr) => new Cipher(new CipherData(cr))); + const key = await this.cryptoService.getOrgKey(organizationId); + const decCiphers = await this.encryptService.decryptItems(ciphers, key); + + decCiphers.sort(this.getLocaleSortingFunction()); + return decCiphers; } async getLastUsedForUrl(url: string, autofillOnPageLoad = false): Promise { diff --git a/libs/common/src/services/container.service.ts b/libs/common/src/services/container.service.ts index 9e50705d6aa..b6cb7b70a0d 100644 --- a/libs/common/src/services/container.service.ts +++ b/libs/common/src/services/container.service.ts @@ -1,11 +1,8 @@ -import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service"; import { CryptoService } from "../abstractions/crypto.service"; +import { EncryptService } from "../abstractions/encrypt.service"; export class ContainerService { - constructor( - private cryptoService: CryptoService, - private encryptService: AbstractEncryptService - ) {} + constructor(private cryptoService: CryptoService, private encryptService: EncryptService) {} attachToGlobal(global: any) { if (!global.bitwardenContainerService) { @@ -26,7 +23,7 @@ export class ContainerService { /** * @throws Will throw if EncryptService was not instantiated and provided to the ContainerService constructor */ - getEncryptService(): AbstractEncryptService { + getEncryptService(): EncryptService { if (this.encryptService == null) { throw new Error("ContainerService.encryptService not initialized."); } diff --git a/libs/common/src/services/crypto.service.ts b/libs/common/src/services/crypto.service.ts index 8524598ede3..5a4137771ea 100644 --- a/libs/common/src/services/crypto.service.ts +++ b/libs/common/src/services/crypto.service.ts @@ -1,8 +1,8 @@ import * as bigInt from "big-integer"; -import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service"; import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service"; import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; +import { EncryptService } from "../abstractions/encrypt.service"; import { LogService } from "../abstractions/log.service"; import { PlatformUtilsService } from "../abstractions/platformUtils.service"; import { StateService } from "../abstractions/state.service"; @@ -25,7 +25,7 @@ import { ProfileProviderResponse } from "../models/response/profile-provider.res export class CryptoService implements CryptoServiceAbstraction { constructor( private cryptoFunctionService: CryptoFunctionService, - private encryptService: AbstractEncryptService, + private encryptService: EncryptService, protected platformUtilService: PlatformUtilsService, protected logService: LogService, protected stateService: StateService diff --git a/libs/common/src/services/encrypt.service.ts b/libs/common/src/services/cryptography/encrypt.service.implementation.ts similarity index 80% rename from libs/common/src/services/encrypt.service.ts rename to libs/common/src/services/cryptography/encrypt.service.implementation.ts index c210fdcd7b6..86b2c795f84 100644 --- a/libs/common/src/services/encrypt.service.ts +++ b/libs/common/src/services/cryptography/encrypt.service.implementation.ts @@ -1,19 +1,21 @@ -import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service"; -import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -import { LogService } from "../abstractions/log.service"; -import { EncryptionType } from "../enums/encryptionType"; -import { IEncrypted } from "../interfaces/IEncrypted"; -import { Utils } from "../misc/utils"; -import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; -import { EncString } from "../models/domain/enc-string"; -import { EncryptedObject } from "../models/domain/encrypted-object"; -import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; +import { CryptoFunctionService } from "../../abstractions/cryptoFunction.service"; +import { EncryptService } from "../../abstractions/encrypt.service"; +import { LogService } from "../../abstractions/log.service"; +import { EncryptionType } from "../../enums/encryptionType"; +import { IEncrypted } from "../../interfaces/IEncrypted"; +import { Decryptable } from "../../interfaces/decryptable.interface"; +import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; +import { Utils } from "../../misc/utils"; +import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; +import { EncString } from "../../models/domain/enc-string"; +import { EncryptedObject } from "../../models/domain/encrypted-object"; +import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; -export class EncryptService implements AbstractEncryptService { +export class EncryptServiceImplementation implements EncryptService { constructor( - private cryptoFunctionService: CryptoFunctionService, - private logService: LogService, - private logMacFailures: boolean + protected cryptoFunctionService: CryptoFunctionService, + protected logService: LogService, + protected logMacFailures: boolean ) {} async encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise { @@ -148,6 +150,17 @@ export class EncryptService implements AbstractEncryptService { return result ?? null; } + async decryptItems( + items: Decryptable[], + key: SymmetricCryptoKey + ): Promise { + if (items == null || items.length < 1) { + return []; + } + + return await Promise.all(items.map((item) => item.decrypt(key))); + } + private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise { const obj = new EncryptedObject(); obj.key = key; diff --git a/libs/common/src/services/cryptography/encrypt.worker.ts b/libs/common/src/services/cryptography/encrypt.worker.ts new file mode 100644 index 00000000000..0ee2914ad4b --- /dev/null +++ b/libs/common/src/services/cryptography/encrypt.worker.ts @@ -0,0 +1,56 @@ +import { Jsonify } from "type-fest"; + +import { Decryptable } from "../../interfaces/decryptable.interface"; +import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; +import { ConsoleLogService } from "../../services/consoleLog.service"; +import { ContainerService } from "../../services/container.service"; +import { WebCryptoFunctionService } from "../../services/webCryptoFunction.service"; + +import { EncryptServiceImplementation } from "./encrypt.service.implementation"; +import { getClassInitializer } from "./get-class-initializer"; + +const workerApi: Worker = self as any; + +let inited = false; +let encryptService: EncryptServiceImplementation; + +/** + * Bootstrap the worker environment with services required for decryption + */ +export function init() { + const cryptoFunctionService = new WebCryptoFunctionService(self); + const logService = new ConsoleLogService(false); + encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true); + + const bitwardenContainerService = new ContainerService(null, encryptService); + bitwardenContainerService.attachToGlobal(self); + + inited = true; +} + +/** + * Listen for messages and decrypt their contents + */ +workerApi.addEventListener("message", async (event: { data: string }) => { + if (!inited) { + init(); + } + + const request: { + id: string; + items: Jsonify>[]; + key: Jsonify; + } = JSON.parse(event.data); + + const key = SymmetricCryptoKey.fromJSON(request.key); + const items = request.items.map((jsonItem) => { + const initializer = getClassInitializer>(jsonItem.initializerKey); + return initializer(jsonItem); + }); + const result = await encryptService.decryptItems(items, key); + + workerApi.postMessage({ + id: request.id, + items: JSON.stringify(result), + }); +}); diff --git a/libs/common/src/services/cryptography/get-class-initializer.ts b/libs/common/src/services/cryptography/get-class-initializer.ts new file mode 100644 index 00000000000..accdb1c4a4f --- /dev/null +++ b/libs/common/src/services/cryptography/get-class-initializer.ts @@ -0,0 +1,22 @@ +import { Jsonify } from "type-fest"; + +import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; +import { Cipher } from "../../models/domain/cipher"; +import { CipherView } from "../../models/view/cipher.view"; + +import { InitializerKey } from "./initializer-key"; + +/** + * Internal reference of classes so we can reconstruct objects properly. + * Each entry should be keyed using the Decryptable.initializerKey property + */ +const classInitializers: Record any> = { + [InitializerKey.Cipher]: Cipher.fromJSON, + [InitializerKey.CipherView]: CipherView.fromJSON, +}; + +export function getClassInitializer( + className: InitializerKey +): (obj: Jsonify) => T { + return classInitializers[className]; +} diff --git a/libs/common/src/services/cryptography/initializer-key.ts b/libs/common/src/services/cryptography/initializer-key.ts new file mode 100644 index 00000000000..88e36d90515 --- /dev/null +++ b/libs/common/src/services/cryptography/initializer-key.ts @@ -0,0 +1,4 @@ +export enum InitializerKey { + Cipher = 0, + CipherView = 1, +} diff --git a/libs/common/src/services/cryptography/multithread-encrypt.service.implementation.ts b/libs/common/src/services/cryptography/multithread-encrypt.service.implementation.ts new file mode 100644 index 00000000000..9df104975cf --- /dev/null +++ b/libs/common/src/services/cryptography/multithread-encrypt.service.implementation.ts @@ -0,0 +1,82 @@ +import { defaultIfEmpty, filter, firstValueFrom, fromEvent, map, Subject, takeUntil } from "rxjs"; +import { Jsonify } from "type-fest"; + +import { Decryptable } from "../../interfaces/decryptable.interface"; +import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; +import { Utils } from "../../misc/utils"; +import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; + +import { EncryptServiceImplementation } from "./encrypt.service.implementation"; +import { getClassInitializer } from "./get-class-initializer"; + +// TTL (time to live) is not strictly required but avoids tying up memory resources if inactive +const workerTTL = 3 * 60000; // 3 minutes + +export class MultithreadEncryptServiceImplementation extends EncryptServiceImplementation { + private worker: Worker; + private timeout: any; + + private clear$ = new Subject(); + + /** + * Sends items to a web worker to decrypt them. + * This utilises multithreading to decrypt items faster without interrupting other operations (e.g. updating UI). + */ + async decryptItems( + items: Decryptable[], + key: SymmetricCryptoKey + ): Promise { + if (items == null || items.length < 1) { + return []; + } + + this.logService.info("Starting decryption using multithreading"); + + this.worker ??= new Worker( + new URL("@bitwarden/common/services/cryptography/encrypt.worker.ts", import.meta.url) + ); + + this.restartTimeout(); + + const request = { + id: Utils.newGuid(), + items: items, + key: key, + }; + + this.worker.postMessage(JSON.stringify(request)); + + return await firstValueFrom( + fromEvent(this.worker, "message").pipe( + filter((response: MessageEvent) => response.data?.id === request.id), + map((response) => JSON.parse(response.data.items)), + map((items) => + items.map((jsonItem: Jsonify) => { + const initializer = getClassInitializer(jsonItem.initializerKey); + return initializer(jsonItem); + }) + ), + takeUntil(this.clear$), + defaultIfEmpty([]) + ) + ); + } + + private clear() { + this.clear$.next(); + this.worker?.terminate(); + this.worker = null; + this.clearTimeout(); + } + + private restartTimeout() { + this.clearTimeout(); + this.timeout = setTimeout(() => this.clear(), workerTTL); + } + + private clearTimeout() { + if (this.timeout != null) { + clearTimeout(this.timeout); + } + } +} diff --git a/libs/electron/src/services/electronCrypto.service.ts b/libs/electron/src/services/electronCrypto.service.ts index 0fe814b1385..bb1ad996d69 100644 --- a/libs/electron/src/services/electronCrypto.service.ts +++ b/libs/electron/src/services/electronCrypto.service.ts @@ -1,5 +1,5 @@ -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; @@ -10,7 +10,7 @@ import { CryptoService } from "@bitwarden/common/services/crypto.service"; export class ElectronCryptoService extends CryptoService { constructor( cryptoFunctionService: CryptoFunctionService, - encryptService: AbstractEncryptService, + encryptService: EncryptService, platformUtilService: PlatformUtilsService, logService: LogService, stateService: StateService diff --git a/libs/shared/tsconfig.json b/libs/shared/tsconfig.json index 3a70f6b92cf..a6cbd30e0d5 100644 --- a/libs/shared/tsconfig.json +++ b/libs/shared/tsconfig.json @@ -4,7 +4,7 @@ "moduleResolution": "node", "noImplicitAny": true, "target": "ES6", - "module": "commonjs", + "module": "es2020", "lib": ["es5", "es6", "es7", "dom"], "sourceMap": true, "allowSyntheticDefaultImports": true, diff --git a/tsconfig.json b/tsconfig.json index f8b94f2a1a3..6761a4f5c20 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "moduleResolution": "node", "noImplicitAny": true, "target": "ES6", - "module": "commonjs", + "module": "ES2020", "lib": ["es5", "es6", "es7", "dom"], "sourceMap": true, "allowSyntheticDefaultImports": true, From 96205bf6b8b6579a7b23638e559426f6c7f399ba Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Fri, 28 Oct 2022 07:41:10 +1000 Subject: [PATCH 03/83] Don't refresh org vault on filter change (#3879) --- .../src/app/organizations/vault/vault.component.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/web/src/app/organizations/vault/vault.component.ts b/apps/web/src/app/organizations/vault/vault.component.ts index 7de36e65318..8b6478fa35c 100644 --- a/apps/web/src/app/organizations/vault/vault.component.ts +++ b/apps/web/src/app/organizations/vault/vault.component.ts @@ -166,10 +166,16 @@ export class VaultComponent implements OnInit, OnDestroy { async applyVaultFilter(vaultFilter: VaultFilter) { this.ciphersComponent.showAddNew = vaultFilter.status !== "trash"; this.activeFilter = vaultFilter; - await this.ciphersComponent.reload( - this.activeFilter.buildFilter(), - vaultFilter.status === "trash" - ); + + // Hack to avoid calling cipherService.getAllFromApiForOrganization every time the vault filter changes. + // Call CiphersComponent.applyFilter directly instead of going through CiphersComponent.reload, which + // reloads all the ciphers unnecessarily. Will be fixed properly by EC-14. + this.ciphersComponent.loaded = false; + this.ciphersComponent.deleted = vaultFilter.status === "trash" || false; + await this.ciphersComponent.applyFilter(this.activeFilter.buildFilter()); + this.ciphersComponent.loaded = true; + // End hack + this.vaultFilterComponent.searchPlaceholder = this.vaultService.calculateSearchBarLocalizationString(this.activeFilter); this.go(); From 8654c0f8afbc3145fbe3055baf77bc629413167b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Oct 2022 02:12:36 +0200 Subject: [PATCH 04/83] Autosync the updated translations (#3914) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/ar/messages.json | 14 +- apps/desktop/src/locales/be/messages.json | 26 +-- apps/desktop/src/locales/da/messages.json | 20 +- apps/desktop/src/locales/fi/messages.json | 104 +++++------ apps/desktop/src/locales/nl/messages.json | 36 ++-- apps/desktop/src/locales/pl/messages.json | 44 ++--- apps/desktop/src/locales/ro/messages.json | 186 +++++++++---------- apps/desktop/src/locales/zh_CN/messages.json | 98 +++++----- 8 files changed, 264 insertions(+), 264 deletions(-) diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 5926319e22b..07e59b1ebc9 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -1267,7 +1267,7 @@ "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "أنشئ", "description": "ex. Date this item was created" }, "datePasswordUpdated": { @@ -1575,10 +1575,10 @@ "message": "يتم استخدام تكامل المتصفح للقياسات الحيوية في المتصفح." }, "enableDuckDuckGoBrowserIntegration": { - "message": "Allow DuckDuckGo browser integration" + "message": "السماح بدمج متصفح DuckDuckGo" }, "enableDuckDuckGoBrowserIntegrationDesc": { - "message": "Use your Bitwarden vault when browsing with DuckDuckGo." + "message": "استخدم مخزن Bitwarden الخاص بك عند التصفح باستخدام DuckDuckGo." }, "browserIntegrationUnsupportedTitle": { "message": "تكامل المتصفح غير مدعوم" @@ -1608,7 +1608,7 @@ "message": "الرجاء التأكد من أن البصمة المعروضة متطابقة مع البصمة المعروضة في ملحق المتصفح." }, "verifyNativeMessagingConnectionTitle": { - "message": "$APPID$ wants to connect to Bitwarden", + "message": "$APPID$ يريد الاتصال بـ Bitwarden", "placeholders": { "appid": { "content": "$1", @@ -1617,10 +1617,10 @@ } }, "verifyNativeMessagingConnectionDesc": { - "message": "Would you like to approve this request?" + "message": "هل ترغب في الموافقة على هذا الطلب؟" }, "verifyNativeMessagingConnectionWarning": { - "message": "If you did not initiate this request, do not approve it." + "message": "وإذا لم تقم بالشروع في هذا الطلب، فلا توافق عليه." }, "biometricsNotEnabledTitle": { "message": "القياسات الحيوية غير مفعلة" @@ -2020,6 +2020,6 @@ "message": "Mir" }, "vault": { - "message": "Vault" + "message": "الخزنة" } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 519ff8f1029..8e3c90a356b 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -361,19 +361,19 @@ "message": "Выдаліць далучэнне" }, "deleteItemConfirmation": { - "message": "Вы ўпэўнены, што хочаце адправіць гэты элемент у сметніцу?" + "message": "Вы сапраўды хочаце адправіць гэты элемент у сметніцу?" }, "deletedItem": { "message": "Элемент адпраўлены ў сметніцу" }, "overwritePasswordConfirmation": { - "message": "Вы ўпэўнены, што хочаце перазапісаць бягучы пароль?" + "message": "Вы сапраўды хочаце перазапісаць бягучы пароль?" }, "overwriteUsername": { "message": "Перазапісаць імя карыстальніка" }, "overwriteUsernameConfirmation": { - "message": "Вы ўпэўнены, што хочаце перазапісаць бягучае імя карыстальніка?" + "message": "Вы сапраўды хочаце перазапісаць бягучае імя карыстальніка?" }, "noneFolder": { "message": "Без папкі", @@ -458,7 +458,7 @@ "message": "Далучэнне выдалена" }, "deleteAttachmentConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэта далучэнне?" + "message": "Вы сапраўды хочаце выдаліць гэта далучэнне?" }, "attachmentSaved": { "message": "Далучэнне захавана." @@ -482,7 +482,7 @@ "message": "Папка дададзена" }, "deleteFolderConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэту папку?" + "message": "Вы сапраўды хочаце выдаліць гэту папку?" }, "deletedFolder": { "message": "Папка выдалена" @@ -728,7 +728,7 @@ "message": "Тэрмін дзеяння вашага сеансу завяршыўся." }, "logOutConfirmation": { - "message": "Вы ўпэўнены, што хочаце выйсці?" + "message": "Вы сапраўды хочаце выйсці?" }, "logOut": { "message": "Выйсці" @@ -1358,7 +1358,7 @@ "message": "Ненадзейны асноўны пароль" }, "weakMasterPasswordDesc": { - "message": "Асноўны пароль, які вы выбралі з'яўляецца ненадзейным. Для належнай абароны ўліковага запісу Bitwarden, вы павінны выкарыстоўваць надзейны асноўны пароль (або парольную фразу). Вы ўпэўнены, што хочаце выкарыстоўваць гэты асноўны пароль?" + "message": "Асноўны пароль, які вы выбралі з'яўляецца ненадзейным. Для належнай абароны ўліковага запісу Bitwarden, вы павінны выкарыстоўваць надзейны асноўны пароль (або парольную фразу). Вы сапраўды хочаце выкарыстоўваць гэты асноўны пароль?" }, "pin": { "message": "PIN", @@ -1447,7 +1447,7 @@ "message": "Палітыка прыватнасці" }, "unsavedChangesConfirmation": { - "message": "Вы ўпэўнены, што хочаце выйсці? У такім выпадку, ваша інфармацыя не будзе захавана." + "message": "Вы сапраўды хочаце выйсці? У такім выпадку, ваша інфармацыя не будзе захавана." }, "unsavedChangesTitle": { "message": "Незахаваныя змены" @@ -1482,7 +1482,7 @@ "message": "Назаўсёды выдаліць элемент" }, "permanentlyDeleteItemConfirmation": { - "message": "Вы ўпэўнены, што хочаце назаўсёды выдаліць гэты элемент?" + "message": "Вы сапраўды хочаце назаўсёды выдаліць гэты элемент?" }, "permanentlyDeletedItem": { "message": "Элемент выдалены назаўсёды" @@ -1491,7 +1491,7 @@ "message": "Аднавіць элемент" }, "restoreItemConfirmation": { - "message": "Вы ўпэўнены, што хочаце аднавіць гэты элемент?" + "message": "Вы сапраўды хочаце аднавіць гэты элемент?" }, "restoredItem": { "message": "Элемент адноўлены" @@ -1500,7 +1500,7 @@ "message": "Выдаліць назаўсёды" }, "vaultTimeoutLogOutConfirmation": { - "message": "Выхад з сістэмы скасуе ўсе магчымасці доступу да сховішча і запатрабуе аўтэнтыфікацыю праз інтэрнэт пасля завяршэння часу чакання. Вы ўпэўнены, што хочаце выкарыстоўваць гэты параметр?" + "message": "Выхад з сістэмы скасуе ўсе магчымасці доступу да сховішча і запатрабуе аўтэнтыфікацыю праз інтэрнэт пасля завяршэння часу чакання. Вы сапраўды хочаце выкарыстоўваць гэты параметр?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Пацвярджэнне дзеяння часу чакання" @@ -1755,7 +1755,7 @@ "message": "Карыстальніцкі" }, "deleteSendConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэты Send?", + "message": "Вы сапраўды хочаце выдаліць гэты Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkToClipboard": { @@ -2014,7 +2014,7 @@ "message": "Доступ да элементаў у адключаных арганізацыях немагчымы. Звяжыце з уладальнікам арганізацыі для атрымання дапамогі." }, "neverLockWarning": { - "message": "Вы ўпэўнены, што хочаце адключыць блакіроўку сховішча? Прызначыўшы параметр блакіравання \"Ніколі\", ключ шыфравання будзе захоўвацца на вашай прыладзе. Калі вы выкарыстоўваеце гэты параметр, вы павінны быць упэўнены ў тым, што ваша прылада надзейна абаронена." + "message": "Вы сапраўды хочаце адключыць блакіроўку сховішча? Прызначыўшы параметр блакіравання \"Ніколі\", ключ шыфравання будзе захоўвацца на вашай прыладзе. Калі вы выкарыстоўваеце гэты параметр, вы павінны быць упэўнены ў тым, што ваша прылада надзейна абаронена." }, "cardBrandMir": { "message": "Mir" diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index c37265b4343..e8d99356078 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -6,7 +6,7 @@ "message": "Filtre" }, "allItems": { - "message": "Alle elementer" + "message": "Alle emner" }, "favorites": { "message": "Favoritter" @@ -33,10 +33,10 @@ "message": "Samlinger" }, "searchVault": { - "message": "Søg i boks" + "message": "Søg i Boks" }, "addItem": { - "message": "Tilføj element" + "message": "Tilføj emne" }, "shared": { "message": "Delt" @@ -67,7 +67,7 @@ "message": "Vedhæftninger" }, "viewItem": { - "message": "Vis element" + "message": "Vis emne" }, "name": { "message": "Navn" @@ -98,22 +98,22 @@ "message": "Adgangssætning" }, "editItem": { - "message": "Redigér element" + "message": "Redigér emne" }, "emailAddress": { - "message": "Emailadresse" + "message": "E-mailadresse" }, "verificationCodeTotp": { - "message": "Verifikationskode (TOTP)" + "message": "Bekræftelseskode (TOTP)" }, "website": { - "message": "Hjemmeside" + "message": "Websted" }, "notes": { "message": "Notater" }, "customFields": { - "message": "Brugerdefinerede felter" + "message": "Tilpassede felter" }, "launch": { "message": "Kør" @@ -1989,7 +1989,7 @@ "message": "Søg i min boks" }, "forwardedEmail": { - "message": "Videresendt E-mail Alias" + "message": "Videresendt e-mail alias" }, "forwardedEmailDesc": { "message": "Generér et e-mail alias med en ekstern viderestillingstjeneste." diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 1ec92986405..785fa955d7e 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -132,7 +132,7 @@ "message": "Näytä tai piilota" }, "toggleCollapse": { - "message": "Laajenna tai pienennä", + "message": "Laajenna tai supista", "description": "Toggling an expand/collapse state." }, "cardholderName": { @@ -148,10 +148,10 @@ "message": "Erääntymisaika" }, "securityCode": { - "message": "Turvakoodi" + "message": "Turvakoodi (CVC/CVV)" }, "identityName": { - "message": "Identiteetin nimi" + "message": "Henkilöllisyyden nimi" }, "company": { "message": "Yritys" @@ -163,7 +163,7 @@ "message": "Passin numero" }, "licenseNumber": { - "message": "Rekisterinumero" + "message": "Ajokortin numero" }, "email": { "message": "Sähköposti" @@ -178,7 +178,7 @@ "message": "Premium vaaditaan" }, "premiumRequiredDesc": { - "message": "Tarvitset premium-jäsenyyden tämän toiminnon käyttämiseen." + "message": "Käyttääksesi tätä toimintoa tarvitset Premium-jäsenyyden." }, "errorOccurred": { "message": "Tapahtui virhe." @@ -308,7 +308,7 @@ "message": "Muokkaa" }, "authenticatorKeyTotp": { - "message": "Todennusavain (TOTP)" + "message": "Todennusmenetelmän avain (TOTP)" }, "folder": { "message": "Kansio" @@ -349,7 +349,7 @@ "message": "Kohde lisättiin" }, "editedItem": { - "message": "Kohdetta muokattiin" + "message": "Kohde tallennettiin" }, "deleteItem": { "message": "Poista kohde" @@ -416,7 +416,7 @@ "message": "Sanojen määrä" }, "wordSeparator": { - "message": "Sanaerotin" + "message": "Sanojen erotin" }, "capitalize": { "message": "Sanat isoilla alkukirjaimilla", @@ -448,7 +448,7 @@ "message": "Hae suosikeista" }, "searchType": { - "message": "Etsi tyypeistä", + "message": "Hae tyypeistä", "description": "Search item type" }, "newAttachment": { @@ -476,10 +476,10 @@ "message": "Et voi käyttää tätä toimintoa ennen kuin päivität salausavaimesi." }, "editedFolder": { - "message": "Kansiota muokattiin" + "message": "Kansio tallennettiin" }, "addedFolder": { - "message": "Kansio lisätty" + "message": "Kansio lisättiin" }, "deleteFolderConfirmation": { "message": "Haluatko varmasti poistaa kansion?" @@ -566,7 +566,7 @@ "message": "Lähetä koodi" }, "codeSent": { - "message": "Koodi lähetetty" + "message": "Koodi lähetettiin" }, "verificationCode": { "message": "Todennuskoodi" @@ -620,7 +620,7 @@ "message": "Kytke todennuslaitteesi tietokoneen USB-porttiin ja jos laitteessa on painike, paina sitä." }, "recoveryCodeDesc": { - "message": "Etkö pysty käyttämään kaksivaiheisen kirjautumisen todentajiasi? Poista kaikki tilisi todentajat käytöstä palautuskoodillasi." + "message": "Etkö pysty käyttämään kaksivaiheisen kirjautumisen todentajiasi? Poista kaikki tilillesi määritetyt todentajat käytöstä palautuskoodillasi." }, "recoveryCodeTitle": { "message": "Palautuskoodi" @@ -650,7 +650,7 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Käytä mitä tahansa WebAuthn‑yhteensopivaa todennuslaitetta päästäksesi käsiksi tiliisi." + "message": "Avaa tilisi millä tahansa WebAuthn‑yhteensopivalla todennuslaitteella." }, "emailTitle": { "message": "Sähköposti" @@ -662,7 +662,7 @@ "message": "Kirjautuminen ei ole käytettävissä" }, "noTwoStepProviders": { - "message": "Tilillä on käytössä kaksivaiheinen kirjautuminen, mutta tämä laite ei tue käytettävissä olevia todentajia." + "message": "Tilille on määritetty kaksivaiheinen kirjautuminen, mutta tämä selain ei tue käytettävissä olevia todentajia." }, "noTwoStepProviders2": { "message": "Lisää muita todentajia, joita tuetaan laajemmin eri laitteilla (kuten todennussovellus)." @@ -701,7 +701,7 @@ "message": "Kuvakepalvelimen URL" }, "environmentSaved": { - "message": "Palvelinympäristön URL-osoitteet tallennettiin." + "message": "Palvelinympäristön URL-osoitteet tallennettiin" }, "ok": { "message": "Ok" @@ -719,7 +719,7 @@ "message": "Lue lisää" }, "featureUnavailable": { - "message": "Toiminto ei ole käytettävissä" + "message": "Ominaisuus ei ole käytettävissä" }, "loggedOut": { "message": "Kirjauduttu ulos" @@ -734,13 +734,13 @@ "message": "Kirjaudu ulos" }, "addNewLogin": { - "message": "Lisää uusi käyttäjätunnus" + "message": "Uusi kirjautumistieto" }, "addNewItem": { - "message": "Lisää uusi kohde" + "message": "Uusi kohde" }, "addNewFolder": { - "message": "Lisää uusi kansio" + "message": "Uusi kansio" }, "view": { "message": "Näytä" @@ -782,7 +782,7 @@ "message": "Voit vaihtaa pääsalasanasi bitwarden.com-verkkoholvissa. Haluatko käydä sivustolla nyt?" }, "fingerprintPhrase": { - "message": "Todennuslauseke", + "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." }, "yourAccountsFingerprint": { @@ -827,7 +827,7 @@ "message": "Virheellinen pääsalasana" }, "twoStepLoginConfirmation": { - "message": "Kaksivaiheinen kirjautuminen tekee tilistäsi turvallisemman edellyttämällä sinulta kirjautumisvahvistuksen todennuslaitteen, ‑sovelluksen, tekstiviestin, puhelun tai sähköpostin avulla. Voit ottaa kaksivaiheisen kirjautumisen käyttöön bitwarden.com‑verkkoholvissa. Haluatko käydä sivustolla nyt?" + "message": "Kaksivaiheinen kirjautuminen tekee tilistäsi turvallisemman edellyttämällä salasanan lisäksi kirjautumisen lisätodennusta todennuslaitteen, ‑sovelluksen, tekstiviestin, puhelun tai sähköpostin avulla. Voit ottaa kaksivaiheisen kirjautumisen käyttöön bitwarden.com‑verkkoholvista. Haluatko avata sivuston nyt?" }, "twoStepLogin": { "message": "Kaksivaiheinen kirjautuminen" @@ -958,10 +958,10 @@ "message": "Näytä Bitwardenin kuvake Dockissa myös silloin, kun se on pienennetty valikkoriville." }, "confirmTrayTitle": { - "message": "Vahvista ilmoitusalueen käytöstä poisto" + "message": "Vahvista piilotus ilmoitusalueelta" }, "confirmTrayDesc": { - "message": "Asetuksen käytöstä poisto kytkee myös kaikki muut ilmoitusalueeseen liittyvät asetukset pois käytöstä." + "message": "Asetuksen käytöstä poisto poistaa myös kaikki muut ilmoitusalueeseen liittyvät asetukset käytöstä." }, "language": { "message": "Kieli" @@ -1012,7 +1012,7 @@ } }, "updateAvailable": { - "message": "Päivitys saatavilla" + "message": "Päivitys on saatavilla" }, "updateAvailableDesc": { "message": "Päivitys löydetty. Haluatko ladata sen nyt?" @@ -1033,7 +1033,7 @@ "message": "Tuntematon" }, "copyUsername": { - "message": "Kopioi käyttäjänimi" + "message": "Kopioi käyttäjätunnus" }, "copyNumber": { "message": "Kopioi numero", @@ -1056,10 +1056,10 @@ "message": "Päivitä jäsenyys" }, "premiumNotCurrentMember": { - "message": "Et ole tällä hetkellä premium-jäsen." + "message": "Et ole tällä hetkellä Premium-jäsen." }, "premiumSignUpAndGet": { - "message": "Ryhdy Premium-jäseneksi saadaksesi:" + "message": "Liity Premium-jäseneksi saadaksesi:" }, "premiumSignUpStorage": { "message": "1 Gt salattua tallennustilaa tiedostoliitteille." @@ -1140,13 +1140,13 @@ "message": "Palauta zoomaus" }, "toggleFullScreen": { - "message": "Koko näytön tila päälle/pois" + "message": "Kytke koko näytön tila" }, "reload": { "message": "Lataa uudelleen" }, "toggleDevTools": { - "message": "Kehittäjätyökalut päälle/pois" + "message": "Kytke kehittäjätyökalut" }, "minimize": { "message": "Pienennä", @@ -1231,7 +1231,7 @@ "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "Täsmäystapa", + "message": "Tunnistustapa", "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { @@ -1263,7 +1263,7 @@ "description": "Application window should always stay on top of other windows" }, "dateUpdated": { - "message": "Päivitetty", + "message": "Päivitettiin", "description": "ex. Date this item was updated" }, "dateCreated": { @@ -1271,7 +1271,7 @@ "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Salasana päivitetty", + "message": "Salasana päivitettiin", "description": "ex. Date this password was updated" }, "exportVault": { @@ -1476,7 +1476,7 @@ "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { - "message": "Etsi roskakorista" + "message": "Hae roskakorista" }, "permanentlyDeleteItem": { "message": "Poista kohde pysyvästi" @@ -1623,13 +1623,13 @@ "message": "Älä hyväksy pyyntöä, jos et tunnista sitä." }, "biometricsNotEnabledTitle": { - "message": "Biometria ei ole käytössä" + "message": "Biometriaa ei ole määritetty" }, "biometricsNotEnabledDesc": { - "message": "Käyttääksesi biometriaa selaimessa, on biometria otettava käyttöön työpöytäsovelluksen asetuksista." + "message": "Biometria selaimissa edellyttää sen määritystä työpöytäsovelluksen asetuksista." }, "personalOwnershipSubmitError": { - "message": "Yrityksen asettaman käytännön johdosta kohteiden tallennus omaan holviisi ei ole mahdollista. Muuta omistusasetus organisaatiolle ja valitse käytettävissä olevista kokoelmista." + "message": "Yrityskäytännön johdosta kohteiden tallennus henkilökohtaiseen holviin ei ole mahdollista. Muuta omistusasetus organisaatiolle ja valitse käytettävissä olevista kokoelmista." }, "hintEqualsPassword": { "message": "Salasanavihjeesi ei voi olla sama kuin salasanasi." @@ -1676,7 +1676,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { - "message": "Käyttöoikeuksien enimmäismäärä", + "message": "Käyttökertojen enimmäismäärä", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "maxAccessCountDesc": { @@ -1711,11 +1711,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send luotu", + "message": "Send lisättiin", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Sendiä muokattiin", + "message": "Send tallennettiin", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { @@ -1730,7 +1730,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "Send luotu", + "message": "Uusi Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -1759,18 +1759,18 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkToClipboard": { - "message": "Kopioi Sendin linkki leikepöydälle", + "message": "Kopioi Send-linkki leikepöydälle", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkOnSave": { "message": "Kopioi Sendin linkki leikepöydälle tallennettaessa." }, "sendDisabled": { - "message": "Send on poistettiin käytöstä", + "message": "Send poistettiin", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Yrityksen käytännön vuoksi voit poistaa vain olemassa olevan Sendin.", + "message": "Yrityskäytännön vuoksi voit poistaa vain olemassa olevan Sendin.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { @@ -1798,7 +1798,7 @@ "message": "Yksi tai useampi organisaatiokäytäntö vaikuttaa Send-asetuksiisi." }, "emailVerificationRequired": { - "message": "Sähköpostiosoitteen vahvistus vaaditaan" + "message": "Sähköpostiosoite on vahvistettava" }, "emailVerificationRequiredDesc": { "message": "Sinun on vahvistettava sähköpostiosoitteesi käyttääksesi tätä ominaisuutta." @@ -1813,10 +1813,10 @@ "message": "Toiminto on suojattu. Jatka vahvistamalla henkilöllisyytesi syöttämällä pääsalasanasi uudelleen." }, "updatedMasterPassword": { - "message": "Pääsalasana on päivitetty" + "message": "Pääsalasana päivitettiin" }, "updateMasterPassword": { - "message": "Päivitä pääsalasana" + "message": "Vaihda pääsalasana" }, "updateMasterPasswordWarning": { "message": "Organisaatiosi ylläpito on hiljattain vaihtanut pääsalasanasi. Käyttääksesi holvia, on sinun päivitettävä se nyt. Tämä uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." @@ -1850,10 +1850,10 @@ "message": "Organisaatiolla on käytäntö, joka liittää tilisi automaattisesti salasanan palautusapuun. Liitos sallii organisaation ylläpitäjien vaihtaa pääsalasanasi." }, "vaultExportDisabled": { - "message": "Holvin vienti on poistettu käytöstä" + "message": "Holvin vienti poistettiin" }, "personalVaultExportPolicyInEffect": { - "message": "Yksi tai useampi organisaation käytäntö estää henkilökohtaisen holvisi viennin." + "message": "Yksi tai useampi organisaatiokäytäntö estää henkilökohtaisen holvisi viennin." }, "addAccount": { "message": "Lisää tili" @@ -1922,7 +1922,7 @@ "message": "Henkilökohtaisen holvin vienti" }, "exportingPersonalVaultDescription": { - "message": "Vain tunnukseen $EMAIL$ liitetyt henkilökohtaiset holvin kohteet viedään. Organisaation kohteet eivät sisälly tähän.", + "message": "Vain tunnukseen $EMAIL$ liitetyt henkilökohtaisen holvin kohteet viedään. Organisaation kohteet eivät sisälly tähän.", "placeholders": { "email": { "content": "$1", @@ -2008,10 +2008,10 @@ "message": "Premium-tilaus vaaditaan" }, "organizationIsDisabled": { - "message": "Organisaatio on poistettu käytöstä." + "message": "Organisaatio on jäädytetty" }, "disabledOrganizationFilterError": { - "message": "Käytöstä poistettujen organisaatioiden kohteet eivät ole käytettävissä. Ole yhteydessä organisaation omistajaan saadaksesi apua." + "message": "Jäädytettyjen organisaatioiden kohteet eivät ole käytettävissä. Ole yhteydessä organisaation omistajaan saadaksesi apua." }, "neverLockWarning": { "message": "Haluatko varmasti käyttää asetusta \"Ei koskaan\"? Se tallentaa holvisi salausavaimen laitteellesi. Jos käytät asetusta, varmista, että laite on suojattu hyvin." diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 163afedfd92..06be89f2e89 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -1281,25 +1281,25 @@ "message": "Bestandsindeling" }, "hCaptchaUrl": { - "message": "hCaptcha Url", + "message": "hCaptcha-URL", "description": "hCaptcha is the name of a website, should not be translated" }, "loadAccessibilityCookie": { - "message": "Load accessibility cookie" + "message": "Toegankelijkheidscookie laden" }, "registerAccessibilityUser": { - "message": "Register as an accessibility user at", + "message": "Registreer als toegankelijkheidsgebruiker op", "description": "ex. Register as an accessibility user at hcaptcha.com" }, "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" + "message": "Kopieer en plak de link die naar uw e-mail is gestuurd hieronder" }, "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", + "message": "Voer een URL in om toegankelijkheidscookie voor hCaptcha te laden", "description": "hCaptcha is the name of a website, should not be translated" }, "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", + "message": "hCaptcha-URL is vereist", "description": "hCaptcha is the name of a website, should not be translated" }, "invalidUrl": { @@ -1309,10 +1309,10 @@ "message": "Klaar" }, "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" + "message": "Toegankelijkheidscookie opgeslagen!" }, "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" + "message": "Geen toegankelijkheidscookie opgeslagen" }, "warning": { "message": "WAARSCHUWING", @@ -1575,13 +1575,13 @@ "message": "Browserintegratie wordt gebruikt voor biometrie in de browser." }, "enableDuckDuckGoBrowserIntegration": { - "message": "Allow DuckDuckGo browser integration" + "message": "DuckDuckGo browserintegratie toestaan" }, "enableDuckDuckGoBrowserIntegrationDesc": { - "message": "Use your Bitwarden vault when browsing with DuckDuckGo." + "message": "Gebruik je Bitwardenkluis tijdens het browsen met DuckDuckGo." }, "browserIntegrationUnsupportedTitle": { - "message": "Browser integration not supported" + "message": "Browserintegratie niet ondersteund" }, "browserIntegrationMasOnlyDesc": { "message": "Helaas wordt browserintegratie momenteel alleen ondersteund in de Mac App Store-versie." @@ -1590,7 +1590,7 @@ "message": "Helaas wordt browserintegratie momenteel niet ondersteund in de Windows Store-versie." }, "browserIntegrationLinuxDesc": { - "message": "Unfortunately browser integration is currently not supported in the linux version." + "message": "Helaas wordt browserintegratie in de linux versie momenteel niet ondersteund." }, "enableBrowserIntegrationFingerprint": { "message": "Vereis verificatie voor browserintegratie" @@ -1608,7 +1608,7 @@ "message": "Verzeker je ervan dat de getoonde vingerafdruk hetzelfde is als de vingerafdruk in de browserextensie." }, "verifyNativeMessagingConnectionTitle": { - "message": "$APPID$ wants to connect to Bitwarden", + "message": "$APPID$ wil verbinding maken met Bitwarden", "placeholders": { "appid": { "content": "$1", @@ -1617,10 +1617,10 @@ } }, "verifyNativeMessagingConnectionDesc": { - "message": "Would you like to approve this request?" + "message": "Wil je dit verzoek goedkeuren?" }, "verifyNativeMessagingConnectionWarning": { - "message": "If you did not initiate this request, do not approve it." + "message": "Als u dit verzoek niet heeft ingediend, keur het dan niet goed." }, "biometricsNotEnabledTitle": { "message": "Biometrie niet ingeschakeld" @@ -1989,17 +1989,17 @@ "message": "Kluis doorzoeken" }, "forwardedEmail": { - "message": "Forwarded email alias" + "message": "Doorgestuurd e-mailalias" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "Genereer een e-mailalias met een externe doorstuurservice." }, "hostname": { "message": "Hostnaam", "description": "Part of a URL." }, "apiAccessToken": { - "message": "API Access Token" + "message": "API-toegangstoken" }, "apiKey": { "message": "API-sleutel" diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 0c57eacdfd0..43b9bed3fe0 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -148,7 +148,7 @@ "message": "Data wygaśnięcia" }, "securityCode": { - "message": "Kod CVV/CVC" + "message": "Kod zabezpieczający" }, "identityName": { "message": "Nazwa profilu" @@ -254,7 +254,7 @@ "message": "Inne" }, "generatePassword": { - "message": "Generuj hasło" + "message": "Wygeneruj hasło" }, "type": { "message": "Rodzaj" @@ -349,7 +349,7 @@ "message": "Element został dodany" }, "editedItem": { - "message": "Element został zaktualizowany" + "message": "Element został zapisany" }, "deleteItem": { "message": "Usuń element" @@ -386,7 +386,7 @@ "message": "Edytuj folder" }, "regeneratePassword": { - "message": "Wygeneruj hasło ponownie" + "message": "Wygeneruj ponownie hasło" }, "copyPassword": { "message": "Kopiuj hasło" @@ -416,7 +416,7 @@ "message": "Liczba słów" }, "wordSeparator": { - "message": "Separator wyrazów" + "message": "Separator słów" }, "capitalize": { "message": "Wielkie litery", @@ -461,13 +461,13 @@ "message": "Czy na pewno chcesz usunąć ten załącznik?" }, "attachmentSaved": { - "message": "Załącznik został zapisany." + "message": "Załącznik został zapisany" }, "file": { "message": "Plik" }, "selectFile": { - "message": "Wybierz plik." + "message": "Wybierz plik" }, "maxFileSize": { "message": "Maksymalny rozmiar pliku to 500 MB." @@ -476,7 +476,7 @@ "message": "Nie możesz używać tej funkcji, dopóki nie zaktualizujesz klucza szyfrowania." }, "editedFolder": { - "message": "Folder został zaktualizowany" + "message": "Folder został zapisany" }, "addedFolder": { "message": "Folder został dodany" @@ -701,7 +701,7 @@ "message": "Adres URL serwera ikon" }, "environmentSaved": { - "message": "Adresy URL środowiska zostały zapisane." + "message": "Adresy URL środowiska zostały zapisane" }, "ok": { "message": "Ok" @@ -737,10 +737,10 @@ "message": "Dodaj dane logowania" }, "addNewItem": { - "message": "Dodaj element" + "message": "Nowy element" }, "addNewFolder": { - "message": "Dodaj folder" + "message": "Nowy folder" }, "view": { "message": "Widok" @@ -773,7 +773,7 @@ "message": "Obserwuj nas" }, "syncVault": { - "message": "Rozpocznij synchronizację sejfu" + "message": "Synchronizuj sejf" }, "changeMasterPass": { "message": "Zmień hasło główne" @@ -1036,7 +1036,7 @@ "message": "Kopiuj nazwę użytkownika" }, "copyNumber": { - "message": "Kopiuj numer karty", + "message": "Kopiuj numer", "description": "Copy credit card number" }, "copySecurityCode": { @@ -1044,10 +1044,10 @@ "description": "Copy credit card security code (CVV)" }, "premiumMembership": { - "message": "Członkostwo premium" + "message": "Konto Premium" }, "premiumManage": { - "message": "Zarządzaj członkostwem" + "message": "Zarządzaj kontem Premium" }, "premiumManageAlert": { "message": "Kontem Premium możesz zarządzać na stronie sejfu bitwarden.com. Czy chcesz otworzyć tę stronę?" @@ -1104,7 +1104,7 @@ "message": "Odświeżanie zostało zakończone" }, "passwordHistory": { - "message": "Historia haseł" + "message": "Historia hasła" }, "clear": { "message": "Wyczyść", @@ -1271,7 +1271,7 @@ "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Aktualizacja hasła", + "message": "Hasło zostało zaktualizowane", "description": "ex. Date this password was updated" }, "exportVault": { @@ -1711,11 +1711,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Wysyłka została utworzona", + "message": "Wysyłka została dodana", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Wysyłka została zaktualizowana", + "message": "Wysyłka została zapisana", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { @@ -1730,7 +1730,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "Utwórz wysyłkę", + "message": "Nowa wysyłka", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -1766,7 +1766,7 @@ "message": "Po zapisaniu wysyłki, skopiuj link do schowka." }, "sendDisabled": { - "message": "Wysyłka została wyłączona", + "message": "Wysyłka została usunięta", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -1862,7 +1862,7 @@ "message": "Usuń hasło główne" }, "removedMasterPassword": { - "message": "Hasło główne zostało usunięte." + "message": "Hasło główne zostało usunięte" }, "convertOrganizationEncryptionDesc": { "message": "Organizacja $ORGANIZATION$ używa jednokrotnego logowania SSO z własnym serwerem kluczy. Użytkownicy nie muszą logować się za pomocą hasła głównego.", diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 1b9489c6c5c..4feafaa3d69 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -6,7 +6,7 @@ "message": "Filtre" }, "allItems": { - "message": "Toate elementele" + "message": "Toate articolele" }, "favorites": { "message": "Favorite" @@ -24,7 +24,7 @@ "message": "Identitate" }, "typeSecureNote": { - "message": "Notă protejată" + "message": "Notă securizată" }, "folders": { "message": "Dosare" @@ -33,7 +33,7 @@ "message": "Colecții" }, "searchVault": { - "message": "Căutare în seif" + "message": "Căutare seif" }, "addItem": { "message": "Adăugare articol" @@ -132,11 +132,11 @@ "message": "Comutare vizibilitate" }, "toggleCollapse": { - "message": "Restrângere / Extindere", + "message": "Comutare restrângere", "description": "Toggling an expand/collapse state." }, "cardholderName": { - "message": "Deținător card" + "message": "Numele titularului cardului" }, "number": { "message": "Număr card" @@ -148,22 +148,22 @@ "message": "Expirare" }, "securityCode": { - "message": "Cod de securitate (CVV/CVC)" + "message": "Cod de securitate" }, "identityName": { - "message": "Nume identitate" + "message": "Numele identității" }, "company": { "message": "Companie" }, "ssn": { - "message": "Cod Numeric Personal" + "message": "Numărul de securitate socială" }, "passportNumber": { - "message": "Număr CI / Pașaport" + "message": "Numărul de pașaport" }, "licenseNumber": { - "message": "Număr licență" + "message": "Numărul de licență" }, "email": { "message": "E-mail" @@ -175,10 +175,10 @@ "message": "Adresă" }, "premiumRequired": { - "message": "Este necesară versiunea Premium" + "message": "Premium necesar" }, "premiumRequiredDesc": { - "message": "Este necesar statutul de membru Premium pentru a utiliza această caracteristică." + "message": "Pentru a utiliza această funcție este necesar un abonament Premium." }, "errorOccurred": { "message": "S-a produs o eroare." @@ -308,7 +308,7 @@ "message": "Editare" }, "authenticatorKeyTotp": { - "message": "Cheie autentificare (TOTP)" + "message": "Cheie de autentificare (TOTP)" }, "folder": { "message": "Dosar" @@ -349,7 +349,7 @@ "message": "Articol adăugat" }, "editedItem": { - "message": "Articol editat" + "message": "Articol salvat" }, "deleteItem": { "message": "Ștergere articol" @@ -364,19 +364,19 @@ "message": "Sigur doriți să trimiteți în coșul de reciclare?" }, "deletedItem": { - "message": "Articolul a fost trimis în coșul de reciclare" + "message": "Articol trimis în coșul de reciclare" }, "overwritePasswordConfirmation": { "message": "Sigur doriți să suprascrieți parola curentă?" }, "overwriteUsername": { - "message": "Suprascrieți numele de utilizator" + "message": "Suprascrieți numele utilizatorului" }, "overwriteUsernameConfirmation": { "message": "Sunteți sigur că doriți să suprascrieți numele de utilizator curent?" }, "noneFolder": { - "message": "Fără dosar", + "message": "Niciun dosar", "description": "This is the folder for uncategorized items" }, "addFolder": { @@ -432,11 +432,11 @@ "message": "Minimum de cifre" }, "minSpecial": { - "message": "Minimum de caractere speciale", + "message": "Minim de caractere speciale", "description": "Minimum Special Characters" }, "ambiguous": { - "message": "Se evită caracterele ambigue" + "message": "Evitare de caractere ambigue" }, "searchCollection": { "message": "Căutare în colecție" @@ -455,19 +455,19 @@ "message": "Adăugare atașament nou" }, "deletedAttachment": { - "message": "Atașamentul s-a șters" + "message": "Atașament șters" }, "deleteAttachmentConfirmation": { "message": "Sigur doriți să ștergeți acest atașament?" }, "attachmentSaved": { - "message": "Atașamentul a fost salvat." + "message": "Atașament salvat" }, "file": { "message": "Fișier" }, "selectFile": { - "message": "Selectare fișier." + "message": "Selectare fișier" }, "maxFileSize": { "message": "Mărimea maximă a fișierului este de 500 MB." @@ -476,7 +476,7 @@ "message": "Nu puteți utiliza această caracteristică înainte de a actualiza cheia de criptare." }, "editedFolder": { - "message": "Dosar editat" + "message": "Dosar salvat" }, "addedFolder": { "message": "Dosar adăugat" @@ -554,7 +554,7 @@ "message": "A survenit o eroare neașteptată." }, "itemInformation": { - "message": "Informații de autentificare" + "message": "Informații despre articol" }, "noItemsInList": { "message": "Niciun articol de afișat." @@ -620,13 +620,13 @@ "message": "Introduceți cheia de securitate în portul USB al computerului. Dacă are un buton, apăsați-l." }, "recoveryCodeDesc": { - "message": "Ați pierdut accesul la toți furnizorii de autentificare în două etape? Utilizați codul de recuperare pentru a dezactiva toți acești furnizori din contul dvs." + "message": "Ați pierdut accesul la toți furnizorii dvs. cu doi factori? Folosiți codul de recuperare pentru a dezactiva toți furnizorii cu doi factori din contul dvs." }, "recoveryCodeTitle": { "message": "Cod de recuperare" }, "authenticatorAppTitle": { - "message": "Aplicație de autentificare" + "message": "Aplicația Autentificator" }, "authenticatorAppDesc": { "message": "Utilizați o aplicație de autentificare (cum ar fi Authy sau Google Authenticator) pentru a genera codurile de verificare bazate pe timp.", @@ -650,7 +650,7 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Utilizați orice cheie de securitate activată WebAuthn pentru a vă accesa contul." + "message": "Utilizați orice cheie de securitate compatibilă cu WebAuthn pentru a vă accesa contul." }, "emailTitle": { "message": "E-mail" @@ -659,7 +659,7 @@ "message": "Codurile de verificare vor fi trimise prin e-mail." }, "loginUnavailable": { - "message": "Conectare indisponibilă" + "message": "Autentificare indisponibilă" }, "noTwoStepProviders": { "message": "Acest cont are activată autentificarea în două etape, dar niciunul dintre furnizorii configurați pentru aceasta nu este acceptat pe acest dispozitiv." @@ -671,7 +671,7 @@ "message": "Opțiuni de autentificare în două etape" }, "selfHostedEnvironment": { - "message": "Mediu de găzduire personal" + "message": "Mediul găzduit local" }, "selfHostedEnvironmentFooter": { "message": "Specificați URL-ul de bază al implementări Bitwarden găzduită local." @@ -689,7 +689,7 @@ "message": "URL server API" }, "webVaultUrl": { - "message": "URL server seif Web" + "message": "URL server seif web" }, "identityUrl": { "message": "URL server de identificare" @@ -698,10 +698,10 @@ "message": "URL server de notificări" }, "iconsUrl": { - "message": "URL server de iconuri" + "message": "URL server de pictograme" }, "environmentSaved": { - "message": "URL-urile mediului au fost salvate." + "message": "URL-urile mediului au fost salvate" }, "ok": { "message": "Ok" @@ -713,13 +713,13 @@ "message": "Nu" }, "overwritePassword": { - "message": "Modificare parolă" + "message": "Suprascriere parolă" }, "learnMore": { "message": "Aflați mai multe" }, "featureUnavailable": { - "message": "Caracteristică indisponibilă" + "message": "Funcție indisponibilă" }, "loggedOut": { "message": "Deconectat" @@ -734,13 +734,13 @@ "message": "Deconectare" }, "addNewLogin": { - "message": "Adăugare conectare nouă" + "message": "Autentificare nouă" }, "addNewItem": { - "message": "Adăugare element nou" + "message": "Articol nou" }, "addNewFolder": { - "message": "Adăugare dosar nou" + "message": "Dosar nou" }, "view": { "message": "Afișare" @@ -790,7 +790,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "goToWebVault": { - "message": "Deplasare la Seiful Web" + "message": "Accesare seif web" }, "getMobileApp": { "message": "Obținere aplicație pentru mobil" @@ -827,7 +827,7 @@ "message": "Parolă principală incorectă" }, "twoStepLoginConfirmation": { - "message": "Autentificarea în două etape întărește siguranța contului dvs. prin solicitarea unei confirmă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 activată în seiful web bitwarden.com. Doriți să vizitați saitul acum?" + "message": "Autentificarea în două etape vă face contul mai sigur, cerându-vă să vă verificați autentificarea 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ă pe seiful web bitwarden.com. Doriți să vizitați site-ul web acum?" }, "twoStepLogin": { "message": "Autentificare în două etape" @@ -928,7 +928,7 @@ "message": "La închiderea ferestrei se afișează un icon în bara de meniu." }, "enableTray": { - "message": "Se activează un icon în zona de notificare" + "message": "Activare pictogramă în tavă" }, "enableTrayDesc": { "message": "Se afișează întotdeauna un icon în zona de notificare." @@ -958,10 +958,10 @@ "message": "Afișează pictograma Bitwarden în Dock chiar și atunci când este minimizată în bara de meniu." }, "confirmTrayTitle": { - "message": "Confirmați dezactivarea zonei de notificare" + "message": "Confirmare ascundere tavă" }, "confirmTrayDesc": { - "message": "Dezactivarea acestei setări va dezactiva, de asemenea, toate celelalte setări legate de zona de notificare." + "message": "Dezactivarea setării va dezactiva, de asemenea, toate celelalte setări legate de tavă." }, "language": { "message": "Limbă" @@ -988,7 +988,7 @@ "description": "Copy to clipboard" }, "checkForUpdates": { - "message": "Verificare pentru actualizări…" + "message": "Verifica actualizări…" }, "version": { "message": "Versiunea $VERSION_NUM$", @@ -1012,7 +1012,7 @@ } }, "updateAvailable": { - "message": "Update disponibil" + "message": "Actualizare disponibilă" }, "updateAvailableDesc": { "message": "A fost găsit un update. Doriți să îl descărcați acum?" @@ -1044,22 +1044,22 @@ "description": "Copy credit card security code (CVV)" }, "premiumMembership": { - "message": "Membru Premium" + "message": "Abonament Premium" }, "premiumManage": { - "message": "Gestionare statut de membru" + "message": "Gestionare abonament" }, "premiumManageAlert": { "message": "Vă puteți gestiona abonamentul pe seiful web bitwarden.com. Doriți să vizitați saitul acum?" }, "premiumRefresh": { - "message": "Actualizare statut de membru" + "message": "Actualizare apartenență" }, "premiumNotCurrentMember": { - "message": "În prezent nu sunteți un membru Premium." + "message": "Nu sunteți în prezent un membru Premium." }, "premiumSignUpAndGet": { - "message": "Înscrieți-vă pentru statutul de membru Premium și obțineți:" + "message": "Înscrieți-vă pentru un abonament Premium și primiți:" }, "premiumSignUpStorage": { "message": "1 GB spațiu de stocare criptat pentru atașamente de fișiere." @@ -1128,7 +1128,7 @@ "description": "Paste from clipboard" }, "selectAll": { - "message": "Selectare totală" + "message": "Selectare tot" }, "zoomIn": { "message": "Mărire" @@ -1156,7 +1156,7 @@ "message": "Zoom" }, "bringAllToFront": { - "message": "Aducere tot în față", + "message": "Aducere totul în față", "description": "Bring all windows to front (foreground)" }, "aboutBitwarden": { @@ -1169,7 +1169,7 @@ "message": "Ascundere Bitwarden" }, "hideOthers": { - "message": "Ascunde celelalte" + "message": "Ascundere celelalte" }, "showAll": { "message": "Afișare tot" @@ -1239,7 +1239,7 @@ "description": "Default URI match detection for auto-fill." }, "toggleOptions": { - "message": "Activare/Dezactivare opțiuni" + "message": "Comutare opțiuni" }, "organization": { "message": "Organizație", @@ -1256,10 +1256,10 @@ "description": "Text for a button that toggles the visibility of the window. Shows the window when it is hidden or hides the window if it is currently open." }, "hideToTray": { - "message": "Ascunde în tava sistem" + "message": "Ascundere în tava sistem" }, "alwaysOnTop": { - "message": "Mereu Deasupra", + "message": "Întotdeauna deasupra", "description": "Application window should always stay on top of other windows" }, "dateUpdated": { @@ -1271,21 +1271,21 @@ "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Parola s-a actualizat", + "message": "Parolă actualizată", "description": "ex. Date this password was updated" }, "exportVault": { - "message": "Export seif" + "message": "Export de seif" }, "fileFormat": { - "message": "Format fișier" + "message": "Format de fișier" }, "hCaptchaUrl": { "message": "Url-ul hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" }, "loadAccessibilityCookie": { - "message": "Încărcare Cookie de accesibilitate" + "message": "Încărcare cookie de accesibilitate" }, "registerAccessibilityUser": { "message": "Înregistrați-vă ca utilizator de accesibilitate la", @@ -1422,13 +1422,13 @@ "message": "Afișează întotdeauna un icon în bara de meniu." }, "hideToMenuBar": { - "message": "Ascunde în bara de meniu" + "message": "Ascundere în bara de meniu" }, "selectOneCollection": { "message": "Trebuie să selectați cel puțin o colecție." }, "premiumUpdated": { - "message": "Ați trecut la Premium." + "message": "Ați făcut upgrade la Premium." }, "restore": { "message": "Restabilire" @@ -1476,19 +1476,19 @@ "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { - "message": "Caută în coșul de gunoi" + "message": "Căutare în coș reciclare" }, "permanentlyDeleteItem": { - "message": "Ștergere definitivă a articolului" + "message": "Ștergere permanentă a articolului" }, "permanentlyDeleteItemConfirmation": { "message": "Sigur doriți să ștergeți definitiv acest articol?" }, "permanentlyDeletedItem": { - "message": "Articolul a fost șters definitiv" + "message": "Articol șters permanent" }, "restoreItem": { - "message": "Restabilire articol" + "message": "Restaurare articol" }, "restoreItemConfirmation": { "message": "Sigur doriți să restabiliți acest articol?" @@ -1506,7 +1506,7 @@ "message": "Confirmare acțiune la expirare" }, "enterpriseSingleSignOn": { - "message": "Conectare unică organizație (SSO)" + "message": "Autentificare unică întreprindere" }, "setMasterPassword": { "message": "Setare parolă principală" @@ -1515,10 +1515,10 @@ "message": "Pentru a finaliza conectarea cu SSO, vă rugăm să setați o parolă principală pentru a vă accesa și proteja seiful." }, "newMasterPass": { - "message": "Parolă principală nouă" + "message": "Parola principală nouă" }, "confirmNewMasterPass": { - "message": "Confirmați noua parolă principală" + "message": "Confirmare parolă principală nouă" }, "masterPasswordPolicyInEffect": { "message": "Una sau mai multe politici ale organizației necesită ca parola principală să îndeplinească următoarele cerințe:" @@ -1623,13 +1623,13 @@ "message": "Dacă nu ați inițiat această solicitare, nu o aprobați." }, "biometricsNotEnabledTitle": { - "message": "Biometria nu a fost activată" + "message": "Biometria nu este configurată" }, "biometricsNotEnabledDesc": { - "message": "Biometria browserului necesită activarea mai întâi a biometriei de pe desktop în setări." + "message": "Biometria browserului necesită ca mai întâi să fie configurată biometria desktopului în setări." }, "personalOwnershipSubmitError": { - "message": "Datorită unei politici pentru întreprinderi, vă este restricționată salvarea de elemente în seiful dvs. personal. Schimbați opțiunea de proprietate la o organizație și alegeți dintre colecțiile disponibile." + "message": "Din cauza unei politici a întreprinderii, nu vă puteți salva elemente în seiful individual. Schimbați opțiunea de proprietate la o organizație și alegeți din colecțiile disponibile." }, "hintEqualsPassword": { "message": "Indiciul dvs. de parolă nu poate fi același cu parola dvs." @@ -1711,11 +1711,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send creat", + "message": "Send adăugat", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send editat", + "message": "Send salvat", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { @@ -1730,7 +1730,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "Creare de Send", + "message": "Nou Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -1766,7 +1766,7 @@ "message": "Copiați în clipboard linkul pentru partajarea acestui Send după salvare." }, "sendDisabled": { - "message": "Send dezactivat", + "message": "Send înlăturat", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -1798,7 +1798,7 @@ "message": "Una sau mai multe politici organizaționale vă afectează opțiunile Send-ului." }, "emailVerificationRequired": { - "message": "Este necesară verificarea adresei de e-mail" + "message": "Verificare e-mail necesară" }, "emailVerificationRequiredDesc": { "message": "Trebuie să vă verificați e-mailul pentru a utiliza această caracteristică." @@ -1813,13 +1813,13 @@ "message": "Această acțiune este protejată. Pentru a continua, vă rugăm să reintroduceți parola principală pentru a vă verifica identitatea." }, "updatedMasterPassword": { - "message": "Parolă principală actualizată" + "message": "Parola principală actualizată" }, "updateMasterPassword": { "message": "Actualizare parolă principală" }, "updateMasterPasswordWarning": { - "message": "Parola dvs. principală a fost modificată recent de unul din administratorii organizației dvs. Pentru a accesa seiful, trebuie să o actualizați acum. Continuarea vă va deconecta de la sesiunea curentă, necesitând să vă reconectați. Sesiunile active de pe alte dispozitive pot continua să rămână active timp de până la o oră." + "message": "Parola principală a fost schimbată recent de către un administrator din organizație. Pentru a accesa seiful, trebuie să o actualizați acum. Continuarea vă va deconecta de la sesiunea curentă, cerându-vă să vă conectați din nou. Sesiunile active de pe alte dispozitive pot continua să rămână active timp de până la o oră." }, "hours": { "message": "Ore" @@ -1828,7 +1828,7 @@ "message": "Minute" }, "vaultTimeoutPolicyInEffect": { - "message": "Politicile organizației dvs vă afectează expirarea seifului. Timpul maxim permis de expirare a seifului este $HOURS$ oră (ore) și $MINUTES$ minut(e)", + "message": "Politicile organizației afectează timpul de expirare a seifului. Timpul maxim de expirare a seifului este de $HOURS$ oră(e) și $MINUTES$ minut(e)", "placeholders": { "hours": { "content": "$1", @@ -1844,13 +1844,13 @@ "message": "Timpul de expirare al seifului depășește restricțiile stabilite de organizația dvs." }, "resetPasswordPolicyAutoEnroll": { - "message": "Înregistrare automată" + "message": "Înscriere automată" }, "resetPasswordAutoEnrollInviteWarning": { "message": "Această organizație are o politică de întreprindere care vă va înregistra automat la resetarea parolei. Înregistrarea va permite administratorilor organizației să vă modifice parola principală." }, "vaultExportDisabled": { - "message": "Export de seif dezactivat" + "message": "Export de seif înlăturat" }, "personalVaultExportPolicyInEffect": { "message": "Una sau mai multe politici ale organizației vă împiedică să exportați seiful personal." @@ -1859,10 +1859,10 @@ "message": "Adăugare cont" }, "removeMasterPassword": { - "message": "Eliminare parolă principală" + "message": "Înlăturare parolă principală" }, "removedMasterPassword": { - "message": "Parolă principală eliminată." + "message": "Parola principală înlăturată" }, "convertOrganizationEncryptionDesc": { "message": "$ORGANIZATION$ folosește SSO cu un server de chei auto-găzduit. Membrii acestei organizații nu mai au nevoie de o parolă principală pentru autentificare.", @@ -1883,10 +1883,10 @@ "message": "Ați părăsit organizația." }, "ssoKeyConnectorError": { - "message": "Eroare de Conector Cheie: asigurați-vă că aveți conectorul Cheie disponibil și că funcționează corect." + "message": "Eroare de Conector Cheie: asigurați-vă că aveți conectorul cheie disponibil și că funcționează corect." }, "lockAllVaults": { - "message": "Blocare toate seifurile" + "message": "Blocare a tuturor seifurilor" }, "accountLimitReached": { "message": "Nu pot fi conectate mai mult de 5 conturi în același timp." @@ -1910,7 +1910,7 @@ } }, "switchAccount": { - "message": "Schimbați contul" + "message": "Comutare cont" }, "options": { "message": "Opțiuni" @@ -1919,10 +1919,10 @@ "message": "Sesiunea dvs. a expirat. Vă rugăm reveniți și încercați să vă autentificați din nou." }, "exportingPersonalVaultTitle": { - "message": "Exportarea seifului personal" + "message": "Exportul seifului individual" }, "exportingPersonalVaultDescription": { - "message": "Numai elementele personale din seif asociate cu $EMAIL$ vor fi exportate. Elementele seifului organizației nu vor fi incluse.", + "message": "Numai articolele de seif individuale asociate cu $EMAIL$ vor fi exportate. Articolele de seif ale organizației nu vor fi incluse.", "placeholders": { "email": { "content": "$1", @@ -1955,7 +1955,7 @@ "message": "Tip de nume de utilizator" }, "plusAddressedEmail": { - "message": "Plus e-mail adresat", + "message": "E-mail Plus adresat", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { @@ -1974,7 +1974,7 @@ "message": "Cuvânt aleatoriu" }, "websiteName": { - "message": "Numele site-ului web" + "message": "Nume website" }, "service": { "message": "Serviciu" @@ -1983,7 +1983,7 @@ "message": "Toate seifurile" }, "searchOrganization": { - "message": "Căutare Organizație" + "message": "Căutare organizație" }, "searchMyVault": { "message": "Căutare în seiful meu" @@ -2008,10 +2008,10 @@ "message": "Este necesar un abonament Premium" }, "organizationIsDisabled": { - "message": "Organizația este dezactivată." + "message": "Organizație suspendată" }, "disabledOrganizationFilterError": { - "message": "Articolele din Organizațiile dezactivate nu pot fi accesate. Contactați proprietarul Organizației pentru asistență." + "message": "Elementele din organizațiile suspendate nu pot fi accesate. Contactați proprietarul organizației pentru asistență." }, "neverLockWarning": { "message": "Sunteți sigur că doriți să folosiți opțiunea „Niciodată”? Setarea opțiunilor de blocare la „Niciodată” stochează cheia de criptare a seifului pe dispozitivul dumneavoastră. Dacă utilizați această opțiune, trebuie să vă asigurați că vă păstrați dispozitivul protejat corespunzător." diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 5cdb9b8d464..f75dc56f855 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -349,7 +349,7 @@ "message": "项目已添加" }, "editedItem": { - "message": "项目已编辑" + "message": "项目已保存" }, "deleteItem": { "message": "删除项目" @@ -361,10 +361,10 @@ "message": "删除附件" }, "deleteItemConfirmation": { - "message": "您确定要删除此项目吗?" + "message": "您确定要将其发送到回收站吗?" }, "deletedItem": { - "message": "项目已删除" + "message": "项目已发送到回收站" }, "overwritePasswordConfirmation": { "message": "您确定要覆盖当前密码吗?" @@ -376,7 +376,7 @@ "message": "您确定要覆盖当前用户名吗?" }, "noneFolder": { - "message": "没有文件夹", + "message": "无文件夹", "description": "This is the folder for uncategorized items" }, "addFolder": { @@ -395,7 +395,7 @@ "message": "复制 URI" }, "copyVerificationCodeTotp": { - "message": "复制验证码(TOTP)" + "message": "复制验证码 (TOTP)" }, "length": { "message": "长度" @@ -429,10 +429,10 @@ "message": "关闭" }, "minNumbers": { - "message": "最小位数" + "message": "数字最少个数" }, "minSpecial": { - "message": "最少特殊符号", + "message": "符号最少个数", "description": "Minimum Special Characters" }, "ambiguous": { @@ -461,13 +461,13 @@ "message": "您确定要删除此附件吗?" }, "attachmentSaved": { - "message": "附件已保存。" + "message": "附件已保存" }, "file": { "message": "文件" }, "selectFile": { - "message": "选择一个文件。" + "message": "选择一个文件" }, "maxFileSize": { "message": "文件最大为 500 MB。" @@ -476,22 +476,22 @@ "message": "在您更新加密密钥前,您不能使用此功能。" }, "editedFolder": { - "message": "已编辑文件夹" + "message": "文件夹已保存" }, "addedFolder": { - "message": "已添加文件夹" + "message": "文件夹已添加" }, "deleteFolderConfirmation": { "message": "您确定要删除此文件夹吗?" }, "deletedFolder": { - "message": "已删除文件夹" + "message": "文件夹已删除" }, "loginOrCreateNewAccount": { "message": "登录或者创建一个账户来访问您的安全密码库。" }, "createAccount": { - "message": "创建账号" + "message": "创建账户" }, "logIn": { "message": "登录" @@ -509,10 +509,10 @@ "message": "主密码提示可以在你忘记密码时帮你回忆。" }, "reTypeMasterPass": { - "message": "重新输入主密码" + "message": "再次输入主密码" }, "masterPassHint": { - "message": "主密码提示 (可选)" + "message": "主密码提示(可选)" }, "settings": { "message": "设置" @@ -521,7 +521,7 @@ "message": "密码提示" }, "enterEmailToGetHint": { - "message": "请输入您账户的电子邮件地址来接收密码提示。" + "message": "请输入您账户的电子邮件地址来接收主密码提示。" }, "getMasterPasswordHint": { "message": "获取主密码提示" @@ -620,7 +620,7 @@ "message": "将您的安全钥匙插入计算机的 USB 端口。如果它有按钮,请按下它。" }, "recoveryCodeDesc": { - "message": "您丢失了所有的两步登录方式?使用您的恢复代码来为您的账户禁用所有两步登录方式。" + "message": "无法访问您所有的双重身份提供程序吗?请使用您的恢复代码来停用您账户中所有的双重身份提供程序。" }, "recoveryCodeTitle": { "message": "恢复代码" @@ -633,7 +633,7 @@ "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." }, "yubiKeyTitle": { - "message": "YubiKey NEO 安全密钥" + "message": "YubiKey OTP 安全钥匙" }, "yubiKeyDesc": { "message": "使用 YubiKey 来访问您的账户。支持 YubiKey 4、4 Nano、4C 以及 NEO 设备。" @@ -650,7 +650,7 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "使用任何具有 WebAuthn 功能的安全密钥来访问您的账户。" + "message": "使用任何 WebAuthn 兼容的安全钥匙访问您的帐户。" }, "emailTitle": { "message": "电子邮件地址" @@ -662,7 +662,7 @@ "message": "登录不可用" }, "noTwoStepProviders": { - "message": "此账户已启用两步登录,但此设备不支持任何已配置的两步登录方法。" + "message": "此账户已设置两步登录,但此设备不支持任何已配置的两步登录提供程序。" }, "noTwoStepProviders2": { "message": "请添加能更好支持跨设备使用的提供程序(比如验证器应用)。" @@ -671,7 +671,7 @@ "message": "两步登录选项" }, "selfHostedEnvironment": { - "message": "自我托管环境" + "message": "自托管环境" }, "selfHostedEnvironmentFooter": { "message": "指定您本地托管的 Bitwarden 安装的基础 URL。" @@ -701,7 +701,7 @@ "message": "图标服务器 URL" }, "environmentSaved": { - "message": "各环境 URL 已保存。" + "message": "环境 URL 已保存" }, "ok": { "message": "确定" @@ -752,7 +752,7 @@ "message": "正在加载..." }, "lockVault": { - "message": "解锁密码库" + "message": "锁定密码库" }, "passwordGenerator": { "message": "密码生成器" @@ -827,7 +827,7 @@ "message": "无效的主密码" }, "twoStepLoginConfirmation": { - "message": "两步登录要求您从其他设备(例如安全钥匙、验证器应用、短信、电话或者电子邮件)来验证你的登录,这能使您的账户更加安全。两步登录可以在 bitwarden.com 网页版密码库启用。现在要访问吗?" + "message": "两步登录要求您从其他设备(例如安全钥匙、验证器应用、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码中设置。您现在要访问这个网站吗?" }, "twoStepLogin": { "message": "两步登录" @@ -928,7 +928,7 @@ "message": "关闭窗口后,改为在菜单栏中显示一个图标。" }, "enableTray": { - "message": "启用托盘图标" + "message": "显示托盘图标" }, "enableTrayDesc": { "message": "始终在系统托盘中显示一个图标。" @@ -958,7 +958,7 @@ "message": "即使已最小化到菜单栏,仍在 Dock 中显示 Bitwarden 图标。" }, "confirmTrayTitle": { - "message": "确认禁用托盘" + "message": "确认隐藏到托盘" }, "confirmTrayDesc": { "message": "禁用此设置也将禁用其他与托盘相关的设置。" @@ -988,7 +988,7 @@ "description": "Copy to clipboard" }, "checkForUpdates": { - "message": "检查更新" + "message": "检查更新…" }, "version": { "message": "版本 $VERSION_NUM$", @@ -1027,7 +1027,7 @@ "message": "当前没有可用的更新。您使用的是最新版本。" }, "updateError": { - "message": "更新错误" + "message": "更新时出错" }, "unknown": { "message": "未知" @@ -1447,7 +1447,7 @@ "message": "隐私条款" }, "unsavedChangesConfirmation": { - "message": "您确定要离开吗?如果您现在离开,您当前的信息不会被保存。" + "message": "您确定要退出吗?如果您现在退出,您当前的信息不会被保存。" }, "unsavedChangesTitle": { "message": "更改未保存" @@ -1485,7 +1485,7 @@ "message": "您确定要永久删除此项目吗?" }, "permanentlyDeletedItem": { - "message": "已永久删除项目" + "message": "项目已永久删除" }, "restoreItem": { "message": "恢复项目" @@ -1506,7 +1506,7 @@ "message": "超时动作确认" }, "enterpriseSingleSignOn": { - "message": "企业单点登录(SSO)" + "message": "企业单点登录" }, "setMasterPassword": { "message": "设置主密码" @@ -1515,10 +1515,10 @@ "message": "要完成 SSO 登陆配置,请设置一个主密码以访问和保护您的密码库。" }, "newMasterPass": { - "message": "新的主密码" + "message": "新主密码" }, "confirmNewMasterPass": { - "message": "确认新的主密码" + "message": "确认新主密码" }, "masterPasswordPolicyInEffect": { "message": "一个或多个组织策略要求您的主密码满足下列要求:" @@ -1623,13 +1623,13 @@ "message": "如果您没有发起过此请求,请不要批准。" }, "biometricsNotEnabledTitle": { - "message": "生物识别未启用" + "message": "生物识别未设置" }, "biometricsNotEnabledDesc": { - "message": "需要先在桌面应用程序的设置中启用生物识别,才能使用浏览器中的生物识别。" + "message": "需要先在桌面应用程序的设置中设置生物识别,才能使用浏览器中的生物识别。" }, "personalOwnershipSubmitError": { - "message": "由于企业策略,您被限制为保存项目到您的个人密码库。将所有权选项更改为组织,并从可用的集合中选择。" + "message": "由于企业策略,您被限制为保存项目到您的个人密码库。将所有权选项更改为组织,然后从可用的集合中选择。" }, "hintEqualsPassword": { "message": "您的密码提示不能与您的密码相同。" @@ -1656,7 +1656,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "myVault": { - "message": "密码库" + "message": "我的密码库" }, "text": { "message": "文本" @@ -1669,7 +1669,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { - "message": "过期日期" + "message": "到期日期" }, "expirationDateDesc": { "message": "设置后,对此 Send 的访问将在指定的日期和时间后过期。", @@ -1687,7 +1687,7 @@ "message": "当前访问次数" }, "disableSend": { - "message": "禁用此 Send 以阻止任何人访问它。", + "message": "停用此 Send 则任何人无法访问它。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendPasswordDesc": { @@ -1707,7 +1707,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "textHiddenByDefault": { - "message": "访问 Send 时,默认隐藏文字内容", + "message": "访问此 Send 时,默认隐藏文本内容", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { @@ -1819,7 +1819,7 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "hours": { "message": "小时" @@ -1862,7 +1862,7 @@ "message": "移除主密码" }, "removedMasterPassword": { - "message": "主密码已移除。" + "message": "主密码已移除" }, "convertOrganizationEncryptionDesc": { "message": "$ORGANIZATION$ 使用自托管密钥服务器 SSO。这个组织的成员登录时将不再需要主密码。", @@ -1874,13 +1874,13 @@ } }, "leaveOrganization": { - "message": "离开组织" + "message": "退出组织" }, "leaveOrganizationConfirmation": { - "message": "您确定要离开这个组织吗?" + "message": "您确定要退出这个组织吗?" }, "leftOrganization": { - "message": "您已经离开该组织。" + "message": "您已经退出该组织。" }, "ssoKeyConnectorError": { "message": "Key Connector 错误:请确保 Key Connector 可用且工作正常。" @@ -1922,7 +1922,7 @@ "message": "正在导出个人密码库" }, "exportingPersonalVaultDescription": { - "message": "仅会导出与 $EMAIL$ 关联的个人密码库。组织密码库的项目不会导出。", + "message": "仅会导出与 $EMAIL$ 关联的个人密码库项目。组织密码库的项目不会导出。", "placeholders": { "email": { "content": "$1", @@ -1974,7 +1974,7 @@ "message": "随机单词" }, "websiteName": { - "message": "网站名" + "message": "网站名称" }, "service": { "message": "服务" @@ -2008,10 +2008,10 @@ "message": "需要高级版订阅" }, "organizationIsDisabled": { - "message": "组织已被禁用。" + "message": "组织已暂停" }, "disabledOrganizationFilterError": { - "message": "无法访问已禁用组织中的项目。请联系您的组织所有者获取协助。" + "message": "无法访问已暂停组织中的项目。请联系您的组织所有者获取帮助。" }, "neverLockWarning": { "message": "确定要使用「从不」选项吗?将锁定选项设置为「从不」会将密码库的加密密钥存储在您的设备上。如果使用此选项,应确保您的设备得到妥善的保护。" From acef1584e2d238d928fed7a3ef71498ae293e48a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Oct 2022 02:15:58 +0200 Subject: [PATCH 05/83] Autosync the updated translations (#3915) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/be/messages.json | 26 +++++----- apps/browser/src/_locales/de/messages.json | 2 +- apps/browser/src/_locales/en_GB/messages.json | 2 +- apps/browser/src/_locales/en_IN/messages.json | 2 +- apps/browser/src/_locales/fi/messages.json | 18 +++---- apps/browser/src/_locales/nl/messages.json | 8 ++-- apps/browser/src/_locales/pl/messages.json | 48 +++++++++---------- apps/browser/src/_locales/zh_CN/messages.json | 28 +++++------ apps/browser/store/locales/pt_BR/copy.resx | 30 ++++++------ 9 files changed, 82 insertions(+), 82 deletions(-) diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 4188db0ba8f..f44e4c3a5f4 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -497,7 +497,7 @@ "message": "Папка адрэдагавана" }, "deleteFolderConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэту папку?" + "message": "Вы сапраўды хочаце выдаліць гэту папку?" }, "deletedFolder": { "message": "Папка выдалена" @@ -540,7 +540,7 @@ "message": "Элемент адрэдагаваны" }, "deleteItemConfirmation": { - "message": "Вы ўпэўнены, што хочаце адправіць гэты элемент у сметніцу?" + "message": "Вы сапраўды хочаце адправіць гэты элемент у сметніцу?" }, "deletedItem": { "message": "Элемент адпраўлены ў сметніцу" @@ -549,13 +549,13 @@ "message": "Перазапісаць пароль" }, "overwritePasswordConfirmation": { - "message": "Вы ўпэўнены, што хочаце перазапісаць бягучы пароль?" + "message": "Вы сапраўды хочаце перазапісаць бягучы пароль?" }, "overwriteUsername": { "message": "Перазапісаць імя карыстальніка" }, "overwriteUsernameConfirmation": { - "message": "Вы ўпэўнены, што хочаце перазапісаць бягучае імя карыстальніка?" + "message": "Вы сапраўды хочаце перазапісаць бягучае імя карыстальніка?" }, "searchFolder": { "message": "Пошук у папцы" @@ -720,7 +720,7 @@ "message": "Выдаліць далучэнне" }, "deleteAttachmentConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэта далучэнне?" + "message": "Вы сапраўды хочаце выдаліць гэта далучэнне?" }, "deletedAttachment": { "message": "Далучэнне выдалена" @@ -1320,7 +1320,7 @@ "description": "ex. Date this password was updated" }, "neverLockWarning": { - "message": "Вы ўпэўнены, што хочаце адключыць блакіроўку сховішча? Прызначыўшы параметр блакіравання \"Ніколі\", ключ шыфравання будзе захоўвацца на вашай прыладзе. Калі вы выкарыстоўваеце гэты параметр, вы павінны быць упэўнены ў тым, што ваша прылада надзейна абаронена." + "message": "Вы сапраўды хочаце адключыць блакіроўку сховішча? Прызначыўшы параметр блакіравання \"Ніколі\", ключ шыфравання будзе захоўвацца на вашай прыладзе. Калі вы выкарыстоўваеце гэты параметр, вы павінны быць упэўнены ў тым, што ваша прылада надзейна абаронена." }, "noOrganizationsList": { "message": "Вы не з'яўляецеся членам якой-небудзь арганізацыі. Арганізацыі дазваляюць бяспечна абменьвацца элементамі з іншымі карыстальнікамі." @@ -1350,7 +1350,7 @@ "message": "Ненадзейны асноўны пароль" }, "weakMasterPasswordDesc": { - "message": "Асноўны пароль, які вы выбралі з'яўляецца ненадзейным. Для належнай абароны ўліковага запісу Bitwarden, вы павінны выкарыстоўваць надзейны асноўны пароль (або парольную фразу). Вы ўпэўнены, што хочаце выкарыстоўваць гэты асноўны пароль?" + "message": "Асноўны пароль, які вы выбралі з'яўляецца ненадзейным. Для належнай абароны ўліковага запісу Bitwarden, вы павінны выкарыстоўваць надзейны асноўны пароль (або парольную фразу). Вы сапраўды хочаце выкарыстоўваць гэты асноўны пароль?" }, "pin": { "message": "PIN-код", @@ -1410,7 +1410,7 @@ "message": "Выдаліць назаўсёды" }, "permanentlyDeleteItemConfirmation": { - "message": "Вы ўпэўнены, што хочаце назаўсёды выдаліць гэты элемент?" + "message": "Вы сапраўды хочаце назаўсёды выдаліць гэты элемент?" }, "permanentlyDeletedItem": { "message": "Элемент выдалены назаўсёды" @@ -1419,13 +1419,13 @@ "message": "Аднавіць элемент" }, "restoreItemConfirmation": { - "message": "Вы ўпэўнены, што хочаце аднавіць гэты элемент?" + "message": "Вы сапраўды хочаце аднавіць гэты элемент?" }, "restoredItem": { "message": "Элемент адноўлены" }, "vaultTimeoutLogOutConfirmation": { - "message": "Выхад з сістэмы скасуе ўсе магчымасці доступу да сховішча і запатрабуе аўтэнтыфікацыю праз інтэрнэт пасля завяршэння часу чакання. Вы ўпэўнены, што хочаце выкарыстоўваць гэты параметр?" + "message": "Выхад з сістэмы скасуе ўсе магчымасці доступу да сховішча і запатрабуе аўтэнтыфікацыю праз інтэрнэт пасля завяршэння часу чакання. Вы сапраўды хочаце выкарыстоўваць гэты параметр?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Пацвярджэнне дзеяння часу чакання" @@ -1643,14 +1643,14 @@ "message": "Адключана" }, "removePasswordConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць пароль?" + "message": "Вы сапраўды хочаце выдаліць пароль?" }, "deleteSend": { "message": "Выдаліць Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэты Send?", + "message": "Вы сапраўды хочаце выдаліць гэты Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -1887,7 +1887,7 @@ "message": "Асноўны пароль выдалены." }, "leaveOrganizationConfirmation": { - "message": "Вы ўпэўнены, што хочаце выйсці з гэтай арганізацыі?" + "message": "Вы сапраўды хочаце выйсці з гэтай арганізацыі?" }, "leftOrganization": { "message": "Вы пакінулі арганізацыю." diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index ba5b31a54df..e1f067d12f7 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -342,7 +342,7 @@ "message": "Identität verifizieren" }, "yourVaultIsLocked": { - "message": "Dein Tresor ist gesperrt. Überprüfe dein Master-Passwort um fortzufahren." + "message": "Your vault is locked. Verify your identity to continue." }, "unlock": { "message": "Entsperren" diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 379f6e0da2d..fdd59ba9ac9 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -342,7 +342,7 @@ "message": "Verify Identity" }, "yourVaultIsLocked": { - "message": "Your vault is locked. Verify your master password to continue." + "message": "Your vault is locked. Verify your identity to continue." }, "unlock": { "message": "Unlock" diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 10f2ccf37c5..d0b47266ddd 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -342,7 +342,7 @@ "message": "Verify Identity" }, "yourVaultIsLocked": { - "message": "Your vault is locked. Verify your master password to continue." + "message": "Your vault is locked. Verify your identity to continue." }, "unlock": { "message": "Unlock" diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index c13047537c3..45f3e2e94ce 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -134,7 +134,7 @@ "message": "Lähetä koodi" }, "codeSent": { - "message": "Koodi lähetetty" + "message": "Koodi lähetettiin" }, "verificationCode": { "message": "Todennuskoodi" @@ -765,7 +765,7 @@ "message": "Et ole tällä hetkellä Premium-jäsen." }, "premiumSignUpAndGet": { - "message": "Ryhdy Premium-jäseneksi saadaksesi:" + "message": "Liity Premium-jäseneksi saadaksesi:" }, "ppremiumSignUpStorage": { "message": "1 Gt salattua tallennustilaa tiedostoliitteille." @@ -1308,7 +1308,7 @@ "message": "Oletus" }, "dateUpdated": { - "message": "Päivitetty", + "message": "Päivitettiin", "description": "ex. Date this item was updated" }, "dateCreated": { @@ -1316,7 +1316,7 @@ "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Salasana päivitetty", + "message": "Salasana päivitettiin", "description": "ex. Date this password was updated" }, "neverLockWarning": { @@ -1563,7 +1563,7 @@ "message": "Toimintoa ei voi suorittaa sivupalkissa, yritä toimintoa uudelleen ponnahdusvalikossa tai ponnahdusikkunassa." }, "personalOwnershipSubmitError": { - "message": "Yrityksen asettaman käytännön johdosta et voi tallentaa kohteita henkilökohtaiseen holviisi. Muuta omistusasetus organisaatiolle ja valitse käytettävissä olevista kokoelmista." + "message": "Yrityskäytännön johdosta kohteiden tallennus henkilökohtaiseen holviin ei ole mahdollista. Muuta omistusasetus organisaatiolle ja valitse käytettävissä olevista kokoelmista." }, "personalOwnershipPolicyInEffect": { "message": "Organisaatiokäytäntö vaikuttaa omistajuusvalintoihisi." @@ -1619,7 +1619,7 @@ "message": "Salasanasuojattu" }, "copySendLink": { - "message": "Kopioi Sendin linkki", + "message": "Kopioi Send-linkki", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { @@ -1742,7 +1742,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Yrityksen käytännön vuoksi voit poistaa vain olemassa olevan Sendin.", + "message": "Yrityskäytännön vuoksi voit poistaa vain olemassa olevan Sendin.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { @@ -1814,7 +1814,7 @@ "message": "Sinun on vahvistettava sähköpostiosoitteesi käyttääksesi ominaisuutta. Voit vahvistaa osoitteesi verkkoholvissa." }, "updatedMasterPassword": { - "message": "Pääsalasana vaihdettiin" + "message": "Pääsalasana päivitettiin" }, "updateMasterPassword": { "message": "Vaihda pääsalasana" @@ -1902,7 +1902,7 @@ "message": "Henkilökohtaisen holvin vienti" }, "exportingPersonalVaultDescription": { - "message": "Vain tunnukselle $EMAIL$ liitetyt henkilökohtaisen holvin kohteet viedään. Organisaation kohteet eivät sisälly tähän.", + "message": "Vain tunnukseen $EMAIL$ liitetyt henkilökohtaisen holvin kohteet viedään. Organisaation kohteet eivät sisälly tähän.", "placeholders": { "email": { "content": "$1", diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 5ae8dd4911a..8f8b080f26c 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -20,7 +20,7 @@ "message": "Inloggen" }, "enterpriseSingleSignOn": { - "message": "Enterprise Single Sign-On" + "message": "Single sign-on voor bedrijven" }, "cancel": { "message": "Annuleren" @@ -330,7 +330,7 @@ "message": "Overig" }, "rateExtension": { - "message": "Extensie beoordelen" + "message": "Deze extensie beoordelen" }, "rateExtensionDesc": { "message": "Je kunt ons helpen door een goede recensie achter te laten!" @@ -1312,7 +1312,7 @@ "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "Aangemaakt", "description": "ex. Date this item was created" }, "datePasswordUpdated": { @@ -2009,7 +2009,7 @@ "message": "Zelfgehost" }, "thirdParty": { - "message": "Third-party" + "message": "van derden" }, "thirdPartyServerMessage": { "message": "Verbonden met server implementatie van derden, $SERVERNAME$. Reproduceer bugs via de officiële server, of meld ze bij het serverproject.", diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index c4682d106ed..c3578693498 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -32,7 +32,7 @@ "message": "Wyślij" }, "emailAddress": { - "message": "Adres email" + "message": "Adres e-mail" }, "masterPass": { "message": "Hasło główne" @@ -56,7 +56,7 @@ "message": "Sejf" }, "myVault": { - "message": "Mój Sejf" + "message": "Mój sejf" }, "allVaults": { "message": "Wszystkie sejfy" @@ -68,25 +68,25 @@ "message": "Ustawienia" }, "currentTab": { - "message": "Bieżąca karta" + "message": "Obecna karta" }, "copyPassword": { - "message": "Skopiuj hasło" + "message": "Kopiuj hasło" }, "copyNote": { - "message": "Skopiuj notatkę" + "message": "Kopiuj notatkę" }, "copyUri": { "message": "Kopiuj URI" }, "copyUsername": { - "message": "Skopiuj nazwę użytkownika" + "message": "Kopiuj nazwę użytkownika" }, "copyNumber": { - "message": "Skopiuj numer" + "message": "Kopiuj numer" }, "copySecurityCode": { - "message": "Skopiuj kod zabezpieczający" + "message": "Kopiuj kod zabezpieczający" }, "autoFill": { "message": "Autouzupełnianie" @@ -199,7 +199,7 @@ "message": "Synchronizacja" }, "syncVaultNow": { - "message": "Rozpocznij synchronizację sejfu" + "message": "Synchronizuj sejf" }, "lastSync": { "message": "Ostatnia synchronizacja:" @@ -224,10 +224,10 @@ "message": "Wybierz" }, "generatePassword": { - "message": "Generuj hasło" + "message": "Wygeneruj hasło" }, "regeneratePassword": { - "message": "Wygeneruj hasło ponownie" + "message": "Wygeneruj ponownie hasło" }, "options": { "message": "Opcje" @@ -494,7 +494,7 @@ "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?" }, "editedFolder": { - "message": "Folder został zaktualizowany" + "message": "Folder został zapisany" }, "deleteFolderConfirmation": { "message": "Czy na pewno chcesz usunąć ten folder?" @@ -537,7 +537,7 @@ "message": "Element został dodany" }, "editedItem": { - "message": "Element został zaktualizowany" + "message": "Element został zapisany" }, "deleteItemConfirmation": { "message": "Czy na pewno chcesz to usunąć?" @@ -726,19 +726,19 @@ "message": "Załącznik został usunięty" }, "newAttachment": { - "message": "Dodaj załącznik" + "message": "Dodaj nowy załącznik" }, "noAttachments": { "message": "Brak załączników." }, "attachmentSaved": { - "message": "Załącznik został zapisany." + "message": "Załącznik został zapisany" }, "file": { "message": "Plik" }, "selectFile": { - "message": "Wybierz plik." + "message": "Wybierz plik" }, "maxFileSize": { "message": "Maksymalny rozmiar pliku to 500 MB." @@ -810,7 +810,7 @@ "message": "Odświeżanie zostało zakończone" }, "enableAutoTotpCopy": { - "message": "Kopiuj TOTP automatycznie" + "message": "Kopiuj kod TOTP automatycznie" }, "disableAutoTotpCopyDesc": { "message": "Jeśli dane logowania posiadają dołączony klucz uwierzytelniający TOTP, kod weryfikacyjny jest automatycznie kopiowany do schowka przy każdym autouzupełnianiu danych logowania." @@ -951,7 +951,7 @@ "message": "Adres URL serwera ikon" }, "environmentSaved": { - "message": "Adresy URL środowiska zostały zapisane." + "message": "Adresy URL środowiska zostały zapisane" }, "enableAutoFillOnPageLoad": { "message": "Włącz autouzupełnianie po załadowaniu strony" @@ -1199,7 +1199,7 @@ "message": "Tożsamość" }, "passwordHistory": { - "message": "Historia haseł" + "message": "Historia hasła" }, "back": { "message": "Powrót" @@ -1731,14 +1731,14 @@ "message": "Obecna liczba dostępów" }, "createSend": { - "message": "Utwórz nową wysyłkę", + "message": "Nowa wysyłka", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { "message": "Nowe hasło" }, "sendDisabled": { - "message": "Wysyłka została wyłączona", + "message": "Wysyłka została usunięta", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -1750,7 +1750,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Wysyłka została zaktualizowana", + "message": "Wysyłka została zapisana", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { @@ -1814,7 +1814,7 @@ "message": "Musisz zweryfikować adres e-mail, aby korzystać z tej funkcji. Adres możesz zweryfikować w sejfie internetowym." }, "updatedMasterPassword": { - "message": "Zaktualizowane hasło główne" + "message": "Hasło główne zostało zaktualizowane" }, "updateMasterPassword": { "message": "Zaktualizuj hasło główne" @@ -1884,7 +1884,7 @@ "message": "Usuń hasło główne" }, "removedMasterPassword": { - "message": "Hasło główne zostało usunięte." + "message": "Hasło główne zostało usunięte" }, "leaveOrganizationConfirmation": { "message": "Czy na pewno chcesz opuścić tę organizację?" diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index e14469c8646..12de271df68 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -7,14 +7,14 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Bitwarden 是一个安全且免费的跨平台密码管理器。", + "message": "安全且免费的跨平台密码管理器。", "description": "Extension description" }, "loginOrCreateNewAccount": { - "message": "登录或新建一个账户来访问你的密码库。" + "message": "登录或者创建一个账户来访问您的安全密码库。" }, "createAccount": { - "message": "新建账户" + "message": "创建账户" }, "login": { "message": "登录" @@ -119,7 +119,7 @@ "message": "密码提示" }, "enterEmailToGetHint": { - "message": "请输入您账户的 Email 地址来接收主密码提示。" + "message": "请输入您账户的电子邮件地址来接收主密码提示。" }, "getMasterPasswordHint": { "message": "获取主密码提示" @@ -540,7 +540,7 @@ "message": "项目已保存" }, "deleteItemConfirmation": { - "message": "您确定要删除此项目吗?" + "message": "您确定要将其发送到回收站吗?" }, "deletedItem": { "message": "项目已发送到回收站" @@ -882,7 +882,7 @@ "message": "两步登录选项" }, "recoveryCodeDesc": { - "message": "无法访问您所有的双重身份提供程序吗?请使用您的恢复代码来禁用您账户中所有的双重身份提供程序。" + "message": "无法访问您所有的双重身份提供程序吗?请使用您的恢复代码来停用您账户中所有的双重身份提供程序。" }, "recoveryCodeTitle": { "message": "恢复代码" @@ -912,7 +912,7 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "使用任何具有 WebAuthn 功能的安全钥匙访问您的账户。" + "message": "使用任何 WebAuthn 兼容的安全钥匙访问您的帐户。" }, "emailTitle": { "message": "电子邮件" @@ -1713,7 +1713,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisableDesc": { - "message": "禁用此 Send 以阻止任何人访问它。", + "message": "停用此 Send 则任何人无法访问它。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendShareDesc": { @@ -1731,14 +1731,14 @@ "message": "当前访问次数" }, "createSend": { - "message": "新建 Send", + "message": "创建 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { "message": "新建密码" }, "sendDisabled": { - "message": "Send 已移除", + "message": "Send 已禁用", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -1887,10 +1887,10 @@ "message": "主密码已移除" }, "leaveOrganizationConfirmation": { - "message": "您确定要离开该组织吗?" + "message": "您确定要退出该组织吗?" }, "leftOrganization": { - "message": "您已经离开该组织。" + "message": "您已经退出该组织。" }, "toggleCharacterCount": { "message": "字符计数开关" @@ -1976,10 +1976,10 @@ "message": "需要高级版订阅" }, "organizationIsDisabled": { - "message": "组织已被挂起。" + "message": "组织已暂停。" }, "disabledOrganizationFilterError": { - "message": "无法访问已被挂起的组织中的项目。请联系您的组织所有者获取帮助。" + "message": "无法访问已暂停组织中的项目。请联系您的组织所有者获取帮助。" }, "cardBrandMir": { "message": "Mir" diff --git a/apps/browser/store/locales/pt_BR/copy.resx b/apps/browser/store/locales/pt_BR/copy.resx index 90a2959e807..48111fa814f 100644 --- a/apps/browser/store/locales/pt_BR/copy.resx +++ b/apps/browser/store/locales/pt_BR/copy.resx @@ -124,31 +124,31 @@ Um gerenciador de senhas gratuito e seguro para todos os seus dispositivos - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Bitwarden, Inc. é a empresa matriz da 8bit Solutions LLC. -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +NOMEADA MELHOR GERENCIADORA DE SENHAS PELA VERGE, U.S. NEWS & WORLD REPORT, CNET, E MUITO MAIS. -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. +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. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +Gere senhas fortes, únicas e aleatórias com base nos requisitos de segurança para cada site que você frequenta. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +A Bitwarden Send transmite rapidamente informações criptografadas --- arquivos e texto em formato de placa -- diretamente para qualquer pessoa. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Bitwarden oferece equipes e planos empresariais para empresas para que você possa compartilhar senhas com colegas com segurança. -Why Choose Bitwarden: +Por que escolher 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. +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. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +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. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Traduções globais +As traduções Bitwarden existem em 40 idiomas e estão crescendo, graças à nossa comunidade global. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +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. From 534e1c804ba8814ee7cd43babf35ff1efd8c294c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Oct 2022 02:34:21 +0200 Subject: [PATCH 06/83] Autosync the updated translations (#3916) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 7 - apps/web/src/locales/ar/messages.json | 7 - apps/web/src/locales/az/messages.json | 7 - apps/web/src/locales/be/messages.json | 73 +++--- apps/web/src/locales/bg/messages.json | 7 - apps/web/src/locales/bn/messages.json | 7 - apps/web/src/locales/bs/messages.json | 7 - apps/web/src/locales/ca/messages.json | 7 - apps/web/src/locales/cs/messages.json | 7 - apps/web/src/locales/da/messages.json | 7 - apps/web/src/locales/de/messages.json | 7 - apps/web/src/locales/el/messages.json | 7 - apps/web/src/locales/en_GB/messages.json | 7 - apps/web/src/locales/en_IN/messages.json | 7 - apps/web/src/locales/eo/messages.json | 7 - apps/web/src/locales/es/messages.json | 7 - apps/web/src/locales/et/messages.json | 7 - apps/web/src/locales/eu/messages.json | 7 - apps/web/src/locales/fi/messages.json | 249 ++++++++++---------- apps/web/src/locales/fil/messages.json | 7 - apps/web/src/locales/fr/messages.json | 7 - apps/web/src/locales/he/messages.json | 7 - apps/web/src/locales/hi/messages.json | 7 - apps/web/src/locales/hr/messages.json | 7 - apps/web/src/locales/hu/messages.json | 7 - apps/web/src/locales/id/messages.json | 7 - apps/web/src/locales/it/messages.json | 7 - apps/web/src/locales/ja/messages.json | 7 - apps/web/src/locales/ka/messages.json | 7 - apps/web/src/locales/km/messages.json | 7 - apps/web/src/locales/kn/messages.json | 7 - apps/web/src/locales/ko/messages.json | 7 - apps/web/src/locales/lv/messages.json | 7 - apps/web/src/locales/ml/messages.json | 7 - apps/web/src/locales/nb/messages.json | 7 - apps/web/src/locales/nl/messages.json | 7 - apps/web/src/locales/nn/messages.json | 7 - apps/web/src/locales/pl/messages.json | 125 +++++----- apps/web/src/locales/pt_BR/messages.json | 7 - apps/web/src/locales/pt_PT/messages.json | 7 - apps/web/src/locales/ro/messages.json | 195 ++++++++-------- apps/web/src/locales/ru/messages.json | 17 +- apps/web/src/locales/si/messages.json | 7 - apps/web/src/locales/sk/messages.json | 7 - apps/web/src/locales/sl/messages.json | 7 - apps/web/src/locales/sr/messages.json | 7 - apps/web/src/locales/sr_CS/messages.json | 7 - apps/web/src/locales/sv/messages.json | 11 +- apps/web/src/locales/tr/messages.json | 7 - apps/web/src/locales/uk/messages.json | 173 +++++++------- apps/web/src/locales/vi/messages.json | 7 - apps/web/src/locales/zh_CN/messages.json | 279 +++++++++++------------ apps/web/src/locales/zh_TW/messages.json | 7 - 53 files changed, 533 insertions(+), 904 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 23f4c497619..169fc28e336 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Aktiveer Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 659152d879f..a6490e142f8 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "تمكين الجرافاتار", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "استخدام صور الأفاتار المحملة من gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 5a1880fd4fe..0d09a001d21 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Hər girişin yanında tanına bilən təsvir göstər." }, - "enableGravatars": { - "message": "\"Gravatar\"ı fəallaşdırın", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "gravatar.com saytından yüklənilən avatarları istifadə edin." - }, "enableFullWidth": { "message": "Tam en görünüşünü fəallaşdır", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 9fbe5ef78b9..b8198b6f6bd 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -464,7 +464,7 @@ "message": "Далучэнне выдалена" }, "deleteAttachmentConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэта далучэнне?" + "message": "Вы сапраўды хочаце выдаліць гэта далучэнне?" }, "attachmentSaved": { "message": "Далучэнне захавана." @@ -519,7 +519,7 @@ "message": "Выдаліць далучэнне" }, "deleteItemConfirmation": { - "message": "Вы ўпэўнены, што хочаце адправіць гэты элемент у сметніцу?" + "message": "Вы сапраўды хочаце адправіць гэты элемент у сметніцу?" }, "deletedItem": { "message": "Элемент адпраўлены ў сметніцу" @@ -531,7 +531,7 @@ "message": "Перамешчаныя элементы" }, "overwritePasswordConfirmation": { - "message": "Вы ўпэўнены, што хочаце перазапісаць бягучы пароль?" + "message": "Вы сапраўды хочаце перазапісаць бягучы пароль?" }, "editedFolder": { "message": "Папка адрэдагавана" @@ -540,7 +540,7 @@ "message": "Папка дададзена" }, "deleteFolderConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэту папку?" + "message": "Вы сапраўды хочаце выдаліць гэту папку?" }, "deletedFolder": { "message": "Папка выдалена" @@ -552,7 +552,7 @@ "message": "Тэрмін дзеяння вашага сеансу завяршыўся." }, "logOutConfirmation": { - "message": "Вы ўпэўнены, што хочаце выйсці?" + "message": "Вы сапраўды хочаце выйсці?" }, "logOut": { "message": "Выйсці" @@ -844,7 +844,7 @@ "message": "Рэдагуйце калекцыі, з якімі гэты элемент знаходзіцца ў агульным доступе. Толькі карыстальнікі арганізацыі з доступам да гэтых калекцый змогуць бачыць гэты элемент." }, "deleteSelectedItemsDesc": { - "message": "Вы выбралі наступную колькасць элементаў для выдалення: $COUNT$ шт. Вы ўпэўнены, што хочаце выдаліць іх?", + "message": "Вы выбралі наступную колькасць элементаў для выдалення: $COUNT$ шт. Вы сапраўды хочаце выдаліць іх?", "placeholders": { "count": { "content": "$1", @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Паказваць распазнавальны відарыс побач з кожным лагінам." }, - "enableGravatars": { - "message": "Паказваць Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Выкарыстоўваць аватары з сайта gravatar.com." - }, "enableFullWidth": { "message": "Адлюстроўваць на ўсю шырыню макета", "description": "Allows scaling the web vault UI's width" @@ -1387,7 +1380,7 @@ "message": "У выпадку, калі вам неабходна дадаць іншую прыладу, ніжэй пазначаны QR-код (або ключы), якія патрабуюцца праграме аўтэнтыфікацыі." }, "twoStepDisableDesc": { - "message": "Вы ўпэўнены, што хочаце адключыць гэтага пастаўшчыка двухэтапнага ўваходу?" + "message": "Вы сапраўды хочаце адключыць гэтага пастаўшчыка двухэтапнага ўваходу?" }, "twoStepDisabled": { "message": "Пастаўшчык двухэтапнага ўваходу адключаны." @@ -1486,7 +1479,7 @@ "message": "Дадаць ключ бяспекі FIDO U2F у ваш уліковы запіс" }, "removeU2fConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэты ключ бяспекі?" + "message": "Вы сапраўды хочаце выдаліць гэты ключ бяспекі?" }, "twoFactorWebAuthnAdd": { "message": "Дадаць ключ бяспекі WebAuthn у ваш уліковы запіс" @@ -1916,13 +1909,13 @@ "message": "Аднавіць падпіску" }, "reinstateConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць запыт, які чакае скасавання і аднавіць сваю падпіску?" + "message": "Вы сапраўды хочаце выдаліць запыт, які чакае скасавання і аднавіць сваю падпіску?" }, "reinstated": { "message": "Падпіска была адноўлена." }, "cancelConfirmation": { - "message": "Вы ўпэўнены, што хочаце скасаваць? Вы страціце доступ да ўсіх функцый падпіскі ў канцы гэтага плацежнага перыяду." + "message": "Вы сапраўды хочаце скасаваць? Вы страціце доступ да ўсіх функцый падпіскі ў канцы гэтага плацежнага перыяду." }, "canceledSubscription": { "message": "Падпіска была скасавана." @@ -2296,7 +2289,7 @@ "message": "Выйсці" }, "leaveOrganizationConfirmation": { - "message": "Вы ўпэўнены, што хочаце выйсці з гэтай арганізацыі?" + "message": "Вы сапраўды хочаце выйсці з гэтай арганізацыі?" }, "leftOrganization": { "message": "Вы выйшлі з арганізацыі." @@ -2341,10 +2334,10 @@ "message": "Рэдагаваць групу" }, "deleteGroupConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэту групу?" + "message": "Вы сапраўды хочаце выдаліць гэту групу?" }, "removeUserConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэтага карыстальніка?" + "message": "Вы сапраўды хочаце выдаліць гэтага карыстальніка?" }, "removeOrgUserConfirmation": { "message": "Пры выдаленні ўдзельніка, ён больш не зможа атрымаць доступ да даных арганізацыі і гэта дзеянне з'яўляецца незваротным. Для таго, каб зноў дадаць удзельніка ў арганізацыю, яго неабходна будзе запрасіць паўторна." @@ -2383,7 +2376,7 @@ "message": "Рэдагаваць калекцыю" }, "deleteCollectionConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэту калекцыю?" + "message": "Вы сапраўды хочаце выдаліць гэту калекцыю?" }, "editUser": { "message": "Рэдагаваць карыстальніка" @@ -3368,7 +3361,7 @@ "message": "Ненадзейны асноўны пароль" }, "weakMasterPasswordDesc": { - "message": "Асноўны пароль, які вы выбралі з'яўляецца ненадзейным. Для належнай абароны ўліковага запісу Bitwarden, вы павінны выкарыстоўваць надзейны асноўны пароль (або парольную фразу). Вы ўпэўнены, што хочаце выкарыстоўваць гэты асноўны пароль?" + "message": "Асноўны пароль, які вы выбралі з'яўляецца ненадзейным. Для належнай абароны ўліковага запісу Bitwarden, вы павінны выкарыстоўваць надзейны асноўны пароль (або парольную фразу). Вы сапраўды хочаце выкарыстоўваць гэты асноўны пароль?" }, "rotateAccountEncKey": { "message": "Таксама змяніць ключ шыфравання майго ўліковага запісу" @@ -3377,7 +3370,7 @@ "message": "Змяніць ключ шыфравання" }, "rotateEncKeyConfirmation": { - "message": "Вы ўпэўнены, што хочаце змяніць ключ шыфравання свайго ўліковага запісу?" + "message": "Вы сапраўды хочаце змяніць ключ шыфравання свайго ўліковага запісу?" }, "attachmentsNeedFix": { "message": "Гэты элемент мае старыя далучаныя файлы, якія неабходна выправіць." @@ -3568,7 +3561,7 @@ "message": "Назаўсёды выдаліць элементы" }, "permanentlyDeleteItemConfirmation": { - "message": "Вы ўпэўнены, што хочаце назаўсёды выдаліць гэты элемент?" + "message": "Вы сапраўды хочаце назаўсёды выдаліць гэты элемент?" }, "permanentlyDeletedItem": { "message": "Элемент выдалены назаўсёды" @@ -3577,7 +3570,7 @@ "message": "Элементы выдалены назаўсёды" }, "permanentlyDeleteSelectedItemsDesc": { - "message": "Вы выбралі наступную колькасць элементаў для выдалення: $COUNT$ шт. Вы ўпэўнены, што хочаце назаўсёды выдаліць іх?", + "message": "Вы выбралі наступную колькасць элементаў для выдалення: $COUNT$ шт. Вы сапраўды хочаце назаўсёды выдаліць іх?", "placeholders": { "count": { "content": "$1", @@ -3610,13 +3603,13 @@ "message": "Адноўленыя элементы" }, "restoreItemConfirmation": { - "message": "Вы ўпэўнены, што хочаце аднавіць гэты элемент?" + "message": "Вы сапраўды хочаце аднавіць гэты элемент?" }, "restoreItems": { "message": "Аднавіць элементы" }, "restoreSelectedItemsDesc": { - "message": "Вы выбралі наступную колькасць элементаў для аднаўлення: $COUNT$. Вы ўпэўнены, што хочаце аднавіць іх?", + "message": "Вы выбралі наступную колькасць элементаў для аднаўлення: $COUNT$. Вы сапраўды хочаце аднавіць іх?", "placeholders": { "count": { "content": "$1", @@ -3634,7 +3627,7 @@ } }, "vaultTimeoutLogOutConfirmation": { - "message": "Выхад з сістэмы скасуе ўсе магчымасці доступу да сховішча і запатрабуе аўтэнтыфікацыю праз інтэрнэт пасля завяршэння часу чакання. Вы ўпэўнены, што хочаце выкарыстоўваць гэты параметр?" + "message": "Выхад з сістэмы скасуе ўсе магчымасці доступу да сховішча і запатрабуе аўтэнтыфікацыю праз інтэрнэт пасля завяршэння часу чакання. Вы сапраўды хочаце выкарыстоўваць гэты параметр?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Пацвярджэнне дзеяння часу чакання" @@ -3694,7 +3687,7 @@ "message": "Адлучыць SSO" }, "unlinkSsoConfirmation": { - "message": "Вы ўпэўнены, што хочаце адлучыць SSO для гэтай арганізацыі?" + "message": "Вы сапраўды хочаце адлучыць SSO для гэтай арганізацыі?" }, "linkSso": { "message": "Звязаць з SSO" @@ -3760,7 +3753,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэты Send?", + "message": "Вы сапраўды хочаце выдаліць гэты Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "whatTypeOfSend": { @@ -3820,7 +3813,7 @@ "message": "Пароль выдалены" }, "removePasswordConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць пароль?" + "message": "Вы сапраўды хочаце выдаліць пароль?" }, "hideEmail": { "message": "Схаваць мой адрас электроннай пошты ад атрымальнікаў." @@ -3964,7 +3957,7 @@ "message": "Запытаць доступ" }, "requestAccessConfirmation": { - "message": "Вы ўпэўнены, што хочаце запытаць экстранны доступ? Вам будзе забяспечаны доступ (меркаваная колькасць дзён: $WAITTIME$) або калі карыстальнік уручную ўхваліць запыт.", + "message": "Вы сапраўды хочаце запытаць экстранны доступ? Вам будзе забяспечаны доступ (меркаваная колькасць дзён: $WAITTIME$) або калі карыстальнік уручную ўхваліць запыт.", "placeholders": { "waittime": { "content": "$1", @@ -3988,7 +3981,7 @@ "message": "Адхіліць" }, "approveAccessConfirmation": { - "message": "Вы ўпэўнены, што хочаце ўхваліць экстранны доступ? Гэта дазволіць карыстальніку $USER$ $ACTION$ ваш уліковы запіс.", + "message": "Вы сапраўды хочаце ўхваліць экстранны доступ? Гэта дазволіць карыстальніку $USER$ $ACTION$ ваш уліковы запіс.", "placeholders": { "user": { "content": "$1", @@ -4413,7 +4406,7 @@ "message": "Гэта дзеянне нельга ўжыць для ніводнага з выбраных карыстальнікаў." }, "removeUsersWarning": { - "message": "Вы ўпэўнены, што хочаце выдаліць наступных карыстальнікаў? Для завяршэння працэсу можа спатрэбіцца некалькі секунд і гэта дзеянне нельга перарваць або скасаваць." + "message": "Вы сапраўды хочаце выдаліць наступных карыстальнікаў? Для завяршэння працэсу можа спатрэбіцца некалькі секунд і гэта дзеянне нельга перарваць або скасаваць." }, "removeOrgUsersConfirmation": { "message": "Пасля выдалення ўдзельніка(-ў) ён згубіць доступ да даных арганізацыі і гэта дзеянне з'яўляецца незваротным. Для паўторнага дабаўлення ўдзельніка ў арганізацыю, яго неабходна будзе зноў запрасіць туды. Працэс можа заняць некалькі секунд і яго немагчыма перарваць або скасаваць." @@ -4542,7 +4535,7 @@ "message": "Мой пастаўшчык" }, "addOrganizationConfirmation": { - "message": "Вы ўпэўнены, што хочаце дадаць арганізацыю $ORGANIZATION$ у якасці кліента да пастаўшчыка $PROVIDER$?", + "message": "Вы сапраўды хочаце дадаць арганізацыю $ORGANIZATION$ у якасці кліента да пастаўшчыка $PROVIDER$?", "placeholders": { "organization": { "content": "$1", @@ -4591,7 +4584,7 @@ } }, "detachOrganizationConfirmation": { - "message": "Вы ўпэўнены, што хочаце адлучыць гэту арганізацыю? Арганізацыя працягне сваё існаванне, але больш не будзе кіравацца пастаўшчыком." + "message": "Вы сапраўды хочаце адлучыць гэту арганізацыю? Арганізацыя працягне сваё існаванне, але больш не будзе кіравацца пастаўшчыком." }, "add": { "message": "Дадаць" @@ -4877,7 +4870,7 @@ "message": "Выдаліць спансіраванне" }, "removeSponsorshipConfirmation": { - "message": "Пасля выдалення спонсарства, вы будзеце адказваць за гэту падпіску і звязаныя рахункі. Вы ўпэўнены, што хочаце працягнуць?" + "message": "Пасля выдалення спонсарства, вы будзеце адказваць за гэту падпіску і звязаныя рахункі. Вы сапраўды хочаце працягнуць?" }, "sponsorshipCreated": { "message": "Спансіраванне створана" @@ -4886,7 +4879,7 @@ "message": "Ліст адпраўлены" }, "revokeSponsorshipConfirmation": { - "message": "Пасля выдалення гэтага ўліковага запісу, спонсарства тарыфнага плана Bitwarden Families завяршыцца ў канцы плацежнага перыяду. У вас не будзе магчымасці скарыстацца новай спонсарскай прапановай, пакуль не завяршыцца тэрмін бягучай прапановы. Вы ўпэўнены, што хочаце працягнуць?" + "message": "Пасля выдалення гэтага ўліковага запісу, спонсарства тарыфнага плана Bitwarden Families завяршыцца ў канцы плацежнага перыяду. У вас не будзе магчымасці скарыстацца новай спонсарскай прапановай, пакуль не завяршыцца тэрмін бягучай прапановы. Вы сапраўды хочаце працягнуць?" }, "removeSponsorshipSuccess": { "message": "Спансіраванне выдалена" @@ -5300,7 +5293,7 @@ "message": "Праверка прылады абноўлена" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { - "message": "Вы ўпэўнены, што хочаце ўключыць праверку прылады? Ліст з праверачным кодам будзе адпраўлены на: $EMAIL$", + "message": "Вы сапраўды хочаце ўключыць праверку прылады? Ліст з праверачным кодам будзе адпраўлены на: $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -5339,7 +5332,7 @@ "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateScimKeyWarning": { - "message": "Вы ўпэўнены, што хочаце змяніць ключ API для SCIM? Бягучы ключ не будзе больш працаваць для любых бягучых інтэграцый.", + "message": "Вы сапраўды хочаце змяніць ключ API для SCIM? Бягучы ключ не будзе больш працаваць для любых бягучых інтэграцый.", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateKey": { diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index c45ce7a04fe..4ab03c40f3d 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Показване на разпознаваемо изображение до всеки запис." }, - "enableGravatars": { - "message": "Включване на граватари", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Използване на иконки за потребители от сайта „gravatar.com“." - }, "enableFullWidth": { "message": "Извеждане на цяла широчина", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 9161cbc3440..2ab63dfc8de 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index a2a01b188dc..1035ed8e287 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 4d32b956dc4..552c4d96ea8 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Mostra una imatge reconeixible al costat de cada inici de sessió." }, - "enableGravatars": { - "message": "Habilita Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Utilitzeu imatges d'avatar carregades de gravatar.com." - }, "enableFullWidth": { "message": "Habilita la disposició de l'amplada completa", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 6d0219a7999..22347e373c4 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Zobrazit rozeznatelný obrázek vedle každého přihlášení." }, - "enableGravatars": { - "message": "Povolit službu Gravatar", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Použije profilový obrázek načtený z gravatar.com." - }, "enableFullWidth": { "message": "Zapnout rozvržení na celou šířku stránky", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 27a671f3cff..5bf22cbf627 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Vis et genkendeligt billede ud for hvert login." }, - "enableGravatars": { - "message": "Vis Gravatarer", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Brug avatar billeder hentet fra gravatar.com." - }, "enableFullWidth": { "message": "Vis layout med fuld bredde", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index e0a228f8a75..0a75ba0fb5f 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Ein wiedererkennbares Bild neben jeden Zugangsdaten anzeigen." }, - "enableGravatars": { - "message": "Gravatare aktivieren", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Nutze Profilbilder von gravatar.com." - }, "enableFullWidth": { "message": "Darstellung in voller Breite aktivieren", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 826f582e067..bbe161a41c5 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Ενεργοποίηση Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Χρήση εικόνων avatar από gravatar.com." - }, "enableFullWidth": { "message": "Ενεργοποίηση διάταξης πλήρους πλάτους", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 2d44c173684..22a0c162984 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognisable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 56d8bf940fb..e4ec9badec6 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Enable Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Enable full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 88a28706610..112ffece10c 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Ebligi Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Uzu avatarajn bildojn ŝarĝitajn de gravatar.com." - }, "enableFullWidth": { "message": "Ebligi plenan larĝan aranĝon", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index e878d003fde..fd0938a68e8 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Mostrar una imagen reconocible junto a cada inicio de sesión." }, - "enableGravatars": { - "message": "Habilitar Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Usa imágenes de avatares cargadas desde gravatar.com." - }, "enableFullWidth": { "message": "Habilitar diseño de ancho completo", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 078232e8c96..9e06ed53245 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Kuva iga kirje kõrval lehekülje ikooni." }, - "enableGravatars": { - "message": "Luba Gravatarid", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Luba avatari pildid, mida laaditakse lehelt gravatar.com." - }, "enableFullWidth": { "message": "Lülita sisse veebihoidla laiem vaade", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 40bac466ac3..5a8243377f9 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Erakutsi irudi bat saio-hasiera bakoitzaren ondoan." }, - "enableGravatars": { - "message": "Erakutsi gravatarrak", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Erabili gravatar.com-etik hartutako avatar irudiak." - }, "enableFullWidth": { "message": "Erabili zabalera osoko diseinua", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 64743f0ab2d..a0169192b15 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -10,7 +10,7 @@ } }, "whatTypeOfItem": { - "message": "Minkä tyyppinen kohde tämä on?" + "message": "Mikä on kohteen tyyppi?" }, "name": { "message": "Nimi" @@ -143,7 +143,7 @@ "message": "Erääntymisvuosi" }, "authenticatorKeyTotp": { - "message": "Todennusmenetelmän avain (TOTP)" + "message": "2FA-todentajan avain (TOTP)" }, "folder": { "message": "Kansio" @@ -485,7 +485,7 @@ "message": "Kohde lisättiin" }, "editedItem": { - "message": "Kohdetta muokattiin" + "message": "Kohde tallennettiin" }, "movedItemToOrg": { "message": "$ITEMNAME$ siirrettiin organisaatiolle $ORGNAME$", @@ -528,7 +528,7 @@ "message": "Kohteet siirrettiin roskakoriin" }, "movedItems": { - "message": "Kohteet siirretty" + "message": "Kohteet siirrettiin" }, "overwritePasswordConfirmation": { "message": "Haluatko varmasti korvata nykyisen salasanan?" @@ -537,7 +537,7 @@ "message": "Kansio tallennettiin" }, "addedFolder": { - "message": "Kansio lisätty" + "message": "Kansio lisättiin" }, "deleteFolderConfirmation": { "message": "Haluatko varmasti poistaa kansion?" @@ -573,10 +573,10 @@ "message": "Laitteella kirjautuminen" }, "loginWithDeviceEnabledInfo": { - "message": "Laitteella kirjautuminen otettava käyttöön Bitwardenin mobiilisovelluksen asetuksista. Tarvitsetko eri vaihtoehdon?" + "message": "Laitteella kirjautuminen on määritettävä Bitwardenin mobiilisovelluksen asetuksista. Tarvitsetko toisen vaihtoehdon?" }, "createAccount": { - "message": "Luo tili" + "message": "Luo uusi tili" }, "newAroundHere": { "message": "Oletko uusi täällä?" @@ -630,7 +630,7 @@ "message": "Salasanavihje" }, "enterEmailToGetHint": { - "message": "Syötä tilisi sähköpostiosoite saadaksesi pääsalasanasi vihjeen." + "message": "Syötä tilisi sähköpostiosoite saadaksesi pääsalasanan vihjeen." }, "getMasterPasswordHint": { "message": "Pyydä pääsalasanan vihjettä" @@ -672,7 +672,7 @@ "message": "Holvi on lukittu. Jatka vahvistamalla pääsalasanasi." }, "unlock": { - "message": "Avaa" + "message": "Avaa holvi" }, "loggedInAsEmailOn": { "message": "Kirjautuneena tunnuksella $EMAIL$ palveluun $HOSTNAME$.", @@ -811,7 +811,7 @@ "message": "FIDO U2F ‑todennuslaite" }, "webAuthnTitle": { - "message": "FIDO2 WebAuthn" + "message": "FIDO2 WebAuthn -todennuslaite" }, "webAuthnDesc": { "message": "Avaa tilisi millä tahansa WebAuthn‑yhteensopivalla todennuslaitteella." @@ -820,7 +820,7 @@ "message": "(siirretty FIDO:sta)" }, "emailTitle": { - "message": "Sähköpostiosoite" + "message": "Sähköposti" }, "emailDesc": { "message": "Todennuskoodit lähetetään sinulle sähköpostitse." @@ -1052,7 +1052,7 @@ "message": "Vaihda pääsalasana" }, "masterPasswordChanged": { - "message": "Pääsalasana vaihdettiin" + "message": "Pääsalasana tallennettiin" }, "currentMasterPass": { "message": "Nykyinen pääsalasana" @@ -1064,7 +1064,7 @@ "message": "Vahvista uusi pääsalasana" }, "encKeySettings": { - "message": "Salausavaimen asetukset" + "message": "Salausavainten asetukset" }, "kdfAlgorithm": { "message": "KDF-algoritmi" @@ -1094,7 +1094,7 @@ "message": "Vaihda KDF-asetuksia" }, "encKeySettingsChanged": { - "message": "Salausavaimen asetuksia muutettiin" + "message": "Salausavainten asetukset tallennettiin" }, "dangerZone": { "message": "Vaaravyöhyke" @@ -1109,7 +1109,7 @@ "message": "Oletko huolissasi, että tilisi on kirjautuneena muissa laitteissa? Jatka alla kirjataksesi ulos kaikki aiemmin käyttämäsi tietokoneet ja muut laitteet. Tämä turvallisuustoimenpide on suositeltava, jos olet aiemmin käyttänyt esimerkiksi julkista tietokonetta tai vahingossa tallentanut kirjautumisesi laitteeseen, joka ei ole sinun. Tämä mitätöi myös kaikki aiemmin muistetut kaksivaiheiset kirjautumiset." }, "deauthorizeSessionsWarning": { - "message": "Jatkamalla kirjaudut ulos nykyisestä istunnostasi ja joudut kirjautumaan uudelleen. Myös kaksivaiheinen kirjautuminen on tehtävä uudelleen. Aktiiviset istunnot toisilla laitteilla saattavat pysyä aktiivisina vielä tunnin ajan." + "message": "Jatkaminen uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen sekä kaksivaiheinen kirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." }, "sessionsDeauthorized": { "message": "Kaikki istunnot mitätöitiin" @@ -1121,7 +1121,7 @@ "message": "Organisaation holvi tyhjennetty." }, "vaultAccessedByProvider": { - "message": "Todentaja on käyttänyt holvia." + "message": "Ylläpito on käyttänyt holvia." }, "purgeVaultDesc": { "message": "Jatka alla poistaaksesi kaikki holvisi kohteet ja kansiot. Kohteita, jotka on jaettu ja kuuluvat organisaatiolle, ei poisteta." @@ -1218,7 +1218,7 @@ "message": "Asetukset" }, "preferencesDesc": { - "message": "Muokkaa verkkoholvisi käyttökokemusta." + "message": "Muokkaa verkkoholvin käyttökokemusta." }, "preferencesUpdated": { "message": "Asetukset tallennettiin" @@ -1227,20 +1227,13 @@ "message": "Kieli" }, "languageDesc": { - "message": "Vaihda verkkoholvissa käytettävää kieltä." + "message": "Vaihda verkkoholvissa käytettävä kieli." }, "enableFavicon": { "message": "Näytä verkkosivustojen kuvakkeet" }, "faviconDesc": { - "message": "Näytä tunnistettava kuva jokaiselle kirjautumistiedolle." - }, - "enableGravatars": { - "message": "Näytä Gravatarit", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Käytä avatar-profiilikuvia Gravatar.com-palvelusta." + "message": "Näytä tunnistettava kuvake jokaiselle kirjautumistiedolle." }, "enableFullWidth": { "message": "Käytä täyden leveyden asettelua", @@ -1277,7 +1270,7 @@ "message": "Lisää oma verkkotunnus" }, "newCustomDomainDesc": { - "message": "Syötä verkkotunnukset pilkulla eroteltuina. Vain \"juuritason\" verkkotunnukset sallitaan, ei aliverkkotunnuksia. Eli syötä esimerkiksi \"www.google.com\" sijasta \"google.com\". Voit syöttää myös \"androidapp://package.name\" -muotoisia osoitteita yhdistääksesi Android-sovelluksia verkkotunnuksiin." + "message": "Syötä verkkotunnukset pilkulla eroteltuina. Vain ns. juuritason verkkotunnukset sallitaan, ei aliverkkotunnuksia. Eli syötä esimerkiksi osoitteen \"www.google.com\" sijaan \"google.com\". Voit syöttää myös \"androidapp://package.name\" -muotoisia osoitteita liittääksesi Android-sovelluksia verkkotunnuksiin." }, "customDomainX": { "message": "Oma verkkotunnus $INDEX$", @@ -1292,7 +1285,7 @@ "message": "Verkkotunnukset tallennettiin" }, "twoStepLogin": { - "message": "Kaksivaiheinen kirjautuminen" + "message": "Kaksivaiheinen kirjautuminen (2FA)" }, "twoStepLoginDesc": { "message": "Suojaa tilisi vaatimalla sisäänkirjautumiseen toinen todennusvaihe." @@ -1301,13 +1294,13 @@ "message": "Vaadi kaksivaiheinen kirjautuminen organisaatiosi käyttäjille määrittämällä todentajat organisaation tasolla." }, "twoStepLoginRecoveryWarning": { - "message": "Kaksivaiheisen kirjautumisen käyttöönotto voi lukita sinut pysyvästi ulos Bitwaren-tililtäsi. Palautuskoodi mahdollistaa pääsyn tilillesi myös silloin, kun et voi käyttää normaaleja kaksivaiheisen kirjautumisen todentajiasi (esim. hävität todennuslaitteesi tai se varastetaan). Myöskään Bitwardenin tuki ei voi auttaa sinua, jos menetät pääsyn tillesi. Suosittelemme, että kirjoitat muistiin tai tulostat palautuskoodin ja pidät sen tallessa turvallisessa paikassa (esim. kassakaapissa tai pankin tallelokerossa)." + "message": "Kaksivaiheisen kirjautumisen käyttöönotto voi lukita sinut pysyvästi ulos Bitwaren-tililtäsi. Palautuskoodi mahdollistaa pääsyn tilillesi myös silloin, kun et voi käyttää normaaleja kaksivaiheisen kirjautumisen todentajiasi (esim. kadotat todennuslaitteesi tai se varastetaan). Myöskään Bitwardenin tuki ei voi auttaa sinua, jos menetät pääsyn tillesi. Suosittelemme, että kirjoitat palautuskoodin muistiin tai tulostat sen ja säilytät sitä turvallisessa paikassa (esim. kassakaapissa tai pankin tallelokerossa)." }, "viewRecoveryCode": { "message": "Näytä palautuskoodi" }, "providers": { - "message": "Todennustarjoajat", + "message": "Todentajat", "description": "Two-step login providers such as YubiKey, Duo, Authenticator apps, Email, etc." }, "enable": { @@ -1453,7 +1446,7 @@ "message": "Jos jokin YubiKey-todennuslaitteesi tukee NFC-tekniikkaa (kuten YubiKey NEO), näytetään mobiililaitteissa kehote NFC:n ollessa käytettävissä." }, "yubikeysUpdated": { - "message": "YubiKey-todennuslaitteet päivitetty" + "message": "YubiKey-todennuslaitteet päivitettiin" }, "disableAllKeys": { "message": "Poista kaikki todennuslaitteet käytöstä" @@ -1519,7 +1512,7 @@ "message": "Odotetaan, että painat todennuslaitteesi painiketta" }, "twoFactorU2fClickSave": { - "message": "Klikkaa alta \"Tallenna\" käyttääksesi tätä todennuslaitetta kaksivaiheiseen kirjautumiseen." + "message": "Paina alta \"Tallenna\" käyttääksesi tätä todennuslaitetta kaksivaiheiseen kirjautumiseen." }, "twoFactorU2fProblemReadingTryAgain": { "message": "Todennuslaittetta luettaessa havaittiin ongelma. Yritä uudelleen." @@ -1534,7 +1527,7 @@ "message": "Bitwardenin kaksivaiheisen kirjautumisen palautuskoodisi" }, "twoFactorRecoveryNoCode": { - "message": "Et ole vielä ottanut käyttöön yhtään kaksivaiheisen kirjautumisen tadentajaa. Otettuasi todennusmenetelmän käyttöön, löydät palautuskoodin täältä." + "message": "Et ole vielä ottanut käyttöön yhtään kaksivaiheisen kirjautumisen todentajaa. Otettuasi todentajan käyttöön, löydät palautuskoodin täältä." }, "printCode": { "message": "Tulosta koodi", @@ -1551,10 +1544,10 @@ "message": "Suojaamattomat sivustot" }, "unsecuredWebsitesReportDesc": { - "message": "Osoitteet, jotka alkavat http://, eivät hyödynnä parasta mahdollista salausta. Vaihda tällaiset kirjatumisosoitteet https://-muotoisiksi turvallisempaa selausta varten." + "message": "URL-osoitteet, jotka alkavat http://, eivät hyödynnä parasta mahdollista salausta. Vaihda tällaiset kirjatumisosoitteet https://-muotoisiksi turvallisempaa selausta varten." }, "unsecuredWebsitesFound": { - "message": "Löydettiin suojaamattomia verkkosivustoja" + "message": "Suojaamattomia verkkosivustoja löytyi" }, "unsecuredWebsitesFoundDesc": { "message": "Löysimme holvistasi $COUNT$ kohdetta suojaamattomilla URI-osoitteilla. Sinun tulisi muuttaa niiden URI suojattuun \"https://\" -muotoon, jos sivusto tukee sitä.", @@ -1572,13 +1565,13 @@ "message": "Tunnusten 2FA-tila" }, "inactive2faReportDesc": { - "message": "Kaksivaiheinen kirjautuminen (2FA) lisää tileillesi yhden suojaustason. Voit käyttää kaksivaiheiseen kirjautumiseen Bitwardenin todentajaa tai vaihtoehtoisia menetelmiä." + "message": "Kaksivaiheinen kirjautuminen (2FA) lisää tileillesi yhden suojaustason. Määritä kaksivaiheinen kirjautuminen näille tileille Bitwardenin todentajalla tai käytä vaihtoehtoisia menetelmiä." }, "inactive2faFound": { - "message": "Tunnuksia ilman kaksivaiheista kirjautumista löytyi" + "message": "Löytyi kirjautumistietoja, joille ei ole määritetty kaksivaiheista kirjautumista" }, "inactive2faFoundDesc": { - "message": "Löysimme holvistasi $COUNT$ sivustoa, joita ei ehkä ole määritetty käyttämään kaksivaiheista kirjautumista (2fa.directory -sivuston mukaan). Suojataksesi nämä tilit paremmin, sinun tulisi ottaa kaksivaiheinen kirjautuminen käyttöön.", + "message": "Löysimme holvistasi $COUNT$ sivustoa, joita ei ehkä ole määritetty käyttämään kaksivaiheista kirjautumista (2fa.directory-sivuston mukaan). Suojataksesi nämä tilit paremmin, sinun tulisi ottaa kaksivaiheinen kirjautuminen käyttöön.", "placeholders": { "count": { "content": "$1", @@ -1587,7 +1580,7 @@ } }, "noInactive2fa": { - "message": "Holvistasi ei löytynyt sivustoja, joille ei ole määritetty kaksivaiheista kirjautumista." + "message": "Holvistasi ei löytynyt kirjautumistietoja, joille ei ole määritetty kaksivaiheista kirjautumista." }, "instructions": { "message": "Ohjeet" @@ -1898,7 +1891,7 @@ "message": "Maksukortti" }, "paypalClickSubmit": { - "message": "Kirjaudu PayPal-tilillesi painamalla PayPal-painiketta ja sitten alta \"Jatka\"." + "message": "Kirjaudu PayPal-tilillesi painamalla PayPal-painiketta ja paina sitten alta \"Jatka\"." }, "cancelSubscription": { "message": "Irtisano tilaus" @@ -1907,7 +1900,7 @@ "message": "Tilaus irtisanottiin." }, "pendingCancellation": { - "message": "Odottaa peruutusta" + "message": "Odottaa irtisanomista" }, "subscriptionPendingCanceled": { "message": "Tilaus on merkitty päättymään kuluvan laskutuskauden lopussa." @@ -1925,7 +1918,7 @@ "message": "Haluatko varmasti irtisanoa tilauksen? Menetät pääsyn kaikkiin tilauksen tarjoamiin ominaisuuksiin kuluvan laskutuskauden lopussa." }, "canceledSubscription": { - "message": "Tilaus irtisanottiin." + "message": "Tilaus irtisanottiin" }, "neverExpires": { "message": "Ei eräänny koskaan" @@ -1946,10 +1939,10 @@ "message": "Päivitä lisenssi" }, "updatedLicense": { - "message": "Lisenssi päivitetty" + "message": "Lisenssi päivitettiin" }, "manageSubscription": { - "message": "Hallinnoi tilausta" + "message": "Hallitse tilausta" }, "storage": { "message": "Tallennustila" @@ -2024,7 +2017,7 @@ } }, "gbStorageAdd": { - "message": "Lisättävän tallennustilan määrä (Gt)" + "message": "Lisättävä tallennustila, Gt" }, "gbStorageRemove": { "message": "Poistettava tallennustila, Gt" @@ -2048,7 +2041,7 @@ "message": "Ota yhteyttä asiakaspalveluun" }, "updatedPaymentMethod": { - "message": "Maksutapa päivitetty." + "message": "Maksutapa päivitettiin" }, "purchasePremium": { "message": "Osta Premium" @@ -2224,7 +2217,7 @@ "message": "Suoritus omassa palvelinympäristössä (valinnainen)" }, "usersGetPremium": { - "message": "Käyttäjät saavat käyttöoikeuden Premium-ominaisuuksiin" + "message": "Käyttäjät saavat Premium-ominaisuuksien käyttöoikeuden" }, "controlAccessWithGroups": { "message": "Hallinnoi käyttäjien oikeuksia ryhmillä" @@ -2290,7 +2283,7 @@ "message": "Uusi organisaatiosi on valmis käyttöön!" }, "organizationUpgraded": { - "message": "Organisaatiosi päivitettiin." + "message": "Organisaatio päivitettiin" }, "leave": { "message": "Eroa" @@ -2311,7 +2304,7 @@ "message": "Hanki sovellukset" }, "loggedInAs": { - "message": "Kirjautuneena käyttäjänä" + "message": "Kirjautuneena" }, "eventLogs": { "message": "Tapahtumalokit" @@ -2488,19 +2481,19 @@ "message": "Verkkoholvi" }, "loggedIn": { - "message": "Kirjautui sisään." + "message": "Kirjautui sisään" }, "changedPassword": { - "message": "Tilin salasana vaihdettiin." + "message": "Tilin salasana tallennettiin" }, "enabledUpdated2fa": { - "message": "Kaksivaiheinen kirjautuminen otettiin käyttöön/päivitettiin." + "message": "Kaksivaiheinen kirjautuminen tallennettiin" }, "disabled2fa": { - "message": "Kaksivaiheinen kirjautuminen poistettiin käytöstä." + "message": "Kaksivaiheinen kirjautuminen poistettiin käytöstä" }, "recovered2fa": { - "message": "Tili vapautettiin kaksivaiheisesta kirjautumisesta." + "message": "Tili vapautettiin kaksivaiheisesta kirjautumisesta" }, "failedLogin": { "message": "Sisäänkirjautumisyritys epäonnistui väärän salasanan vuoksi." @@ -2509,13 +2502,13 @@ "message": "Kirjautuminen epäonnistui virheellisen kaksivaiheisen kirjautumisen todennuksen vuoksi." }, "exportedVault": { - "message": "Holvi viety." + "message": "Holvi vietiin" }, "exportedOrganizationVault": { - "message": "Organisaation holvi viety." + "message": "Organisaation holvi vietiin" }, "editedOrgSettings": { - "message": "Organisaation asetuksia muokattiin." + "message": "Organisaation asetukset tallennettiin" }, "createdItemId": { "message": "Kohde $ID$ luotu.", @@ -2869,7 +2862,7 @@ "message": "Muokkaa ryhmiä, joihin tämä käyttäjä kuuluu." }, "invitedUsers": { - "message": "Kutsutut käyttäjät." + "message": "Käyttäjät kutsuttiin" }, "resendInvitation": { "message": "Lähetä kutsu uudelleen" @@ -2878,7 +2871,7 @@ "message": "Lähetä sähköposti uudelleen" }, "hasBeenReinvited": { - "message": "$USER$ kutsuttiin uudelleen.", + "message": "$USER$ kutsuttiin uudelleen", "placeholders": { "user": { "content": "$1", @@ -2893,7 +2886,7 @@ "message": "Vahvista käyttäjä" }, "hasBeenConfirmed": { - "message": "$USER$ vahvistettiin.", + "message": "$USER$ vahvistettiin", "placeholders": { "user": { "content": "$1", @@ -2932,7 +2925,7 @@ "message": "Sähköpostiosoitettasi ei voitu vahvistaa. Yritä lähettää uusi vahvistussähköposti." }, "emailVerificationRequired": { - "message": "Sähköpostiosoitteen vahvistus vaaditaan" + "message": "Sähköpostiosoite on vahvistettava" }, "emailVerificationRequiredDesc": { "message": "Sinun on vahvistettava sähköpostiosoitteesi käyttääksesi tätä ominaisuutta." @@ -2974,10 +2967,10 @@ "message": "Jos et pääse tilillesi käyttämilläsi kaksivaiheisen kirjautumisen todentajilla, voit kaksivaiheisen kirjautumisen palautuskoodilla poistaa kaikki tilillesi määritetyt todentajat käytöstä." }, "recoverAccountTwoStep": { - "message": "Vapauta tilin kaksivaiheinen kirjautuminen" + "message": "Vapauta tili kaksivaiheisesta kirjautumisesta" }, "twoStepRecoverDisabled": { - "message": "Kaksivaiheinen kirjautuminen on poistettu tililtäsi käytöstä." + "message": "Kaksivaiheinen kirjautuminen poistettiin käytöstä tililtäsi." }, "learnMore": { "message": "Lue lisää" @@ -2989,7 +2982,7 @@ "message": "Jos tilisi on olemassa, olemme lähettäneet sinulle lisäohjeita sähköpostitse." }, "deleteRecoverConfirmDesc": { - "message": "Olet pyytänyt Bitwarden-tilisi poistoa. Vahvista painamalla alla olevaa painiketta." + "message": "Olet pyytänyt Bitwarden-tilisi poistoa. Vahvista alla olevalla painikeella." }, "myOrganization": { "message": "Oma organisaatio" @@ -3025,7 +3018,7 @@ "message": "Organisaatio ja kaikki siihen liittyvät tiedot on poistettu." }, "organizationUpdated": { - "message": "Organisaatio päivitettiin" + "message": "Organisaatio tallennettiin" }, "taxInformation": { "message": "Verotiedot" @@ -3106,7 +3099,7 @@ "message": "Yritys tai yhteisö" }, "bankAccountTypeIndividual": { - "message": "Yksityinen" + "message": "Henkilökohtainen" }, "enterInstallationId": { "message": "Syötä asennuksesi ID-tunnus" @@ -3148,7 +3141,7 @@ "message": "Tilauksen käyttäjäpaikat" }, "subscriptionUpdated": { - "message": "Tilaus päivitetty" + "message": "Tilaus päivitettiin" }, "additionalOptions": { "message": "Lisävalinnat" @@ -3316,7 +3309,7 @@ "message": "Selaimen sivupäivityksen yhteydessä" }, "dateUpdated": { - "message": "Päivitetty", + "message": "Päivitettiin", "description": "ex. Date this item was updated" }, "dateCreated": { @@ -3337,7 +3330,7 @@ "message": "Lisenssi on erääntynyt." }, "updatedUsers": { - "message": "Päivitetyt käyttäjät" + "message": "Käyttäjät päivitettiin" }, "selected": { "message": "Valittu" @@ -3542,10 +3535,10 @@ "message": "Holvin aikakatkaisutoiminto" }, "vaultTimeoutActionLockDesc": { - "message": "Holvisi käyttö edellyttää pääsalasanaa tai muuta avaustapaa." + "message": "Vaadi pääsalasana tai jokin vaihtoehtoinen avaustapa, kun holvi avataan uudelleen." }, "vaultTimeoutActionLogOutDesc": { - "message": "Holvisi käyttö edellyttää uutta todennusta." + "message": "Vaadi uusi todennus, kun holvi avataan uudelleen." }, "lock": { "message": "Lukitse", @@ -3634,7 +3627,7 @@ } }, "vaultTimeoutLogOutConfirmation": { - "message": "Uloskirjautuminen estää pääsyn holviisi ja vaatii ajan umpeuduttua todennuksen internet-yhteyden välityksellä. Haluatko varmasti käyttää tätä asetusta?" + "message": "Uloskirjautuminen estää pääsyn holviisi ja vaatii ajan umpeuduttua todennuksen Internet-yhteyden välityksellä. Haluatko varmasti käyttää tätä asetusta?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Aikakatkaisutoiminnon vahvistus" @@ -3652,7 +3645,7 @@ "message": "ALV/GST-verotustunniste" }, "taxInfoUpdated": { - "message": "Verotiedot päivitetty." + "message": "Verotiedot päivitettiin" }, "setMasterPassword": { "message": "Aseta pääsalasana" @@ -3682,7 +3675,7 @@ "message": "Kertakirjautumisen (SSO) todennus SAML 2.0 ja OpenID Connect -todentajilla" }, "includeEnterprisePolicies": { - "message": "Yrityksen käytännöt" + "message": "Yritykskäytännöt" }, "ssoValidationFailed": { "message": "Kertakirjautumisen (SSO) todennus epäonnistui" @@ -3715,7 +3708,7 @@ "message": "Vaadi kertakirjautumisen (SSO) todennus" }, "requireSsoPolicyDesc": { - "message": "Vaadi jäseniä kirjautumaan käyttäen yrityksen kertakirjautumista (SSO)." + "message": "Vaadi jäseniä kirjautumaan käyttäen yrityksen määrittämää kertakirjautumista (SSO)." }, "prerequisite": { "message": "Edellytys" @@ -3724,7 +3717,7 @@ "message": "\"Yksittäinen organisaatio\" -käytäntö on otettava käyttöön ennen tämän käytännön käyttöönottoa." }, "requireSsoPolicyReqError": { - "message": "\"Yksittäinen organisaatio\" -käytäntö ei ole käytössä." + "message": "\"Yksittäinen organisaatio\" -käytäntöä ei ole määritetty." }, "requireSsoExemption": { "message": "Organisaation omistajat ja ylläpitäjät on vapautettu tämän käytännön piiristä." @@ -3736,7 +3729,7 @@ "message": "Teksti" }, "createSend": { - "message": "Luo uusi Send", + "message": "Uusi Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -3744,11 +3737,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Luotiin Send", + "message": "Send tallennettiin", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Sendiä muokattiin", + "message": "Send tallennettiin", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { @@ -3877,7 +3870,7 @@ "message": "Varmuuskäyttö" }, "emergencyAccessDesc": { - "message": "Myönnä ja hallinnoi luotettujen kontaktien varmuuskäyttöoikeuksia. Luotetut kontaktit voivat anoa oikeutta tilisi tarkasteluun tai hallintaan ongelmatilanteissa. Käy ohjesivullamme saadaksesi lisätietoja ja tarkempia tietoja siitä, miten Zero Knowledge -jako toimii (englanniksi)." + "message": "Myönnä ja hallinnoi luotettujen kontaktien varmuuskäyttöoikeuksia. Luotetut kontaktit voivat anoa oikeutta tilisi tarkasteluun tai hallintaan ongelmatilanteissa. Käy ohjesivullamme saadaksesi lisätietoja ja tarkempia tietoja siitä, miten Zero Knowledge -jako toimii." }, "emergencyAccessOwnerWarning": { "message": "Olet yhden tai useamman organisaation omistaja. Jos myönnät varmuuskontaktille hallintaoikeuden, on heillä haltuunoton jälkeen samat oikeudet." @@ -3907,10 +3900,10 @@ "message": "Kutsu uusi varmuuskontakti syöttämällä alle hänen Bitwarden-tilinsä sähköpostiosoite. Jos hänellä ei vielä ole Bitwarden-tiliä, pyydetään häntä luomaan uusi tili." }, "emergencyAccessRecoveryInitiated": { - "message": "Varmuuskäyttö aloitettu" + "message": "Varmuuskäyttö aloitettiin" }, "emergencyAccessRecoveryApproved": { - "message": "Varmuuskäyttö sallittu" + "message": "Varmuuskäyttö sallittiin" }, "viewDesc": { "message": "Voi tarkastella kaikkia holvisi kohteita." @@ -4001,7 +3994,7 @@ } }, "emergencyApproved": { - "message": "Varmuuskäyttö sallittu." + "message": "Varmuuskäyttö sallittiin" }, "emergencyRejected": { "message": "Varmuuskäyttö hylätty." @@ -4025,7 +4018,7 @@ "message": "Organisaation omistajat ja ylläpitäjät on vapautettu tämän käytännön piiristä." }, "personalOwnershipSubmitError": { - "message": "Yrityksen asettaman käytännön johdosta kohteiden tallennus omaan holviisi ei ole mahdollista. Muuta omistusasetus organisaatiolle ja valitse käytettävissä olevista kokoelmista." + "message": "Yrityskäytännön johdosta kohteiden tallennus henkilökohtaiseen holviin ei ole mahdollista. Muuta omistusasetus organisaatiolle ja valitse käytettävissä olevista kokoelmista." }, "disableSend": { "message": "Poista Send" @@ -4035,14 +4028,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disableSendExemption": { - "message": "Käytäntöä ei pakoteta käyttöön niille organisaation käyttäjille, joilla on organisaation käytäntöjen hallintaoikeudet." + "message": "Käytäntöä ei pakoteta käyttöön niille organisaation käyttäjille, joilla on organisaatiokäytäntöjen hallintaoikeudet." }, "sendDisabled": { - "message": "Send on poistettu käytöstä", + "message": "Send poistettiin", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Yrityksen käytännön vuoksi voit poistaa vain olemassa olevan Sendin.", + "message": "Yrityskäytännön vuoksi voit poistaa vain olemassa olevan Sendin.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptions": { @@ -4054,7 +4047,7 @@ "description": "'Sends' is a plural noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsExemption": { - "message": "Käytäntöä ei pakoteta käyttöön niille organisaation käyttäjille, joilla on organisaation käytäntöjen hallintaoikeudet." + "message": "Käytäntöä ei pakoteta käyttöön niille organisaation käyttäjille, joilla on organisaatiokäytäntöjen hallintaoikeudet." }, "disableHideEmail": { "message": "Näytä jäsenen sähköpostiosoite aina vastaanottajien ohessa, kun Send luodaan tai sitä muokataan.", @@ -4158,7 +4151,7 @@ "message": "Organisaatiokäytäntö vaikuttaa omistajuusvalintoihisi." }, "personalOwnershipPolicyInEffectImports": { - "message": "Organisaation käytäntö estää kohteiden tuonnin henkilökohtaiseen holviisi." + "message": "Organisaatiokäytäntö estää kohteiden tuonnin henkilökohtaiseen holviisi." }, "personalOwnershipCheckboxDesc": { "message": "Poista henkilökohtaisen omistajuuden valinta käytöstä organisaation käyttäjiltä" @@ -4260,13 +4253,13 @@ "message": "Tapahtui virhe tallennettaessa poisto- ja erääntymisajankohtia." }, "webAuthnFallbackMsg": { - "message": "Vahvista kaksivaiheinen kirjautuminen (2FA) klikkaamalla alla olevaa painiketta." + "message": "Vahvista kaksivaiheinen kirjautuminen (2FA) alla olevalla painikeella." }, "webAuthnAuthenticate": { "message": "WebAuthn-todennus" }, "webAuthnNotSupported": { - "message": "WebAuthn ei ole tuettu tässä selaimessa." + "message": "WebAuthn-todennusta ei tueta tässä selaimessa." }, "webAuthnSuccess": { "message": "WebAuthn-todennus onnistui! Voit sulkea välilehden." @@ -4425,7 +4418,7 @@ "message": "Teema" }, "themeDesc": { - "message": "Valitse verkkoholvin teema." + "message": "Valitse verkkoholvin ulkoasuteema." }, "themeSystem": { "message": "Järjestelmä" @@ -4443,22 +4436,22 @@ "message": "Joukkotoiminnon tila" }, "bulkConfirmMessage": { - "message": "Vahvistus onnistui." + "message": "Vahvistus onnistui" }, "bulkReinviteMessage": { - "message": "Uuudelleenkutsu onnistui." + "message": "Uuudelleenkutsu onnistui" }, "bulkRemovedMessage": { "message": "Poisto onnistui." }, "bulkRevokedMessage": { - "message": "Organisaation käyttöoikeus peruttiin onnistuneesti" + "message": "Organisaation käyttöoikeus peruttiin" }, "bulkRestoredMessage": { "message": "Organisaation käyttöoikeuden palautus onnistui" }, "bulkFilteredMessage": { - "message": "Ohitettu, ei koske tätä toimintoa." + "message": "Ohitettu, ei koske tätä toimintoa" }, "fingerprint": { "message": "Sormenjälki" @@ -4512,7 +4505,7 @@ "message": "Kutsu todentajallesi uusi käyttäjä syöttämällä alle hänen Bitwarden-tilinsä sähköpostiosoite. Jos hänellä ei vielä ole Bitwarden-tiliä, pyydetään häntä luomaan uusi tili." }, "joinProvider": { - "message": "Liity todennustarjoajaan" + "message": "Liity todentajaan" }, "joinProviderDesc": { "message": "Sinut on kutsuttu liittymään yllä mainittuun todentajaan. Hyväksyäksesi kutsun, sinun on kirjauduttava tilillesi tai luotava uusi Bitwarden-tili." @@ -4527,7 +4520,7 @@ "message": "Sinulla on kutsunsa hyväksyneitä käyttäjiä, jotka on vielä vahvistettava. Käyttäjät eivät voi käyttää todentajaa ennen vahvistusta." }, "provider": { - "message": "Todennustarjoaja" + "message": "Todentaja" }, "newClientOrganization": { "message": "Uusi asiakasorganisaatio" @@ -4539,7 +4532,7 @@ "message": "Lisää olemassa oleva organisaatio" }, "myProvider": { - "message": "Oma todennustarjoaja" + "message": "Oma todentaja" }, "addOrganizationConfirmation": { "message": "Haluatko varmasti lisätä organisaation $ORGANIZATION$ todentajan $PROVIDER$ asiakkaaksi?", @@ -4570,7 +4563,7 @@ "message": "Todentaja on jäädytetty" }, "providerUpdated": { - "message": "Todentaja on päivitetty" + "message": "Todentaja tallennettiin" }, "yourProviderIs": { "message": "Todentajasi on $PROVIDER$. Heillä on hallinta- ja laskutusoikeudet organisaatioosi.", @@ -4597,16 +4590,16 @@ "message": "Lisää" }, "updatedMasterPassword": { - "message": "Pääsalasana on päivitetty" + "message": "Pääsalasana päivitettiin" }, "updateMasterPassword": { "message": "Päivitä pääsalasana" }, "updateMasterPasswordWarning": { - "message": "Organisaatiosi ylläpito on hiljattain vaihtanut pääsalasanasi. Käyttääksesi holvia, on sinun päivitettävä se nyt. Tämä uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." + "message": "Organisaatiosi ylläpito on hiljattain vaihtanut pääsalasanasi. Käyttääksesi holvia, sinun on vaihdettava se nyt. Tämä uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." }, "masterPasswordInvalidWarning": { - "message": "Pääsalasanasi ei vastaa organisaation käytäntöä. Liittyäksesi organisaatioon, on sinun päivitettävä se nyt. Tämä uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." + "message": "Pääsalasanasi ei vastaa organisaatiokäytäntöä. Liittyäksesi organisaatioon, on sinun päivitettävä se nyt. Tämä uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." }, "maximumVaultTimeout": { "message": "Holvin aikakatkaisu" @@ -4658,10 +4651,10 @@ "message": "Älä salli jäseniä viemästä yksityisten holviensa tietoja." }, "vaultExportDisabled": { - "message": "Holvin vienti on poistettu käytöstä" + "message": "Holvin vienti poistettiin" }, "personalVaultExportPolicyInEffect": { - "message": "Yksi tai useampi organisaation käytäntö estää henkilökohtaisen holvisi viennin." + "message": "Yksi tai useampi organisaatiokäytäntö estää henkilökohtaisen holvisi viennin." }, "selectType": { "message": "Valitse SSO-tyyppi" @@ -4673,7 +4666,7 @@ "message": "OpenID Connect -määritykset" }, "samlSpConfig": { - "message": "SAML-todennustarjoajan määritykset" + "message": "SAML-palveluntarjoajan määritykset" }, "samlIdpConfig": { "message": "SAML-tunnistustietojen tarjoajan määritykset" @@ -4736,10 +4729,10 @@ "message": "Outbound Signing -algoritmi" }, "spSigningBehavior": { - "message": "Signing-käyttäytyminen" + "message": "Allekirjoituskäyttäytyminen" }, "spMinIncomingSigningAlgorithm": { - "message": "Pienin sallittu Incoming Signing -algoritmi" + "message": "Pienin saapuva allekirjoitusalgoritmi" }, "spWantAssertionsSigned": { "message": "Odota Assertion-lähetysten allekirjoitusta" @@ -4754,16 +4747,16 @@ "message": "Sidontatyyppi" }, "idpSingleSignOnServiceUrl": { - "message": "Single Sign On Service URL" + "message": "Kertakirjautumispalvelun URL" }, "idpSingleLogoutServiceUrl": { - "message": "Single Log Out Service URL" + "message": "Single log-out service URL" }, "idpX509PublicCert": { "message": "Julkinen X509 -varmenne" }, "idpOutboundSigningAlgorithm": { - "message": "Outbound Signing -algoritmi" + "message": "Lähtevä allekirjoitusalgoritmi" }, "idpAllowUnsolicitedAuthnResponse": { "message": "Salli Unsolicited-todennusvastaus" @@ -4880,16 +4873,16 @@ "message": "Sponsoroinnin poiston jälkeen olet vastuussa tilauksesta ja siihen liittyvistä laskuista. Haluatko varmasti jatkaa?" }, "sponsorshipCreated": { - "message": "Sponsorointi on luotu" + "message": "Sponsorointi luotiin" }, "emailSent": { - "message": "Sähköposti on lähetetty" + "message": "Sähköposti lähetettiin" }, "revokeSponsorshipConfirmation": { "message": "Tilin poiston jälkeen Perheille-tilauksen sponsorointi päättyy kuluvan laskutusjakson lopussa. Et voi lunastaa uuttaa sponsorointitarjousta ennen nykyisen päättymistä. Haluatko varmasti jatkaa?" }, "removeSponsorshipSuccess": { - "message": "Sponsorointi on poistettu" + "message": "Sponsorointi poistettiin" }, "ssoKeyConnectorError": { "message": "Key Connector -virhe: Varmista, että Key Connector on käytettävissä ja toimii oikein." @@ -4904,7 +4897,7 @@ "message": "Lähetä koodi" }, "codeSent": { - "message": "Koodi lähetetty" + "message": "Koodi lähetettiin" }, "verificationCode": { "message": "Todennuskoodi" @@ -4973,10 +4966,10 @@ "message": "Kertakirjautuminen (SSO) Key Connector -salauksenpurulla on käytössä. Käytäntö vaikuttaa vain omistajiin ja ylläpitäjiin." }, "enabledSso": { - "message": "Kertakirjautuminen otettiin käyttöön" + "message": "SSO otettiin käyttöön" }, "disabledSso": { - "message": "Kertakirjautuminen poistettiin käytöstä" + "message": "SSO poistettiin käytöstä" }, "enabledKeyConnector": { "message": "Key Connector otettiin käyttöön" @@ -4985,7 +4978,7 @@ "message": "Key Connector poistettiin käytöstä" }, "keyConnectorWarning": { - "message": "Kun käyttäjät alkavat käyttämään Key Connectoria, organisaatiosi ei voi palata takaisin pääsalasanalla tapahtuvaan salauksenpurkuun. Jatka ainoastaan siinä tapauksessa, jos olet valmis määrittämään avainpalvelimen ja myös ylläpitämään sitä." + "message": "Kun käyttäjät alkavat käyttämään Key Connectoria, organisaatiosi ei voi palata takaisin pääsalasanalla tapahtuvaan salauksenpurkuun. Jatka vain, jos olet valmis määrittämään avainpalvelimen ja myös ylläpitämään sitä." }, "migratedKeyConnector": { "message": "Siirretty käyttämään Key Connectoria" @@ -5057,16 +5050,16 @@ "message": "Määrittääksesi organisaation omalle palvelimellesi, tulee lisenssitiedosto tallentaa sinne. Sinun on määritettävä laskutuksen synkronointi, jotta ilmaiset Perheille-tilaukset ja edistyneet laskutusominaisuudet ovat itse ylläpitämäsi organisaation käytettävissä." }, "billingSyncApiKeyRotated": { - "message": "Tunniste uudistettiin." + "message": "Tunniste uudistettiin" }, "billingSync": { "message": "Laskutuksen synkronointi" }, "billingSyncDesc": { - "message": "Laskutuksen synkronointi mahdollistaa jäsenille ilmaiset Perheille-tilaukset ja edistyneet laskutusominaisuudet liittämällä itse ylläpitämäsi Bitwarden-palvelin Bitwardenin pilvipalvelimeen." + "message": "Laskutuksen synkronointi mahdollistaa jäsenille ilmaiset Perheille-tilaukset ja edistyneet laskutusominaisuudet liittämällä itse ylläpitämäsi Bitwarden-palvelimen Bitwardenin pilvipalvelimeen." }, "billingSyncKeyDesc": { - "message": "Lomakkeen täyttö edellyttää laskutuksen synkronointitunnisteen pilviorganisaatiosi tilausasetuksista." + "message": "Lomakkeen täyttöön tarvitaan laskutuksen synkronointitunniste pilviorganisaatiosi tilausasetuksista." }, "billingSyncKey": { "message": "Laskutuksen synkronointitunniste" @@ -5135,7 +5128,7 @@ "message": "Organisaation holvin vienti" }, "exportingPersonalVaultDescription": { - "message": "Vain tunnukseen $EMAIL$ liitetyt henkilökohtaiset holvin kohteet viedään. Organisaation kohteet eivät sisälly tähän.", + "message": "Vain tunnukseen $EMAIL$ liitetyt henkilökohtaisen holvin kohteet viedään. Organisaation kohteet eivät sisälly tähän.", "placeholders": { "email": { "content": "$1", @@ -5291,13 +5284,13 @@ "message": "Laitevahvistus" }, "enableDeviceVerification": { - "message": "Käytä laitevahvistus" + "message": "Käytä laitevahvistusta" }, "deviceVerificationDesc": { "message": "Kun käytössä, todennuskoodit lähetetään sinulle sähköpostitse kirjauduttaessa tuntemattomasta laitteesta." }, "updatedDeviceVerification": { - "message": "Laitevahvostus päivitettiin" + "message": "Laitevahvistus päivitettiin" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { "message": "Haluatko varmasti käyttää laitevahvistusta? Todennuskoodit lähetetään sähköpostiosoitteeseen $EMAIL$", @@ -5316,7 +5309,7 @@ "description": "The text, 'SCIM', is an acronymn and should not be translated." }, "scimDescription": { - "message": "Provisioi käyttäjät ja ryhmät haluamasi tunnistustietojen tarjoajan kanssa SCIM-provisioinnilla", + "message": "Provisioi käyttäjät ja ryhmät haluamasi tunnistustietojen tarjoajan (Identity Provider) kanssa SCIM-provisioinnilla.", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDesc": { @@ -5324,7 +5317,7 @@ "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDescHelpText": { - "message": "Määritä haluamasi tunnistustietojen tarjoaja määrittämällä URL-osoite ja SCIM API -avain", + "message": "Määritä haluamasi tunnistustietojen tarjoaja (Identity Provider) määrittämällä URL-osoite ja SCIM API -avain.", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimApiKeyHelperText": { @@ -5358,11 +5351,11 @@ "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." }, "scimApiKeyRotated": { - "message": "SCIM API-avaimen uudistus onnistui", + "message": "SCIM API-avain uudistettiin", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "scimSettingsSaved": { - "message": "SCIM-asetusten tallennus onnistui", + "message": "SCIM-asetukset tallennettiin", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "inputRequired": { diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 4defa63fab7..6ed39c50b20 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index b911d3af55b..2277e3acd38 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Afficher une image reconnaissable à côté de chaque identifiant." }, - "enableGravatars": { - "message": "Activer Gravatar", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Charger votre image de profil depuis gravatar.com." - }, "enableFullWidth": { "message": "Activer la mise en page en pleine largeur", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 5bc2dd49508..5e01d8dd244 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "אפשר שימוש ב-Gravatar", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "שימוש בתמונות אווטאר שנטענו מהאתר gravatar.com." - }, "enableFullWidth": { "message": "אפשר תצוגה ברוחב מלא", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 58e8c6c78bb..b8146165c0b 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 24d5375ab6a..db36d6ccce4 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Omogući Gravatar", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Koristi slike učitane s gravatar.com." - }, "enableFullWidth": { "message": "Uključi raspored s punom širinom", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index a7a61e31ebe..55ab5a95d44 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Felismerhető kép megjelenítése minden bejelentkezés mellett." }, - "enableGravatars": { - "message": "Gravatarok megjelenítése", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Avatar képek használata a gravatar.com webhelyről." - }, "enableFullWidth": { "message": "Teljes szélességű elrendezés engedélyezése", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 9def26714f2..9726cf8c062 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Tampilkan gambar pengenal di samping setiap login." }, - "enableGravatars": { - "message": "Aktifkan Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Gunakan gambar avatar yang dimuat dari gravatar.com." - }, "enableFullWidth": { "message": "Aktifkan Tata Letak Lebar Penuh", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 2f8ac231dad..359c6b5e6c4 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Mostra un'immagine riconoscibile accanto a ogni login." }, - "enableGravatars": { - "message": "Abilita Gravatar", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Usa immagine profilo caricata da gravatar.com." - }, "enableFullWidth": { "message": "Abilita disposizione a larghezza piena", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index cdabf12c7c6..3465f3c66ed 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "ログイン情報の隣にアイコン画像を表示します" }, - "enableGravatars": { - "message": "Gravatar を有効化", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "gravatar.com から読み込んだアバター画像を使います。" - }, "enableFullWidth": { "message": "全幅レイアウトを有効化", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 2fe1fd7d356..8fb1d8504da 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 2fe1fd7d356..8fb1d8504da 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 702af436785..3a5e5aa7c37 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Gravatars ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Gravatar.com ನಿಂದ ಲೋಡ್ ಮಾಡಲಾದ ಅವತಾರ್ ಚಿತ್ರಗಳನ್ನು ಬಳಸಿ." - }, "enableFullWidth": { "message": "ಪೂರ್ಣ ಅಗಲ ವಿನ್ಯಾಸವನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 97624623af3..3c952fce1c3 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Gravatar 사용", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "gravatar.com의 아바타 이미지를 사용." - }, "enableFullWidth": { "message": "전체 너비 레이아웃 활성화", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 5e7d8045883..a16055d0ac6 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Attēlot atpazīstamu attēlu pie katra pierakstīšanās vienuma." }, - "enableGravatars": { - "message": "Iespējot Gravatārus", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Izmantot no gravatar.com ielādētus avatāra attēlus." - }, "enableFullWidth": { "message": "Iespējot pilna platuma izkārtojumu", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 81f23cdcd23..2d82407e601 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Enable Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Enable Full Width Layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index b309082db4a..c8346a009e0 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Vis et gjenkjennelig bilde ved siden av hver innlogging." }, - "enableGravatars": { - "message": "Skru på Gravatarer", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Bruk avatarbilder som har blitt lastet inn fra gravatar.com." - }, "enableFullWidth": { "message": "Aktiver fullbreddeoppsett", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 918f809c68b..7a1f01a77ae 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Een herkenbare afbeelding naast iedere login weergeven." }, - "enableGravatars": { - "message": "Gravatars activeren", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Gebruik avatar-afbeeldingen van gravatar.com." - }, "enableFullWidth": { "message": "Weergave op volle breedte inschakelen", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index c042f4ba03f..d68ecb50662 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 8b033d42a61..72aeabd9d4c 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -71,7 +71,7 @@ "message": "Firma" }, "ssn": { - "message": "PESEL" + "message": "Numer PESEL" }, "passportNumber": { "message": "Numer paszportu" @@ -227,7 +227,7 @@ "description": "Toggling an expand/collapse state." }, "generatePassword": { - "message": "Generuj hasło" + "message": "Wygeneruj hasło" }, "checkPassword": { "message": "Sprawdź, czy hasło zostało ujawnione." @@ -467,7 +467,7 @@ "message": "Czy na pewno chcesz usunąć ten załącznik?" }, "attachmentSaved": { - "message": "Załącznik został zapisany." + "message": "Załącznik został zapisany" }, "file": { "message": "Plik" @@ -485,7 +485,7 @@ "message": "Element został dodany" }, "editedItem": { - "message": "Element został zaktualizowany" + "message": "Element został zapisany" }, "movedItemToOrg": { "message": "Element $ITEMNAME$ został przeniesiony do organizacji $ORGNAME$", @@ -528,13 +528,13 @@ "message": "Elementy zostały przeniesione do kosza" }, "movedItems": { - "message": "Przeniesione elementy" + "message": "Elementy zostały przeniesione" }, "overwritePasswordConfirmation": { "message": "Czy na pewno chcesz zastąpić obecne hasło?" }, "editedFolder": { - "message": "Folder został zaktualizowany" + "message": "Folder został zapisany" }, "addedFolder": { "message": "Folder został dodany" @@ -597,7 +597,7 @@ "message": "Używaj swojego adresu e-mail do logowania." }, "yourName": { - "message": "Twoje imię" + "message": "Twoja nazwa" }, "yourNameDesc": { "message": "Jak powinniśmy cię nazywać?" @@ -691,7 +691,7 @@ "message": "Hasło główne jest nieprawidłowe" }, "invalidFilePassword": { - "message": "Nieprawidłowe hasło, użyj hasła, które wprowadziłeś podczas tworzenia pliku eksportu." + "message": "Hasło pliku jest nieprawidłowe. Użyj prawidłowego hasła." }, "lockNow": { "message": "Zablokuj" @@ -718,7 +718,7 @@ "message": "Nie należysz do żadnej organizacji. Organizacje pozwalają na bezpieczne udostępnianie elementów innym użytkownikom." }, "notificationSentDevice": { - "message": "Powiadomienie zostało wysłane na twoje urządzenie." + "message": "Powiadomienie zostało wysłane na urządzenie." }, "versionNumber": { "message": "Wersja $VERSION_NUMBER$", @@ -909,10 +909,10 @@ "message": "Format pliku" }, "fileEncryptedExportWarningDesc": { - "message": "Ten plik będzie chroniony hasłem i będzie wymagał tego hasła do odszyfrowania." + "message": "Plik będzie chroniony hasłem, które będzie wymagane do odszyfrowania pliku." }, "exportPasswordDescription": { - "message": "To hasło będzie używane do eksportu i importu tego pliku" + "message": "Hasło będzie używane do eksportowania i importowania pliku" }, "confirmMasterPassword": { "message": "Potwierdź hasło główne" @@ -921,10 +921,10 @@ "message": "Potwierdź format" }, "filePassword": { - "message": "Hasło do pliku" + "message": "Hasło pliku" }, "confirmFilePassword": { - "message": "Potwierdź hasło do pliku" + "message": "Potwierdź hasło pliku" }, "accountBackupOptionDescription": { "message": "Wykorzystuje szyfrowanie konta Bitwarden, a nie hasło główne, aby chronić eksport. Ten eksport może być zaimportowany tylko na tym koncie. Użyj tego, aby utworzyć kopię zapasową, której nie można użyć nigdzie indziej." @@ -933,7 +933,7 @@ "message": "Utwórz hasło wygenerowane przez użytkownika, aby chronić eksport. Użyj tego, aby utworzyć eksport, który może być używany na innych kontach." }, "fileTypeHeading": { - "message": "Typ Pliku" + "message": "Rodzaj pliku" }, "accountBackup": { "message": "Kopia zapasowa konta" @@ -942,16 +942,16 @@ "message": "Zabezpieczone hasłem" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "\"Hasło do pliku\" i \"Potwierdź hasło do pliku\" nie pasują do siebie." + "message": "“Hasło pliku” i “Potwierdź hasło pliku“ nie pasują do siebie." }, "confirmVaultImport": { - "message": "Potwierdź import sejfu" + "message": "Potwierdź importowanie sejfu" }, "confirmVaultImportDesc": { - "message": "Ten plik jest chroniony hasłem. Wprowadź hasło pliku, aby zaimportować dane." + "message": "Plik jest chroniony hasłem. Wprowadź hasło pliku, aby zaimportować dane." }, "exportSuccess": { - "message": "Dane z sejfu zostały wyeksportowane." + "message": "Dane sejfu zostały wyeksportowane" }, "passwordGenerator": { "message": "Generator hasła" @@ -970,7 +970,7 @@ "message": "Unikaj niejednoznacznych znaków" }, "regeneratePassword": { - "message": "Wygeneruj hasło ponownie" + "message": "Wygeneruj ponownie hasło" }, "length": { "message": "Długość" @@ -1003,7 +1003,7 @@ "message": "Uwzględnij cyfry" }, "passwordHistory": { - "message": "Historia haseł" + "message": "Historia hasła" }, "noPasswordsInList": { "message": "Brak haseł." @@ -1013,7 +1013,7 @@ "description": "To clear something out. Example: To clear browser history." }, "accountUpdated": { - "message": "Konto zostało zaktualizowane" + "message": "Konto zostało zapisane" }, "changeEmail": { "message": "Zmień adres e-mail" @@ -1040,7 +1040,7 @@ "message": "Ta czynność spowoduje wylogowanie z bieżącej sesji, przez co konieczne będzie ponowne zalogowanie się. Aktywne sesje na innych urządzeniach mogą pozostać aktywne przez maksymalnie godzinę." }, "emailChanged": { - "message": "Adres e-mail został zmieniony" + "message": "Adres e-mail został zapisany" }, "logBackIn": { "message": "Zaloguj się ponownie." @@ -1052,7 +1052,7 @@ "message": "Zmień hasło główne" }, "masterPasswordChanged": { - "message": "Hasło główne zostało zmienione" + "message": "Hasło główne zostało zapisane" }, "currentMasterPass": { "message": "Obecne hasło główne" @@ -1094,7 +1094,7 @@ "message": "Zmień KDF" }, "encKeySettingsChanged": { - "message": "Ustawienia klucza szyfrowania zostały zaktualizowane" + "message": "Ustawienia klucza szyfrowania zostały zapisane" }, "dangerZone": { "message": "Niebezpieczna strefa" @@ -1166,7 +1166,7 @@ "message": "Wystąpił problem z danymi, które chcesz zaimportować. Rozwiąż poniższe problemy w Twoim pliku i spróbuj ponownie." }, "importSuccess": { - "message": "Dane zostały zaimportowane do sejfu." + "message": "Dane zostały zaimportowane" }, "importWarning": { "message": "Importujesz dane do organizacji $ORGANIZATION$. Dane mogą zostać udostępnione członkom organizacji. Czy chcesz kontynuować?", @@ -1221,7 +1221,7 @@ "message": "Dostosuj swoje doświadczenia z sejfem internetowym." }, "preferencesUpdated": { - "message": "Ustawienia zostały zaktualizowane" + "message": "Ustawienia zostały zapisane" }, "language": { "message": "Język" @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Pokaż rozpoznawalny obraz obok każdych danych logowania." }, - "enableGravatars": { - "message": "Pokaż Gravatary", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Korzystaj ze zdjęć profilowych załadowanych z serwisu gravatar.com." - }, "enableFullWidth": { "message": "Włącz układ na całą szerokość", "description": "Allows scaling the web vault UI's width" @@ -1289,7 +1282,7 @@ } }, "domainsUpdated": { - "message": "Domeny zostały zaktualizowane" + "message": "Domeny zostały zapisane" }, "twoStepLogin": { "message": "Logowanie dwustopniowe" @@ -1345,7 +1338,7 @@ "message": "Wyłącz" }, "revokeAccess": { - "message": "Odwołaj dostęp" + "message": "Unieważnij dostęp" }, "twoStepLoginProviderEnabled": { "message": "Ten dostawca logowania dwustopniowego jest już włączony na koncie." @@ -1480,7 +1473,7 @@ "message": "Wpisz 6-cyfrowy kod weryfikacyjny z wiadomości e-mail" }, "sendEmail": { - "message": "Wyślij e-mail" + "message": "Wyślij wiadomość" }, "twoFactorU2fAdd": { "message": "Dodaj klucz bezpieczeństwa FIDO U2F do swojego konta" @@ -1548,7 +1541,7 @@ "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "unsecuredWebsitesReport": { - "message": "Niezabezpieczone witryny" + "message": "Niezabezpieczone strony" }, "unsecuredWebsitesReportDesc": { "message": "Używanie niezabezpieczonych stron (protokół HTTP) może być niebezpieczne. Jeśli strona na to pozwala, zawsze powinieneś używać protokołu HTTPS, aby Twoje połączenie było szyfrowane." @@ -1724,13 +1717,13 @@ "message": "Strona" }, "affectedUsers": { - "message": "Liczba poszkodowanych użytkowników" + "message": "Poszkodowani użytkownicy" }, "breachOccurred": { - "message": "Data wystąpienia naruszenia" + "message": "Data naruszenia" }, "breachReported": { - "message": "Naruszenie zostało zgłoszone" + "message": "Data zgłoszenia" }, "reportError": { "message": "Wystąpił błąd podczas próby załadowania raportu. Spróbuj ponownie" @@ -1925,7 +1918,7 @@ "message": "Czy na pewno chcesz anulować? Dostęp do wszystkich funkcji związanych z tą subskrypcją zostanie wyłączony na koniec tego okresu rozliczeniowego." }, "canceledSubscription": { - "message": "Subskrypcja została anulowana." + "message": "Subskrypcja została anulowana" }, "neverExpires": { "message": "Nigdy nie wygasa" @@ -2027,7 +2020,7 @@ "message": "Wybierz liczbę GB do dodania" }, "gbStorageRemove": { - "message": "GB przestrzeni do usunięcia" + "message": "Wybierz liczbę GB do usunięcia" }, "storageAddNote": { "message": "Dodanie przestrzeni dyskowej będzie skutkować korektą sumy należności i natychmiastowym obciążeniem konta. Pierwsza opłata zostanie naliczona proporcjonalnie do końca obecnego okresu rozliczeniowego." @@ -2087,13 +2080,13 @@ "message": "To konto jest własnością firmy." }, "billingEmail": { - "message": "E-mail rozliczeniowy" + "message": "Adres rozliczeniowy" }, "businessName": { "message": "Nazwa firmy" }, "chooseYourPlan": { - "message": "Wybierz swój plan" + "message": "Wybierz plan" }, "users": { "message": "Użytkownicy" @@ -2488,7 +2481,7 @@ "message": "Sejf internetowy" }, "loggedIn": { - "message": "Zalogowano." + "message": "Zalogowano" }, "changedPassword": { "message": "Hasło do konta zostało zmienione." @@ -2509,13 +2502,13 @@ "message": "Logowanie nie powiodło się z powodu nieprawidłowego logowania dwustopniowego." }, "exportedVault": { - "message": "Sejf został wyeksportowany." + "message": "Sejf został wyeksportowany" }, "exportedOrganizationVault": { - "message": "Sejf organizacji został wyeksportowany." + "message": "Sejf organizacji został wyeksportowany" }, "editedOrgSettings": { - "message": "Ustawienia organizacji zostały zaktualizowane." + "message": "Ustawienia organizacji zostały zapisane" }, "createdItemId": { "message": "Element $ID$ został utworzony.", @@ -2584,7 +2577,7 @@ } }, "viewedSecurityCodeItemId": { - "message": "Wyświetlono kod bezpieczeństwa dla elementu $ID$.", + "message": "Wyświetlono kod zabezpieczający dla elementu $ID$.", "placeholders": { "id": { "content": "$1", @@ -2611,7 +2604,7 @@ } }, "copiedSecurityCodeItemId": { - "message": "Skopiowano kod bezpieczeństwa dla elementu $ID$.", + "message": "Skopiowano kod zabezpieczający dla elementu $ID$.", "placeholders": { "id": { "content": "$1", @@ -2860,7 +2853,7 @@ "message": "Dostęp użytkownika" }, "userType": { - "message": "Typ użytkownika" + "message": "Rodzaj użytkownika" }, "groupAccess": { "message": "Dostęp grupowy" @@ -2869,7 +2862,7 @@ "message": "Zmień grupy, do których należy użytkownik." }, "invitedUsers": { - "message": "Użytkownicy zostali zaproszeni." + "message": "Użytkownicy zostali zaproszeni" }, "resendInvitation": { "message": "Wyślij ponownie zaproszenie" @@ -2878,7 +2871,7 @@ "message": "Wyślij ponownie wiadomość" }, "hasBeenReinvited": { - "message": "Użytkownik $USER$ został ponownie zaproszony.", + "message": "Użytkownik $USER$ został ponownie zaproszony", "placeholders": { "user": { "content": "$1", @@ -3025,7 +3018,7 @@ "message": "Organizacja i wszystkie połączone z nią dane zostały usunięte." }, "organizationUpdated": { - "message": "Organizacja została zaktualizowana" + "message": "Organizacja została zapisana" }, "taxInformation": { "message": "Informacje podatkowe" @@ -3062,7 +3055,7 @@ "message": "Pobierz fakturę" }, "verifyBankAccount": { - "message": "Weryfikuj konto bankowe" + "message": "Zweryfikuj konto bankowe" }, "verifyBankAccountDesc": { "message": "Dokonaliśmy dwóch mikro przelewów na konto bankowe (mogą upłynąć 1-2 dni robocze, zanim je zobaczysz). Wpisz te kwoty, aby zweryfikować konto bankowe." @@ -3100,7 +3093,7 @@ "message": "Nazwa posiadacza konta" }, "bankAccountType": { - "message": "Typ konta" + "message": "Rodzaj konta" }, "bankAccountTypeCompany": { "message": "Firmowe" @@ -3324,7 +3317,7 @@ "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Aktualizacja hasła", + "message": "Hasło zostało zaktualizowane", "description": "ex. Date this password was updated" }, "organizationIsDisabled": { @@ -3586,7 +3579,7 @@ } }, "permanentlyDeletedItemId": { - "message": "Element $ID$ został trwale usunięty.", + "message": "Element $ID$ został trwale usunięty", "placeholders": { "id": { "content": "$1", @@ -3625,7 +3618,7 @@ } }, "restoredItemId": { - "message": "Element $ID$ został przywrócony.", + "message": "Element $ID$ został przywrócony", "placeholders": { "id": { "content": "$1", @@ -3736,7 +3729,7 @@ "message": "Tekst" }, "createSend": { - "message": "Utwórz nową wysyłkę", + "message": "Nowa wysyłka", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -3744,11 +3737,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Wysyłka została utworzona", + "message": "Wysyłka została zapisana", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Wysyłka została zaktualizowana", + "message": "Wysyłka została zapisana", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { @@ -4001,7 +3994,7 @@ } }, "emergencyApproved": { - "message": "Dostęp awaryjny został zatwierdzony." + "message": "Dostęp awaryjny został zatwierdzony" }, "emergencyRejected": { "message": "Dostęp awaryjny został odrzucony" @@ -4038,7 +4031,7 @@ "message": "Użytkownicy organizacji, którzy mogą zarządzać zasadami organizacji są zwolnieni z przestrzegania wymagań zasad." }, "sendDisabled": { - "message": "Wysyłka została wyłączona", + "message": "Wysyłka została usunięta", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -4570,7 +4563,7 @@ "message": "Dostawca jest wyłączony." }, "providerUpdated": { - "message": "Dostawca został zaktualizowany" + "message": "Dostawca został zapisany" }, "yourProviderIs": { "message": "Twój dostawca to $PROVIDER$. Posiada on uprawnienia administracyjne i rozliczeniowe dla Twojej organizacji.", @@ -5057,7 +5050,7 @@ "message": "Aby skonfigurować swoją organizację na własnym serwerze, musisz przesłać plik licencyjny. Aby wesprzeć darmowe plany rodzinne i zaawansowane możliwości rozliczeniowe dla własnej organizacji, musisz skonfigurować synchronizację płatności." }, "billingSyncApiKeyRotated": { - "message": "Token został zmieniony." + "message": "Token został zmieniony" }, "billingSync": { "message": "Synchronizacja płatności" diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 2e717bd85aa..8941af3dc68 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Mostrar uma imagem reconhecível ao lado de cada login." }, - "enableGravatars": { - "message": "Habilitar Gravatar", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use imagens de avatar carregadas do gravatar.com." - }, "enableFullWidth": { "message": "Habilitar Esquema de Largura Total", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 1334c574d55..290d7e55524 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Mostre uma imagem reconhecível ao lado de cada login." }, - "enableGravatars": { - "message": "Ativar Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Utilizar imagens de avatar carregadas a partir de gravatar.com." - }, "enableFullWidth": { "message": "Ativar layout de largura total", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 6be8b599310..e606d92c495 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Afișează o imagine ușor de recunoscut lângă fiecare autentificare." }, - "enableGravatars": { - "message": "Afișare Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Folosește imagini avatar încărcate de pe gravatar.com." - }, "enableFullWidth": { "message": "Afișare aspect pe toată lățimea", "description": "Allows scaling the web vault UI's width" @@ -4715,31 +4708,31 @@ "message": "Tipuri de claim cu nume personalizat" }, "acrValues": { - "message": "Requested authentication context class reference values" + "message": "Valorile de referință ale clasei de context de autentificare solicitate" }, "expectedReturnAcrValue": { - "message": "Expected \"acr\" claim value in response" + "message": "Valoarea claim „acr” așteptată ca răspuns" }, "spEntityId": { - "message": "SP entity ID" + "message": "ID entitate SP" }, "spMetadataUrl": { - "message": "SAML 2.0 metadata URL" + "message": "URL metadata SAML 2.0" }, "spAcsUrl": { - "message": "Assertion consumer service (ACS) URL" + "message": "URL-ul Serviciului de aserțiuni consumatori (ACS)" }, "spNameIdFormat": { - "message": "Name ID format" + "message": "Format nume ID" }, "spOutboundSigningAlgorithm": { - "message": "Outbound signing algorithm" + "message": "Algoritm de semnare de ieșire" }, "spSigningBehavior": { - "message": "Signing behavior" + "message": "Comportament de semnare" }, "spMinIncomingSigningAlgorithm": { - "message": "Minimum incoming signing algorithm" + "message": "Algoritm de semnare de intrare minim" }, "spWantAssertionsSigned": { "message": "Se așteaptă afirmații semnate" @@ -4751,19 +4744,19 @@ "message": "ID-ul Entității" }, "idpBindingType": { - "message": "Binding type" + "message": "Tipul de legare" }, "idpSingleSignOnServiceUrl": { - "message": "Single sign-on service URL" + "message": "URL-ul serviciului de autentificare unică" }, "idpSingleLogoutServiceUrl": { - "message": "Single log-out service URL" + "message": "URL-ul serviciului de deconectare unică" }, "idpX509PublicCert": { - "message": "X509 public certificate" + "message": "Certificat public X509" }, "idpOutboundSigningAlgorithm": { - "message": "Outbound signing algorithm" + "message": "Algoritm de semnare de ieșire" }, "idpAllowUnsolicitedAuthnResponse": { "message": "Se permite răspunsul nesolicitat de autentificare" @@ -4775,7 +4768,7 @@ "message": "Semnare a solicitărilor de autentificare" }, "ssoSettingsSaved": { - "message": "Single sign-on configuration saved" + "message": "Configurație de autentificare unică salvată" }, "sponsoredFamilies": { "message": "Planul Bitwarden Familii gratuit" @@ -4820,10 +4813,10 @@ "message": "Acceptați oferta pentru o organizație existentă sau creați o nouă organizație Familiile." }, "setupSponsoredFamiliesLoginDesc": { - "message": "You've been offered a free Bitwarden Families plan organization. To continue, you need to log in to the account that received the offer." + "message": "Vi s-a oferit o organizare gratuită a planului Bitwarden Familii. Pentru a continua, trebuie să vă conectați la contul care a primit oferta." }, "sponsoredFamiliesAcceptFailed": { - "message": "Unable to accept offer. Please resend the offer email from your Enterprise account and try again." + "message": "Oferta nu poate fi acceptată. Retrimiteți e-mailul de ofertă din contul Enterprise și încercați din nou." }, "sponsoredFamiliesAcceptFailedShort": { "message": "Imposibil de acceptat oferta. $DESCRIPTION$", @@ -4844,7 +4837,7 @@ "message": "Revendicată" }, "redeemedAccount": { - "message": "Account redeemed" + "message": "Cont revendicat" }, "revokeAccount": { "message": "Revocare cont $NAME$", @@ -4856,7 +4849,7 @@ } }, "resendEmailLabel": { - "message": "Resend sponsorship email to $NAME$ sponsorship", + "message": "Retrimiteți e-mailul de sponsorizare la sponsorizarea $NAME$", "placeholders": { "name": { "content": "$1", @@ -4865,31 +4858,31 @@ } }, "freeFamiliesPlan": { - "message": "Free Families plan" + "message": "Plan gratuit pentru familii" }, "redeemNow": { - "message": "Redeem now" + "message": "Revendicați acum" }, "recipient": { "message": "Beneficiar" }, "removeSponsorship": { - "message": "Remove sponsorship" + "message": "Înlăturare sponsorizare" }, "removeSponsorshipConfirmation": { "message": "După eliminarea unei sponsorizări, veți fi responsabil pentru acest abonament și facturile conexe. Sigur doriți să continuați?" }, "sponsorshipCreated": { - "message": "Sponsorship created" + "message": "Sponsorizare creată" }, "emailSent": { - "message": "Email sent" + "message": "E-mail trimis" }, "revokeSponsorshipConfirmation": { "message": "După eliminarea acestui cont, sponsorizarea planului Familii va expira la sfârșitul perioadei de facturare. Nu veți putea revendica o nouă ofertă de sponsorizare până când nu expiră cea existentă. Sunteți sigur că doriți să continuați?" }, "removeSponsorshipSuccess": { - "message": "Sponsorship removed" + "message": "Sponsorizare înlăturată" }, "ssoKeyConnectorError": { "message": "Eroare de Conector Cheie: asigurați-vă că aveți conectorul Cheie disponibil și că funcționează corect." @@ -4901,13 +4894,13 @@ "message": "Trimite un cod de verificare la adresa dvs. de e-mail" }, "sendCode": { - "message": "Send code" + "message": "Trimitere cod" }, "codeSent": { - "message": "Code sent" + "message": "Cod trimis" }, "verificationCode": { - "message": "Verification code" + "message": "Cod de verificare" }, "confirmIdentity": { "message": "Confirmați-vă identitatea pentru a continua." @@ -4928,13 +4921,13 @@ } }, "leaveOrganization": { - "message": "Leave organization" + "message": "Părăsire organizație" }, "removeMasterPassword": { - "message": "Remove master password" + "message": "Înlăturare parolă principală" }, "removedMasterPassword": { - "message": "Master password removed" + "message": "Parola principală înlăturată" }, "allowSso": { "message": "Permite autentificarea SSO" @@ -4958,34 +4951,34 @@ "message": "Pentru a configura decriptarea Conectorul Cheie este necesară atât autentificarea SSO, cât și politicile de organizație unică." }, "memberDecryptionOption": { - "message": "Member decryption options" + "message": "Opțiuni de decriptare a membrilor" }, "memberDecryptionPassDesc": { - "message": "Once authenticated, members will decrypt vault data using their master passwords." + "message": "Odată autentificați, membrii vor decripta datele din seif folosind parolele lor principale." }, "keyConnector": { "message": "Conector Cheie" }, "memberDecryptionKeyConnectorDesc": { - "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. Contact Bitwarden Support for set up assistance." + "message": "Conectați-vă autentificarea cu SSO la propriul server de chei de decriptare găzduit. Utilizând această opțiune, membrii nu vor trebui să-și folosească parolele principale pentru a decripta datele din seif. Contactați serviciul de asistență Bitwarden pentru asistență la configurare." }, "keyConnectorPolicyRestriction": { - "message": "\"Login with SSO and Key Connector Decryption\" is activated. This policy will only apply to owners and admins." + "message": "„Autentificare cu SSO și Decriptare conector cheie” este activată. Această politică se va aplica numai la proprietari și administratori." }, "enabledSso": { - "message": "SSO turned on" + "message": "SSO a fost activat" }, "disabledSso": { - "message": "SSO turned on" + "message": "SSO a fost activat" }, "enabledKeyConnector": { - "message": "Key Connector activated" + "message": "Conector cheie activat" }, "disabledKeyConnector": { - "message": "Key Connector deactivated" + "message": "Conector cheie dezactivat" }, "keyConnectorWarning": { - "message": "Once members begin using Key Connector, your organization cannot revert to master password decryption. Proceed only if you are comfortable deploying and managing a key server." + "message": "Odată ce membrii încep să utilizeze Conectorul cheie, organizația dvs. nu mai poate reveni la decriptarea parolei principale. Continuați numai dacă vă simțiți confortabil cu implementarea și gestionarea unui server cheie." }, "migratedKeyConnector": { "message": "Migrat la Conectorul Cheie" @@ -4997,13 +4990,13 @@ "message": "Oferta de sponsorizare a expirat. Puteți șterge organizația pe care ați creat-o pentru a evita o taxă la sfârșitul perioadei de încercare de 7 zile. În caz contrar, puteți închide această solicitare pentru a păstra organizația și pentru a vă asuma responsabilitatea de facturare." }, "newFamiliesOrganization": { - "message": "New Families organization" + "message": "Organizație de familii nouă" }, "acceptOffer": { - "message": "Accept offer" + "message": "Acceptare ofertă" }, "sponsoringOrg": { - "message": "Sponsoring organization" + "message": "Organizație de sponsorizare" }, "keyConnectorTest": { "message": "Test" @@ -5021,55 +5014,55 @@ "message": "GRATUIT cu sponsorizare" }, "viewBillingSyncToken": { - "message": "View billing sync token" + "message": "Afișare token sincronizare facturare" }, "generateBillingSyncToken": { - "message": "Generate billing sync token" + "message": "Generare token de sincronizare facturare" }, "copyPasteBillingSync": { - "message": "Copy and paste this token into the billing sync settings of your self-hosted organization." + "message": "Copiați și lipiți acest token în setările de sincronizare a facturării organizației auto-găzduite." }, "billingSyncCanAccess": { - "message": "Your billing sync token can access and edit this organization's subscription settings." + "message": "Tokenul de sincronizare a facturării poate accesa și edita setările de abonament ale acestei organizații." }, "manageBillingSync": { - "message": "Manage billing sync" + "message": "Gestionare sincronizare de facturare" }, "setUpBillingSync": { - "message": "Set up billing sync" + "message": "Configurare sincronizare de facturare" }, "generateToken": { - "message": "Generate token" + "message": "Generare token" }, "rotateToken": { - "message": "Rotate token" + "message": "Revocare token" }, "rotateBillingSyncTokenWarning": { "message": "Continuând, va trebui să reinițializați sincronizarea facturării pe serverul dvs. auto-găzduit." }, "rotateBillingSyncTokenTitle": { - "message": "Rotating the billing sync token will invalidate the previous token." + "message": "Revocarea tokenului de sincronizare a facturării va invalida tokenul anterior." }, "selfHostingTitle": { - "message": "Self-hosting" + "message": "Auto-găzduire" }, "selfHostingEnterpriseOrganizationSectionCopy": { "message": "Pentru a vă configura organizația pe propriul server, va trebui să încărcați fișierul de licență. Pentru a susține planurile Familii gratuite și capacitățile avansate de facturare pentru organizația dvs. auto-găzduită, va trebui să configurați sincronizarea facturării." }, "billingSyncApiKeyRotated": { - "message": "Token rotated" + "message": "Token revocat" }, "billingSync": { - "message": "Billing sync" + "message": "Sincronizare facturare" }, "billingSyncDesc": { - "message": "Billing sync provides Free Families plans for members and advanced billing capabilities by linking your self-hosted Bitwarden to the Bitwarden cloud server." + "message": "Sincronizarea de facturare oferă planuri gratuite de familii pentru membri și capabilități avansate de facturare prin conectarea Bitwarden-ului dvs. auto-găzduit la serverul cloud Bitwarden." }, "billingSyncKeyDesc": { - "message": "A billing sync token from your cloud organization's subscription settings is required to complete this form." + "message": "Pentru a completa acest formular este necesar un token de sincronizare a facturării din setările de abonament ale organizației dvs. cloud." }, "billingSyncKey": { - "message": "Billing sync token" + "message": "Token de sincronizare a facturării" }, "active": { "message": "Activ" @@ -5078,13 +5071,13 @@ "message": "Inactiv" }, "sentAwaitingSync": { - "message": "Sent (awaiting sync)" + "message": "Trimis (în așteptarea sincronizării)" }, "sent": { "message": "Trimis" }, "requestRemoved": { - "message": "Removed (awaiting sync)" + "message": "Înlăturat (în așteptare de sincronizare)" }, "requested": { "message": "Solicitat" @@ -5117,7 +5110,7 @@ "message": "Necesar dacă ID-ul de entitate nu este un URL." }, "openIdOptionalCustomizations": { - "message": "Optional customizations" + "message": "Personalizări opționale" }, "openIdAuthorityRequired": { "message": "Necesar dacă Autoritatea nu este validă." @@ -5129,13 +5122,13 @@ "message": "Sesiunea dvs. a expirat. Vă rugăm reveniți și încercați să vă autentificați din nou." }, "exportingPersonalVaultTitle": { - "message": "Exporting individual vault" + "message": "Exportul seifului individual" }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "Exportul seifului organizației" }, "exportingPersonalVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included.", + "message": "Numai articolele de seif individuale asociate cu $EMAIL$ vor fi exportate. Articolele de seif ale organizației nu vor fi incluse.", "placeholders": { "email": { "content": "$1", @@ -5144,7 +5137,7 @@ } }, "exportingOrganizationVaultDescription": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Individual vault items and items from other organizations will not be included.", + "message": "Numai seiful organizației asociat cu $ORGANIZATION$ va fi exportat. Articolele de seif individuale și articolele din alte organizații nu vor fi incluse.", "placeholders": { "organization": { "content": "$1", @@ -5153,10 +5146,10 @@ } }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "Acces refuzat. Nu aveți permisiunea de a vizualiza această pagină." }, "masterPassword": { - "message": "Master password" + "message": "Parola principală" }, "security": { "message": "Securitate" @@ -5165,10 +5158,10 @@ "message": "Chei" }, "billingHistory": { - "message": "Billing history" + "message": "Istoric facturare" }, "backToReports": { - "message": "Back to reports" + "message": "Înapoi la rapoarte" }, "organizationPicker": { "message": "Selector de organizație" @@ -5178,7 +5171,7 @@ "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." }, "accountSettings": { - "message": "Account settings" + "message": "Setări cont" }, "generator": { "message": "Generator" @@ -5187,26 +5180,26 @@ "message": "Ce doriți să generați?" }, "passwordType": { - "message": "Password type" + "message": "Tip de parolă" }, "regenerateUsername": { - "message": "Regenerate username" + "message": "Regenerare nume de utilizator" }, "generateUsername": { - "message": "Generate username" + "message": "Generare nume de utilizator" }, "usernameType": { - "message": "Username type" + "message": "Tip de nume de utilizator" }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "E-mail Plus adresat", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { "message": "Utilizați capacitățile de subadresare ale furnizorului dvs. de e-mail." }, "catchallEmail": { - "message": "Catch-all email" + "message": "E-mail Catch-all" }, "catchallEmailDesc": { "message": "Utilizați inbox-ul catch-all configurat pentru domeniul dvs." @@ -5216,13 +5209,13 @@ "description": "Generates domain-based username using random letters" }, "randomWord": { - "message": "Random word" + "message": "Cuvânt aleatoriu" }, "service": { "message": "Serviciu" }, "unknownCipher": { - "message": "Unknown item, you may need to request permission to access this item." + "message": "Articol necunoscut, ar putea fi necesară solicitarea permisiunii de a accesa acest articol." }, "cannotSponsorSelf": { "message": "Nu puteți revendica pentru contul activ. Introduceți un alt e-mail." @@ -5255,7 +5248,7 @@ } }, "lastSync": { - "message": "Last sync", + "message": "Ultima sincronizare", "Description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\"" }, "sponsorshipsSynced": { @@ -5275,7 +5268,7 @@ "description": "This text is displayed if an organization's billing is managed by a Provider. It tells the user to contact the Provider for assistance." }, "forwardedEmail": { - "message": "Forwarded email alias" + "message": "Alias de e-mail redirecționat" }, "forwardedEmailDesc": { "message": "Generați un alias de e-mail cu un serviciu de redirecționare extern." @@ -5285,22 +5278,22 @@ "description": "Part of a URL." }, "apiAccessToken": { - "message": "API access token" + "message": "Token de acces API" }, "deviceVerification": { - "message": "Device verification" + "message": "Verificarea dispozitivului" }, "enableDeviceVerification": { - "message": "Turn on device verification" + "message": "Activare verificare dispozitiv" }, "deviceVerificationDesc": { - "message": "Verification codes are sent to your email address when logging in from an unrecognized device" + "message": "Codurile de verificare sunt trimise la adresa dvs. de e-mail la conectarea de pe un dispozitiv nerecunoscut" }, "updatedDeviceVerification": { - "message": "Updated device verification" + "message": "Verificare dispozitiv actualizată" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { - "message": "Are you sure you want to turn on device verification? The verification code emails will arrive at: $EMAIL$", + "message": "Sunteți sigur că doriți să activați verificarea dispozitivului? E-mailurile cu codul de verificare vor sosi la: $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -5312,7 +5305,7 @@ "message": "Este necesar un abonament Premium" }, "scim": { - "message": "SCIM provisioning", + "message": "Provizionare SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." }, "scimDescription": { @@ -5331,11 +5324,11 @@ "message": "Această cheie API are acces pentru a gestiona utilizatorii din cadrul organizației dumneavoastră. Ar trebui să fie păstrată secretă." }, "copyScimKey": { - "message": "Copy the SCIM API key to your clipboard", + "message": "Copiere cheie API SCIM în clipboard", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateScimKey": { - "message": "Rotate the SCIM API key", + "message": "Revocare cheie API SCIM", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateScimKeyWarning": { @@ -5343,10 +5336,10 @@ "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateKey": { - "message": "Rotate key" + "message": "Revocare cheie" }, "scimApiKey": { - "message": "SCIM API key", + "message": "Cheia API SCIM", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "copyScimUrl": { @@ -5358,18 +5351,18 @@ "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." }, "scimApiKeyRotated": { - "message": "SCIM API key successfully rotated", + "message": "Revocarea cheii API SCIM s-a finalizat", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "scimSettingsSaved": { - "message": "SCIM settings saved", + "message": "Setări SCIM salvate", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "inputRequired": { "message": "Este necesară o intrare." }, "inputEmail": { - "message": "Input is not an email address." + "message": "Intrarea nu este o adresă de e-mail." }, "inputMinLength": { "message": "Intrarea trebuie să aibă o lungime de cel puțin $COUNT$ caractere.", diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index dda4855c4c9..db8983f0445 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -485,7 +485,7 @@ "message": "Элемент добавлен" }, "editedItem": { - "message": "Элемент изменен" + "message": "Элемент сохранен" }, "movedItemToOrg": { "message": "$ITEMNAME$ перемещен в $ORGNAME$", @@ -534,7 +534,7 @@ "message": "Вы хотите перезаписать текущий пароль?" }, "editedFolder": { - "message": "Папка отредактирована" + "message": "Папка сохранена" }, "addedFolder": { "message": "Папка добавлена" @@ -769,13 +769,13 @@ "message": "Вход недоступен" }, "noTwoStepProviders": { - "message": "У этого аккаунта включена двухфакторная аутентификация, однако ни один из настроенных вариантов не поддерживается этим веб-браузером." + "message": "У этой учетной записи включена двухфакторная аутентификация, однако ни один из настроенных вариантов не поддерживается этим веб-браузером." }, "noTwoStepProviders2": { "message": "Используйте поддерживаемый веб-браузер (например Chrome) и/или добавьте дополнительные варианты аутентификации, которые поддерживаются в веб-браузерах (например приложение-аутентификатор)." }, "twoStepOptions": { - "message": "Настройки двухэтапной аутентификации" + "message": "Настройки двухфакторной аутентификации" }, "recoveryCodeDesc": { "message": "Потеряли доступ ко всем вариантам двухфакторной аутентификации? Используйте код восстановления, чтобы отключить ее для вашего аккаунта." @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Отображать узнаваемое изображение рядом с каждым логином." }, - "enableGravatars": { - "message": "Включить Gravatar", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Использовать аватары с сайта gravatar.com." - }, "enableFullWidth": { "message": "Активировать отображение на всю ширину", "description": "Allows scaling the web vault UI's width" @@ -1456,7 +1449,7 @@ "message": "YubiKey обновлены" }, "disableAllKeys": { - "message": "Отключить все ключи" + "message": "Деактивировать все ключи" }, "twoFactorDuoDesc": { "message": "Введите информацию о приложении Bitwarden из панели администратора Duo." diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 760ffeaa13b..f5eda855e97 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 713df091ce4..35c9b3ad408 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Pri každom prihlásení zobraziť rozpoznateľný obrázok." }, - "enableGravatars": { - "message": "Zobraziť Gravatary", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Použiť avatar obrázky načítané z gravatar.com." - }, "enableFullWidth": { "message": "Zapnúť vzhľad na celú šírku strany", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index fd63d6c8fd4..9184b034254 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 0d86412f24a..c15fcd789c5 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Прикажи препознатљиву слику поред сваке ставке за пријаву." }, - "enableGravatars": { - "message": "Омогући Gravatar", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Користите слике аватара учитане са gravatar.com." - }, "enableFullWidth": { "message": "Упали пуни ширину распореда", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index f91edebe61a..59afd50aee9 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Omogućite Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 7905fd55fec..2e7508b9482 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -1193,10 +1193,10 @@ "message": "Välj importfilen" }, "chooseFile": { - "message": "Choose File" + "message": "Välj fil" }, "noFileChosen": { - "message": "No file chosen" + "message": "Ingen fil har valts" }, "orCopyPasteFileContents": { "message": "eller kopiera och klistra in innehållet från filen" @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Aktivera gravatarer", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Använd avatarbilder hämtade från gravatar.com." - }, "enableFullWidth": { "message": "Aktivera layout med full bredd", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 939d859fc72..b5be3629c30 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Hesapların yanında tanıdık görseller göster." }, - "enableGravatars": { - "message": "Gravatar'ı Etkinleştir", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "gravatar.com adresinden yüklenen avatarları kullan." - }, "enableFullWidth": { "message": "Tam genişlik görünümünü etkinleştir", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 37dae125923..a1511fccfa6 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -467,7 +467,7 @@ "message": "Ви дійсно хочете видалити це вкладення?" }, "attachmentSaved": { - "message": "Вкладення збережено." + "message": "Вкладення збережено" }, "file": { "message": "Файл" @@ -485,7 +485,7 @@ "message": "Запис додано" }, "editedItem": { - "message": "Запис змінено" + "message": "Запис збережено" }, "movedItemToOrg": { "message": "$ITEMNAME$ переміщено до $ORGNAME$", @@ -516,7 +516,7 @@ "message": "Видалити теку" }, "deleteAttachment": { - "message": "Видалити файл" + "message": "Видалити вкладення" }, "deleteItemConfirmation": { "message": "Ви дійсно хочете перенести до смітника?" @@ -534,7 +534,7 @@ "message": "Ви дійсно хочете перезаписати поточний пароль?" }, "editedFolder": { - "message": "Теку змінено" + "message": "Теку збережено" }, "addedFolder": { "message": "Теку додано" @@ -694,7 +694,7 @@ "message": "Неправильний пароль файлу. Використайте пароль, який ви вводили під час створення експортованого файлу." }, "lockNow": { - "message": "Заблокувати зараз" + "message": "Блокувати зараз" }, "noItemsInList": { "message": "Немає записів." @@ -769,7 +769,7 @@ "message": "Вхід недоступний" }, "noTwoStepProviders": { - "message": "Для цього облікового запису увімкнено двоетапну перевірку. Однак, жоден з налаштованих провайдерів двоетапної перевірки не підтримується цим браузером." + "message": "Для цього облікового запису увімкнено двоетапну перевірку. Однак, жоден із налаштованих провайдерів не підтримується цим браузером." }, "noTwoStepProviders2": { "message": "Будь ласка, скористайтеся підтримуваним браузером (наприклад, Chrome) та/або іншими провайдерами, що краще підтримуються браузерами (наприклад, програма авторизації)." @@ -903,7 +903,7 @@ "message": "Експорт" }, "exportVault": { - "message": "Експорт сховища" + "message": "Експортувати сховище" }, "fileFormat": { "message": "Формат файлу" @@ -951,7 +951,7 @@ "message": "Цей файл захищений паролем. Будь ласка, введіть пароль для імпортування даних." }, "exportSuccess": { - "message": "Дані сховища експортовано." + "message": "Дані сховища експортовано" }, "passwordGenerator": { "message": "Генератор паролів" @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Показувати впізнаване зображення біля кожного запису." }, - "enableGravatars": { - "message": "Показувати Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Використовувати зображення профілю завантажені з gravatar.com." - }, "enableFullWidth": { "message": "Показувати на всю ширину сторінки", "description": "Allows scaling the web vault UI's width" @@ -1333,10 +1326,10 @@ "message": "Для використання цієї функції необхідна передплата преміум." }, "youHavePremiumAccess": { - "message": "У вас є преміум-доступ" + "message": "У вас є преміумдоступ" }, "alreadyPremiumFromOrg": { - "message": "У вас вже є доступ до преміум-функцій, тому що ви входите до організації, яка вам їх надає." + "message": "У вас вже є доступ до преміумфункцій, тому що ви входите до організації, яка вам їх надає." }, "manage": { "message": "Керувати" @@ -1408,7 +1401,7 @@ "message": "Збережіть форму." }, "twoFactorYubikeyWarning": { - "message": "У зв'язку з обмеженнями платформи, YubiKey не можна використовувати в усіх програмах Bitwarden. Вам слід активувати іншого провайдера двоетапної перевірки, щоб ви могли отримати доступ до свого облікового запису, коли неможливо скористатися YubiKey. Підтримувані платформи:" + "message": "У зв'язку з обмеженнями платформи, YubiKey не можна використовувати в усіх програмах Bitwarden. Вам слід активувати іншого провайдера двоетапної перевірки, щоб мати змогу отримати доступ до свого облікового запису, коли неможливо скористатися YubiKey. Підтримувані платформи:" }, "twoFactorYubikeySupportUsb": { "message": "Веб сховище, програма для комп'ютера, CLI, а також усі розширення браузера на пристроях, де можливо під'єднати YubiKey до USB." @@ -1456,7 +1449,7 @@ "message": "YubiKey оновлено" }, "disableAllKeys": { - "message": "Вимкнути всі ключі" + "message": "Деактивувати всі ключі" }, "twoFactorDuoDesc": { "message": "Введіть інформацію про програму Bitwarden з вашої панелі адміністратора Duo." @@ -1510,7 +1503,7 @@ "message": "Зберегти форму." }, "twoFactorU2fWarning": { - "message": "У зв'язку з обмеженнями платформи, FIDO U2F не можна використовувати в усіх програмах Bitwarden. Вам слід активувати іншого провайдера двоетапної перевірки, щоб ви могли отримати доступ до свого облікового запису, коли неможливо скористатися FIDO U2F. Підтримувані платформи:" + "message": "У зв'язку з обмеженнями платформи, FIDO U2F не можна використовувати в усіх програмах Bitwarden. Вам слід активувати іншого провайдера двоетапної перевірки, щоб мати змогу отримати доступ до свого облікового запису, коли неможливо скористатися FIDO U2F. Підтримувані платформи:" }, "twoFactorU2fSupportWeb": { "message": "Веб сховище і розширення браузера на комп'ютерах і ноутбуках з браузерами, що мають підтримку U2F (Chrome, Opera, Vivaldi, або Firefox з увімкненим FIDO U2F)." @@ -1525,7 +1518,7 @@ "message": "Сталася проблема при читанні ключа безпеки. Спробуйте знову." }, "twoFactorWebAuthnWarning": { - "message": "У зв'язку з обмеженнями платформи, WebAuthn не можна використовувати в усіх програмах Bitwarden. Вам слід активувати іншого провайдера двоетапної перевірки, щоб ви могли отримати доступ до свого облікового запису, коли неможливо скористатися WebAuthn. Підтримувані платформи:" + "message": "У зв'язку з обмеженнями платформи, WebAuthn не можна використовувати в усіх програмах Bitwarden. Вам слід активувати іншого провайдера двоетапної перевірки, щоб мати змогу отримати доступ до свого облікового запису, коли неможливо скористатися WebAuthn. Підтримувані платформи:" }, "twoFactorWebAuthnSupportWeb": { "message": "Веб сховище і розширення браузера на комп'ютерах і ноутбуках з браузерами, що мають підтримку WebAuthn (Chrome, Opera, Vivaldi, або Firefox з увімкненим FIDO U2F)." @@ -1534,7 +1527,7 @@ "message": "Ваш код відновлення двоетапної перевірки Bitwarden" }, "twoFactorRecoveryNoCode": { - "message": "Ви ще не увімкнули жодного провайдера двоетапної перевірки. Після того, як ви це зробите, ви можете повернутися сюди для отримання коду відновлення." + "message": "Ви ще не увімкнули жодного провайдера двоетапної перевірки. Після того, як ви це зробите, можна повернутися сюди для отримання коду відновлення." }, "printCode": { "message": "Друкувати код", @@ -1572,7 +1565,7 @@ "message": "Неактивна двоетапна перевірка" }, "inactive2faReportDesc": { - "message": "Двоетапна перевірка забезпечує додатковий рівень захисту для ваших облікових записів. Увімкніть двоетапну перевірку з використанням вбудованих засобів Bitwarden для цих облікових записів, або скористайтеся альтернативним способом." + "message": "Двоетапна перевірка забезпечує додатковий рівень захисту для ваших облікових записів. Налаштуйте двоетапну перевірку для цих облікових записів за допомогою вбудованих засобів Bitwarden, або скористайтеся альтернативним способом." }, "inactive2faFound": { "message": "Знайдено записи без двоетапної перевірки" @@ -1587,7 +1580,7 @@ } }, "noInactive2fa": { - "message": "У вашому сховищі не знайдено вебсайтів з неналаштованою двоетапною перевіркою." + "message": "У вашому сховищі не знайдено вебсайтів з неактивною двоетапною перевіркою." }, "instructions": { "message": "Інструкції" @@ -1724,7 +1717,7 @@ "message": "Вебсайт" }, "affectedUsers": { - "message": "Постраждалі користувачі" + "message": "Стосується користувачів" }, "breachOccurred": { "message": "Стався витік даних" @@ -1813,10 +1806,10 @@ "message": "Додатки" }, "premiumAccess": { - "message": "Преміум-доступ" + "message": "Premium" }, "premiumAccessDesc": { - "message": "Ви можете додати преміум-доступ для всіх учасників вашої організації за $PRICE$ /$INTERVAL$.", + "message": "Ви можете додати доступ Premium для всіх учасників вашої організації за $PRICE$ /$INTERVAL$.", "placeholders": { "price": { "content": "$1", @@ -1895,10 +1888,10 @@ "message": "З вас не буде стягнуто кошти протягом 7 днів пробного періоду." }, "creditCard": { - "message": "Кредитна карта" + "message": "Кредитна картка" }, "paypalClickSubmit": { - "message": "Натисніть кнопку PayPal для входу в свій обліковий запис PayPal, потім натисніть кнопку Відправити внизу для продовження." + "message": "Натисніть кнопку PayPal, щоб увійти до свого облікового запису PayPal, потім натисніть кнопку \"Відправити\" для продовження." }, "cancelSubscription": { "message": "Скасувати передплату" @@ -1925,7 +1918,7 @@ "message": "Ви справді хочете скасувати? Ви втратите доступ до всіх можливостей, пов'язаних з нею після завершення поточного періоду передплати." }, "canceledSubscription": { - "message": "Передплату було скасовано." + "message": "Передплату скасовано" }, "neverExpires": { "message": "Необмежений термін дії" @@ -1955,10 +1948,10 @@ "message": "Сховище" }, "addStorage": { - "message": "Додати сховище" + "message": "Збільшити сховище" }, "removeStorage": { - "message": "Вилучити сховище" + "message": "Зменшити сховище" }, "subscriptionStorage": { "message": "Ваша передплата включає всього $MAX_STORAGE$ ГБ зашифрованого сховища файлів. Ви зараз використовуєте $USED_STORAGE$.", @@ -2024,10 +2017,10 @@ } }, "gbStorageAdd": { - "message": "ГБ сховища для додавання" + "message": "ГБ додаткового обсягу сховища" }, "gbStorageRemove": { - "message": "ГБ сховища для вилучення" + "message": "ГБ обсягу сховища вилучається" }, "storageAddNote": { "message": "Додавання сховища призведе до змін суми вашої оплати і негайно буде стягнуто плату способом, зазначеним у файлі. Перша оплата буде пропорційна решті за поточний цикл оплати." @@ -2045,7 +2038,7 @@ } }, "contactSupport": { - "message": "Зв'язатися зі службою підтримки клієнтів" + "message": "Звернутися до служби підтримки клієнтів" }, "updatedPaymentMethod": { "message": "Спосіб оплати оновлено." @@ -2066,7 +2059,7 @@ } }, "uploadLicenseFilePremium": { - "message": "Для оновлення вашого облікового запису до Premium, вам необхідно вивантажити дійсний файл ліцензії." + "message": "Щоб оновити обліковий запис до Premium, вам необхідно вивантажити дійсний файл ліцензії." }, "uploadLicenseFileOrg": { "message": "Для створення організації, розміщеної на локальному хостингу, вам необхідно вивантажити дійсний файл ліцензії." @@ -2099,16 +2092,16 @@ "message": "Користувачі" }, "userSeats": { - "message": "Місця користувачів" + "message": "Місця для користувачів" }, "additionalUserSeats": { - "message": "Додаткові місця користувачів" + "message": "Додаткові місця для користувачів" }, "userSeatsDesc": { - "message": "# місць користувачів" + "message": "# місць для користувачів" }, "userSeatsAdditionalDesc": { - "message": "Ваш тарифний план постачається з $BASE_SEATS$ місць користувачів. Ви можете додати місця для користувачів по ціні $SEAT_PRICE$ за користувача на місяць.", + "message": "Ваш тарифний план включає $BASE_SEATS$ місць для користувачів. Ви можете додати нових користувачів за ціною $SEAT_PRICE$ за одного користувача на місяць.", "placeholders": { "base_seats": { "content": "$1", @@ -2121,7 +2114,7 @@ } }, "userSeatsHowManyDesc": { - "message": "Скільки місць користувачів вам необхідно? При необхідності, ви також можете пізніше додати місця користувачів." + "message": "Скільки вам потрібно місць для користувачів? Згодом ви зможете додати більше місць для користувачів за необхідності." }, "planNameFree": { "message": "Безплатно", @@ -2290,7 +2283,7 @@ "message": "Ваша нова організація готова до використання!" }, "organizationUpgraded": { - "message": "Вашу організацію було оновлено." + "message": "Організацію оновлено" }, "leave": { "message": "Покинути" @@ -2299,7 +2292,7 @@ "message": "Ви справді хочете покинути цю організацію?" }, "leftOrganization": { - "message": "Ви покинули організацію." + "message": "Ви покинули організацію" }, "defaultCollection": { "message": "Типова збірка" @@ -2326,7 +2319,7 @@ "message": "Єдиний вхід" }, "editPolicy": { - "message": "Змінити політику" + "message": "Редагувати політику" }, "groups": { "message": "Групи" @@ -2338,7 +2331,7 @@ "message": "Додати групу" }, "editGroup": { - "message": "Змінити групу" + "message": "Редагувати групу" }, "deleteGroupConfirmation": { "message": "Ви справді хочете видалити цю групу?" @@ -2380,7 +2373,7 @@ "message": "Додати збірку" }, "editCollection": { - "message": "Змінити збірку" + "message": "Редагувати збірку" }, "deleteCollectionConfirmation": { "message": "Ви справді хочете видалити цю збірку?" @@ -2488,19 +2481,19 @@ "message": "Веб сховище" }, "loggedIn": { - "message": "Вхід виконано." + "message": "Вхід виконано" }, "changedPassword": { - "message": "Пароль облікового запису змінено." + "message": "Пароль облікового запису збережено" }, "enabledUpdated2fa": { - "message": "Двоетапну перевірку увімкнено/оновлено." + "message": "Двоетапну перевірку збережено" }, "disabled2fa": { - "message": "Двоетапну перевірку вимкнено." + "message": "Двоетапну перевірку вимкнено" }, "recovered2fa": { - "message": "Обліковий запис відновлено після двоетапної перевірки." + "message": "Обліковий запис відновлено після двоетапної перевірки" }, "failedLogin": { "message": "Не вдалося виконати вхід через неправильний пароль." @@ -2509,13 +2502,13 @@ "message": "Не вдалося виконати вхід через невдалу двоетапну перевірку." }, "exportedVault": { - "message": "Експортовано сховище." + "message": "Сховище експортовано" }, "exportedOrganizationVault": { - "message": "Експортовано сховище організації." + "message": "Сховище організації експортовано" }, "editedOrgSettings": { - "message": "Налаштування організації змінено." + "message": "Налаштування організації збережено" }, "createdItemId": { "message": "Створено запис $ID$.", @@ -2869,7 +2862,7 @@ "message": "Змінюйте приналежність користувача до груп." }, "invitedUsers": { - "message": "Запрошений користувач." + "message": "Запрошені користувачі" }, "resendInvitation": { "message": "Повторно надіслати запрошення" @@ -2878,7 +2871,7 @@ "message": "Надіслати лист повторно" }, "hasBeenReinvited": { - "message": "$USER$ було повторно запрошено.", + "message": "$USER$ повторно запрошено", "placeholders": { "user": { "content": "$1", @@ -2893,7 +2886,7 @@ "message": "Підтвердити користувача" }, "hasBeenConfirmed": { - "message": "$USER$ було підтверджено.", + "message": "$USER$ підтверджено.", "placeholders": { "user": { "content": "$1", @@ -2926,7 +2919,7 @@ "message": "Знайдіть посилання для підтвердження у своїх поштовій скриньці." }, "emailVerified": { - "message": "Вашу е-пошту було підтверджено." + "message": "Адресу е-пошти підтверджено" }, "emailVerifiedFailed": { "message": "Неможливо підтвердити вашу е-пошту. Спробуйте надіслати нове повідомлення для підтвердження." @@ -2977,7 +2970,7 @@ "message": "Відновити вхід з використанням двоетапної перевірки" }, "twoStepRecoverDisabled": { - "message": "Вхід з використанням двоетапної перевірки було вимкнено." + "message": "Вхід з використанням двоетапної перевірки вимкнено." }, "learnMore": { "message": "Докладніше" @@ -2989,7 +2982,7 @@ "message": "Якщо ваш обліковий запис існує, ми надіслали вам електронне повідомлення з подальшими інструкціями." }, "deleteRecoverConfirmDesc": { - "message": "Ви відправили запит видалення облікового запису Bitwarden. Натисніть на кнопку внизу для підтвердження." + "message": "Ви відправили запит на видалення облікового запису Bitwarden. Натисніть кнопку внизу для підтвердження." }, "myOrganization": { "message": "Моя організація" @@ -3025,7 +3018,7 @@ "message": "Організацію і всі пов'язані дані було видалено." }, "organizationUpdated": { - "message": "Організацію оновлено" + "message": "Організацію збережено" }, "taxInformation": { "message": "Інформація про податки" @@ -3071,10 +3064,10 @@ "message": "Оплата з банківського рахунку доступна лише для клієнтів США. Вам необхідно буде засвідчити свій банківський рахунок. Ми зробимо два мікро-депозити протягом наступних 1-2 днів. Введіть ці суми на сторінці оплати організації для підтвердження банківського рахунку." }, "verifyBankAccountFailureWarning": { - "message": "Неможливість засвідчення банківського рахунку призведе до втраченого платежу і ваша передплата залишиться неактивною." + "message": "Якщо не вдасться підтвердити банківський рахунок, платіж не виконається, а вашу передплату буде призупинено." }, "verifiedBankAccount": { - "message": "Банківський рахунок було засвідчено." + "message": "Банківський рахунок підтверджено" }, "bankAccount": { "message": "Банківський рахунок" @@ -3112,10 +3105,10 @@ "message": "Введіть ID вашої інсталяції" }, "limitSubscriptionDesc": { - "message": "Встановіть ліміт місць для вашої передплати. Після досягнення цього ліміту ви не зможете запрошувати нових користувачів." + "message": "Встановіть обмеження кількості місць для вашої передплати. Після досягнення цього обмеження ви не зможете запросити нових користувачів." }, "maxSeatLimit": { - "message": "Максимальний ліміт місць (необов'язково)", + "message": "Обмеження кількості місць (необов'язково)", "description": "Upper limit of seats to allow through autoscaling" }, "maxSeatCost": { @@ -3130,7 +3123,7 @@ "description": "Seat = User Seat" }, "subscriptionDesc": { - "message": "Коригування вашої передплати призведе до відповідних змін у ваших рахунках. Якщо нові запрошені користувачі перевищать обмеження ваших місць, ви відразу отримаєте пропорційний рахунок для оплати за додаткових користувачів." + "message": "Коригування передплати призведе до відповідної зміни суми вашого рахунку. Якщо нові запрошені користувачі перевищать кількість передплачених місць, з вас негайно буде стягнуто пропорційну оплату за додаткових користувачів." }, "subscriptionUserSeats": { "message": "Ваша передплата дозволяє всього $COUNT$ користувачів.", @@ -3157,10 +3150,10 @@ "message": "Для отримання додаткової допомоги в керуванні вашою передплатою, будь ласка, зверніться до служби підтримки." }, "subscriptionUserSeatsUnlimitedAutoscale": { - "message": "Коригування вашої передплати призведе до відповідних змін у ваших рахунках. Якщо нові запрошені користувачі перевищать обмеження ваших місць, ви відразу отримаєте пропорційний рахунок для оплати за додаткових користувачів." + "message": "Коригування передплати призведе до відповідної зміни суми вашого рахунку. Якщо нові запрошені користувачі перевищать кількість передплачених місць, з вас негайно буде стягнуто пропорційну оплату за додаткових користувачів." }, "subscriptionUserSeatsLimitedAutoscale": { - "message": "Коригування вашої передплати призведе до відповідних змін у ваших рахунках. Якщо нові запрошені користувачі перевищать обмеження ваших місць, ви відразу отримаєте пропорційний рахунок для оплати за додаткових користувачів, доки не досягнуто вашого обмеження $MAX$ місць.", + "message": "Коригування передплати призведе до відповідної зміни суми вашого рахунку. Якщо нові запрошені користувачі перевищать кількість передплачених місць, з вас негайно буде стягнуто пропорційну оплату за додаткових користувачів, доки не буде досягнуто встановленого вами обмеження $MAX$ місць.", "placeholders": { "max": { "content": "$1", @@ -3196,7 +3189,7 @@ } }, "subscriptionMaxReached": { - "message": "Коригування вашої передплати призведе до відповідних змін у ваших рахунках. Ви не можете запросити понад $COUNT$ користувачів без збільшення передплачених місць.", + "message": "Коригування передплати призведе до відповідної зміни суми вашого рахунку. Ви не можете запросити понад $COUNT$ користувачів, не збільшивши кількість передплачених місць.", "placeholders": { "count": { "content": "$1", @@ -3205,16 +3198,16 @@ } }, "seatsToAdd": { - "message": "Додається місць" + "message": "Додаються місця" }, "seatsToRemove": { - "message": "Вилучається місць" + "message": "Вилучаються місця" }, "seatsAddNote": { - "message": "Додавання місць користувачів призведе до змін суми вашого рахунку і одразу ж буде стягнуто плату згідно зазначеного способу. Перша оплата буде пропорційною залишку поточного циклу оплати." + "message": "Додавання місць для користувачів призведе до зміни суми вашого рахунку та негайного платежу з використанням визначеного способу оплати. Перший платіж буде пропорційним залишку поточного платіжного періоду." }, "seatsRemoveNote": { - "message": "Вилучення місць користувачів призведе до змін суми вашого рахунку, що буде пропорційно розділено у вигляді кредитів за наступний цикл оплати." + "message": "Вилучення місць для користувачів призведе до зміни суми вашого рахунку та пропорційного розподілу залишку коштів у вигляді кредитів для наступного платіжного періоду." }, "adjustedSeats": { "message": "Змінено $AMOUNT$ місць користувачів.", @@ -3256,7 +3249,7 @@ "message": "Оновити" }, "upgradeOrganization": { - "message": "Оновити організацію" + "message": "Підвищити рівень організації" }, "upgradeOrganizationDesc": { "message": "Ця функція недоступна для безплатних організацій. Перемкніться на платний тарифний план для розблокування додаткових можливостей." @@ -3328,10 +3321,10 @@ "description": "ex. Date this password was updated" }, "organizationIsDisabled": { - "message": "Організацію вимкнено." + "message": "Організацію призупинено" }, "disabledOrganizationFilterError": { - "message": "Записи у вимкнених організаціях недоступні. Зверніться до власника вашої організації для отримання допомоги." + "message": "Записи у призупинених організаціях недоступні. Зверніться до власника вашої організації для отримання допомоги." }, "licenseIsExpired": { "message": "Термін дії ліцензії завершився." @@ -3407,7 +3400,7 @@ "message": "Фраза відбитка" }, "dontAskFingerprintAgain": { - "message": "Ніколи не питати про перевірку фрази відбитка для запрошених користувачів (Не рекомендовано)", + "message": "Ніколи не питати про перевірку фрази відбитка для запрошених користувачів (не рекомендовано)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "free": { @@ -3449,7 +3442,7 @@ "message": "Нам не вдалося виконати оплату з вашої картки. Будь ласка, перегляньте і проведіть оплату за рахунком, вказаним внизу." }, "inAppPurchase": { - "message": "Покупка в додатку" + "message": "Покупка в програмі" }, "cannotPerformInAppPurchase": { "message": "Ви не можете виконати цю дію під час використання способу оплати покупки в додатку." @@ -3476,7 +3469,7 @@ "message": "Зобов'язувати учасників встановлювати двоетапну перевірку." }, "twoStepLoginPolicyWarning": { - "message": "Учасники організації, які не є власниками, чи адміністратори, в яких не увімкнено двоетапну перевірку для їхніх облікових записів, будуть вилучені з організації та проінформовані поштовим повідомленням." + "message": "Учасники організації, які не є власниками чи адміністраторами, і в яких не налаштовано двоетапну перевірку для їхніх облікових записів, будуть вилучені з організації та проінформовані поштовим повідомленням." }, "twoStepLoginPolicyUserWarning": { "message": "Ви включені до організації, яка зобов'язує використання двоетапної перевірки у вашому обліковому записі. Якщо ви вимкнете всі способи двоетапної перевірки, вас буде автоматично вилучено з цієї організації." @@ -3536,7 +3529,7 @@ "message": "Стандартний тип" }, "userPreference": { - "message": "Користувацьке налаштування" + "message": "Налаштовано користувачем" }, "vaultTimeoutAction": { "message": "Дія після часу очікування сховища" @@ -3586,7 +3579,7 @@ } }, "permanentlyDeletedItemId": { - "message": "Запис $ID$ остаточно видалено.", + "message": "Запис $ID$ остаточно видалено", "placeholders": { "id": { "content": "$1", @@ -4025,20 +4018,20 @@ "message": "Власники організації та адміністратори звільняються від дотримання цієї політики." }, "personalOwnershipSubmitError": { - "message": "У зв'язку з корпоративною політикою, вам не дозволено зберігати записи до особистого сховища. Змініть налаштування власності на організацію та виберіть серед доступних збірок." + "message": "У зв'язку з політикою компанії, вам не дозволено зберігати записи до особистого сховища. Змініть налаштування власності на організацію та виберіть серед доступних збірок." }, "disableSend": { "message": "Вилучити відправлення" }, "disableSendPolicyDesc": { - "message": "Не дозволяти учасникам створювати чи змінювати відправлення.", + "message": "Не дозволяти учасникам створювати чи редагувати відправлення.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disableSendExemption": { "message": "Користувачі організації, які можуть керувати політиками організації, звільняються від дотримання цієї політики." }, "sendDisabled": { - "message": "Відправлення вимкнено", + "message": "Відправлення вилучено", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -4110,7 +4103,7 @@ "message": "У вас недостатньо повноважень для виконання цієї дії." }, "manageAllCollections": { - "message": "Керування всіма збірками" + "message": "Керувати всіма збірками" }, "createNewCollections": { "message": "Створювати нові збірки" @@ -4122,25 +4115,25 @@ "message": "Видаляти будь-яку збірку" }, "manageAssignedCollections": { - "message": "Керування призначеними збірками" + "message": "Керувати призначеними збірками" }, "editAssignedCollections": { - "message": "Редагувати призначені колекції" + "message": "Редагувати призначені збірки" }, "deleteAssignedCollections": { - "message": "Видаляти призначені колекції" + "message": "Видаляти призначені збірки" }, "manageGroups": { "message": "Керування групами" }, "managePolicies": { - "message": "Керування політиками" + "message": "Керувати політиками" }, "manageSso": { "message": "Керування SSO" }, "manageUsers": { - "message": "Керування користувачами" + "message": "Керувати користувачами" }, "manageResetPassword": { "message": "Керувати скиданням пароля" diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index a031f4b0290..de91c5523e2 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "Show a recognizable image next to each login." }, - "enableGravatars": { - "message": "Show Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "Use avatar images loaded from gravatar.com." - }, "enableFullWidth": { "message": "Display full width layout", "description": "Allows scaling the web vault UI's width" diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 96a036ebc67..81f84b4d1cc 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -62,7 +62,7 @@ "message": "过期日" }, "securityCode": { - "message": "安全码(CVV)" + "message": "安全码 (CVV)" }, "identityName": { "message": "身份名称" @@ -137,13 +137,13 @@ "message": "博士" }, "expirationMonth": { - "message": "过期月份" + "message": "到期月份" }, "expirationYear": { - "message": "过期年份" + "message": "到期年份" }, "authenticatorKeyTotp": { - "message": "验证器密钥(TOTP)" + "message": "验证器密钥 (TOTP)" }, "folder": { "message": "文件夹" @@ -425,7 +425,7 @@ "message": "我" }, "myVault": { - "message": "密码库" + "message": "我的密码库" }, "allVaults": { "message": "所有密码库" @@ -467,7 +467,7 @@ "message": "您确定要删除此附件吗?" }, "attachmentSaved": { - "message": "附件已保存。" + "message": "附件已保存" }, "file": { "message": "文件" @@ -485,7 +485,7 @@ "message": "项目已添加" }, "editedItem": { - "message": "项目已编辑" + "message": "项目已保存" }, "movedItemToOrg": { "message": "$ITEMNAME$ 已移动到 $ORGNAME$", @@ -519,7 +519,7 @@ "message": "删除附件" }, "deleteItemConfirmation": { - "message": "您确定要删除此项目吗?" + "message": "您确定要将其发送到回收站吗?" }, "deletedItem": { "message": "项目已发送到回收站" @@ -534,7 +534,7 @@ "message": "您确定要覆盖当前密码吗?" }, "editedFolder": { - "message": "文件夹已编辑" + "message": "文件夹已保存" }, "addedFolder": { "message": "文件夹已添加" @@ -567,7 +567,7 @@ "message": "否" }, "loginOrCreateNewAccount": { - "message": "登录或者新建一个账户来访问您的安全密码库。" + "message": "登录或者创建一个账户来访问您的安全密码库。" }, "loginWithDevice": { "message": "使用设备登录" @@ -594,7 +594,7 @@ "message": "提交" }, "emailAddressDesc": { - "message": "您将使用您的电子邮件地址登录。" + "message": "使用您的电子邮件地址登录。" }, "yourName": { "message": "您的姓名" @@ -612,10 +612,10 @@ "message": "主密码忘记后,将无法恢复!" }, "masterPassHintDesc": { - "message": "主密码提示可以在你忘记密码时帮你回忆起来。" + "message": "主密码提示可以在您忘记密码时帮您回忆起来。" }, "reTypeMasterPass": { - "message": "确认主密码" + "message": "再次输入主密码" }, "masterPassHint": { "message": "主密码提示(可选)" @@ -630,7 +630,7 @@ "message": "密码提示" }, "enterEmailToGetHint": { - "message": "请输入您账号的电子邮件地址来接收主密码提示。" + "message": "请输入您账户的电子邮件地址来接收主密码提示。" }, "getMasterPasswordHint": { "message": "获取主密码提示" @@ -645,7 +645,7 @@ "message": "必须填写主密码。" }, "confirmMasterPasswordRequired": { - "message": "必须填写确认主密码。" + "message": "必须再次输入主密码。" }, "masterPasswordMinlength": { "message": "主密码至少需要 8 个字符。" @@ -778,13 +778,13 @@ "message": "两步登录选项" }, "recoveryCodeDesc": { - "message": "失去对您所有的双重身份验证设备的访问?请使用您的恢复代码来禁用您账户中所有的两步登录提供程序。" + "message": "失去对您所有的双重身份验证设备的访问?请使用您的恢复代码来停用您账户中所有的两步登录提供程序。" }, "recoveryCodeTitle": { "message": "恢复代码" }, "authenticatorAppTitle": { - "message": "认证器 App" + "message": "验证器应用" }, "authenticatorAppDesc": { "message": "使用验证器应用(例如 Authy 或 Google Authenticator)来生成基于时间的验证码。", @@ -805,7 +805,7 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "u2fDesc": { - "message": "使用任何支持 FIDO U2F 的安全钥匙来访问您的账户。" + "message": "使用任何 FIDO U2F 兼容的安全钥匙访问您的账户。" }, "u2fTitle": { "message": "FIDO U2F 安全钥匙" @@ -814,7 +814,7 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "使用任何启用了 WebAuthn 的安全钥匙访问您的账户。" + "message": "使用任何 WebAuthn 兼容的安全钥匙访问您的帐户。" }, "webAuthnMigrated": { "message": "(迁移自 FIDO)" @@ -888,13 +888,13 @@ "message": "警告" }, "confirmVaultExport": { - "message": "确认导出密码库" + "message": "确认密码库导出" }, "exportWarningDesc": { "message": "导出的密码库数据包含未加密格式。您不应该通过不安全的渠道(例如电子邮件)来存储或发送导出的文件。用完后请立即将其删除。" }, "encExportKeyWarningDesc": { - "message": "此导出将使用您账户的加密密钥来加密您的数据。 如果您曾经轮换过账户的加密密钥,您应将其重新导出,否则您将无法解密导出的文件。" + "message": "此导出使用您账户的加密密钥来加密您的数据。如果您曾经轮换过账户的加密密钥,您应将其重新导出,否则您将无法解密导出的文件。" }, "encExportAccountWarningDesc": { "message": "账户加密密钥对每个 Bitwarden 用户账户都是唯一的,所以您不能将加密的导出导入到另一个账户。" @@ -951,7 +951,7 @@ "message": "此文件受密码保护。请输入文件密码以导入数据。" }, "exportSuccess": { - "message": "已经导出您的密码库数据。" + "message": "密码库数据已导出" }, "passwordGenerator": { "message": "密码生成器" @@ -1013,10 +1013,10 @@ "description": "To clear something out. Example: To clear browser history." }, "accountUpdated": { - "message": "帐户已更新" + "message": "账户已保存" }, "changeEmail": { - "message": "更改电子邮件" + "message": "修改电子邮件地址" }, "changeEmailTwoFactorWarning": { "message": "继续操作将更改您的账户电子邮件地址。这不会更改用于双重身份验证的电子邮件地址。您可以在两步登录设置中更改它。" @@ -1040,7 +1040,7 @@ "message": "接下来将会注销您当前的会话,要求您重新登录。其他设备上的活动会话可能会继续保持最多一小时。" }, "emailChanged": { - "message": "电子邮件已更改" + "message": "电子邮件已保存" }, "logBackIn": { "message": "请重新登录。" @@ -1052,7 +1052,7 @@ "message": "修改主密码" }, "masterPasswordChanged": { - "message": "主密码已修改" + "message": "主密码已保存" }, "currentMasterPass": { "message": "当前主密码" @@ -1094,7 +1094,7 @@ "message": "更改 KDF" }, "encKeySettingsChanged": { - "message": "加密密钥设置已更改" + "message": "加密密钥设置已保存" }, "dangerZone": { "message": "危险操作区" @@ -1112,7 +1112,7 @@ "message": "接下来将会注销您当前的会话,并要求您重新登录。如果有设置两步登录,也需要重新认证。其他设备上的活动会话可能会继续保持最多一小时。" }, "sessionsDeauthorized": { - "message": "已取消会话授权" + "message": "已取消所有会话授权" }, "purgeVault": { "message": "清空密码库" @@ -1133,7 +1133,7 @@ "message": "清空密码库是永久性的。不能被撤消。" }, "vaultPurged": { - "message": "你的密码库已经被清空。" + "message": "密码库已清空。" }, "deleteAccount": { "message": "删除账户" @@ -1160,13 +1160,13 @@ "message": "导入数据" }, "importError": { - "message": "导入错误" + "message": "导入出错" }, "importErrorDesc": { "message": "您尝试导入的数据有问题。请解决如下列出的源文件中的错误,然后重试。" }, "importSuccess": { - "message": "数据已经成功导入到密码库" + "message": "数据导入成功" }, "importWarning": { "message": "您正在将数据导入到 $ORGANIZATION$。您的数据可能会与此组织中的成员共享。是否继续?", @@ -1196,7 +1196,7 @@ "message": "选择文件" }, "noFileChosen": { - "message": "没有选择文件" + "message": "未选择文件" }, "orCopyPasteFileContents": { "message": "或复制/粘贴要导入的文件内容" @@ -1221,7 +1221,7 @@ "message": "自定义您的网页版密码库。" }, "preferencesUpdated": { - "message": "偏好设置已更新" + "message": "偏好设置已保存" }, "language": { "message": "语言" @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "在每个登录旁显示一个可识别的图像。" }, - "enableGravatars": { - "message": "显示 Gravatar 头像", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "使用从 gravatar.com 加载的头像图像。" - }, "enableFullWidth": { "message": "显示全宽布局", "description": "Allows scaling the web vault UI's width" @@ -1289,7 +1282,7 @@ } }, "domainsUpdated": { - "message": "域名已更新" + "message": "域名已保存" }, "twoStepLogin": { "message": "两步登录" @@ -1314,7 +1307,7 @@ "message": "启用" }, "enabled": { - "message": "启用" + "message": "已启用" }, "restoreAccess": { "message": "恢复访问权限" @@ -1387,13 +1380,13 @@ "message": "如果您要把它添加到另一个设备,下面是您的验证器应用所需要的二维码(或密钥)。" }, "twoStepDisableDesc": { - "message": "您确定要禁用此两步登录提供程序吗?" + "message": "您确定要停用此两步登录提供程序吗?" }, "twoStepDisabled": { - "message": "此两步登录提供程序已禁用。" + "message": "此两步登录提供程序已停用。" }, "twoFactorYubikeyAdd": { - "message": "将新的 YubiKey 添加到您的账户" + "message": "添加一个新的 YubiKey 到您的帐户" }, "twoFactorYubikeyPlugIn": { "message": "将 YubiKey 插入您电脑的 USB 端口。" @@ -1483,7 +1476,7 @@ "message": "发送电子邮件" }, "twoFactorU2fAdd": { - "message": "在您的账户中添加 FIDO U2F 安全钥匙" + "message": "添加一个 FIDO U2F 安全钥匙到您的帐户" }, "removeU2fConfirmation": { "message": "您确认要删除这个安全钥匙吗?" @@ -1513,28 +1506,28 @@ "message": "由于平台的限制,FIDO U2F 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 FIDO U2F 时可以访问您的账户。支持的平台:" }, "twoFactorU2fSupportWeb": { - "message": "桌面/笔记本电脑上启用了 U2F 的浏览器(Chrome、Opera、Vivaldi 或启用了 FIDO U2F 的 Firefox)中的网页版密码库和浏览器扩展。" + "message": "桌面/笔记本电脑上支持 U2F 的浏览器(启用了 FIDO U2F 的 Chrome、Opera、Vivaldi 或 Firefox)中的网页版密码库和浏览器扩展。" }, "twoFactorU2fWaiting": { "message": "等待您按下安全钥匙上的按钮" }, "twoFactorU2fClickSave": { - "message": "单击下面的「保存」按钮,以启用此安全钥匙进行两步登录。" + "message": "单击下面的「保存」按钮,以启用此安全钥匙用于两步登录。" }, "twoFactorU2fProblemReadingTryAgain": { "message": "读取安全钥匙时出现问题,请再试一次。" }, "twoFactorWebAuthnWarning": { - "message": "由于平台限制,无法在所有 Bitwarden 应用程序中使用 WebAuthn。您应该启用另一种两步登录方式,以便在 WebAuthn 无法使用时可以访问您的账户。支持的平台有:" + "message": "由于平台限制,无法在所有 Bitwarden 应用程序中使用 WebAuthn。您应该启用另一个两步登录提供程序,以便在 WebAuthn 无法使用时可以访问您的账户。支持的平台有:" }, "twoFactorWebAuthnSupportWeb": { - "message": "桌面/笔记本电脑上启用了 WebAuthn 的浏览器(Chrome、Opera、Vivaldi 或启用了 FIDO U2F 的 Firefox)上的网页密码库和浏览器扩展。" + "message": "桌面/笔记本电脑上支持 WebAuthn 的浏览器(启用了 FIDO U2F 的 Chrome、Opera、Vivaldi 或 Firefox)中的网页密码库和浏览器扩展。" }, "twoFactorRecoveryYourCode": { "message": "您的 Bitwarden 两步登录恢复代码" }, "twoFactorRecoveryNoCode": { - "message": "您尚未启用任何两步登录提供程序。在启用了一个两步登录提供程序后,您可以在这里检查恢复代码。" + "message": "您尚未设置任何两步登录提供程序。在启用了一个两步登录提供程序后,请返回这里检查恢复代码。" }, "printCode": { "message": "打印代码", @@ -1569,16 +1562,16 @@ "message": "你的密码库中没有带不安全 URI 的项目。" }, "inactive2faReport": { - "message": "未设置两步登录" + "message": "未激活两步登录" }, "inactive2faReportDesc": { "message": "两步登录为您的账户增加了一层保护。使用 Bitwarden 验证器或其他方式为这些账户开启两步登录。" }, "inactive2faFound": { - "message": "发现未启用 2FA 的登录项目" + "message": "发现未启用两步登录的登录项目" }, "inactive2faFoundDesc": { - "message": "我们在您的密码库中发现 $COUNT$ 个网站可能没有配置双重身份验证(根据 2fa.directory)。为了进一步保护这些账户,您应该启用双重身份验证。", + "message": "我们在您的密码库中发现 $COUNT$ 个网站可能没有配置两步登录(根据 2fa.directory)。为了进一步保护这些账户,您应该设置两步登录。", "placeholders": { "count": { "content": "$1", @@ -1587,7 +1580,7 @@ } }, "noInactive2fa": { - "message": "没有在您的密码库发现未使用双重身份认证的网站。" + "message": "没有在您的密码库发现未配置两步登录的网站。" }, "instructions": { "message": "说明" @@ -1798,7 +1791,7 @@ "message": "优先客户支持。" }, "premiumSignUpFuture": { - "message": "所有未来的高级功能,即将推出!" + "message": "未来会增加更多高级功能。敬请期待!" }, "premiumPrice": { "message": "每年只需 $PRICE$ !", @@ -1829,7 +1822,7 @@ } }, "additionalStorageGb": { - "message": "附加存储(GB)" + "message": "附加存储 (GB)" }, "additionalStorageGbDesc": { "message": "# GB 附加存储" @@ -1925,7 +1918,7 @@ "message": "您确定要取消吗?在本次计费周期结束后,您将无法使用此订阅的所有功能。" }, "canceledSubscription": { - "message": "订阅已取消。" + "message": "订阅已取消" }, "neverExpires": { "message": "永不过期" @@ -1955,7 +1948,7 @@ "message": "存储" }, "addStorage": { - "message": "添加存储" + "message": "添加存储空间" }, "removeStorage": { "message": "移除存储空间" @@ -2004,7 +1997,7 @@ "description": "Payment/credit transactions." }, "noTransactions": { - "message": "无交易记录" + "message": "无交易记录。" }, "chargeNoun": { "message": "费用", @@ -2290,16 +2283,16 @@ "message": "你的组织准备好了!" }, "organizationUpgraded": { - "message": "您的组织已升级。" + "message": "组织已升级" }, "leave": { - "message": "离开" + "message": "退出" }, "leaveOrganizationConfirmation": { - "message": "您确定要离开此组织吗?" + "message": "您确定要退出这个组织吗?" }, "leftOrganization": { - "message": "您已经离开该组织。" + "message": "您已经退出该组织。" }, "defaultCollection": { "message": "默认集合" @@ -2323,7 +2316,7 @@ "message": "策略" }, "singleSignOn": { - "message": "单点登录 (SSO)" + "message": "单点登录" }, "editPolicy": { "message": "编辑策略" @@ -2353,7 +2346,7 @@ "message": "撤销成员后,他们将不再具有对组织数据的访问权限。要快速恢复此成员的访问权限,请转到「已撤销」选项卡。" }, "removeUserConfirmationKeyConnector": { - "message": "警告!此用户需要 Key Connector 来管理他们的加密。从您的组织中移除此用户将永久禁用他们的账户。此操作无法撤消。您要继续吗?" + "message": "警告!此用户需要 Key Connector 来管理他们的加密。从您的组织中移除此用户将永久停用他们的账户。此操作无法撤消。您要继续吗?" }, "externalId": { "message": "外部 ID" @@ -2491,16 +2484,16 @@ "message": "已登录。" }, "changedPassword": { - "message": "账户密码已更改。" + "message": "账户密码已保存" }, "enabledUpdated2fa": { - "message": "两步登录已启用/更新。" + "message": "两步登录已保存" }, "disabled2fa": { - "message": "两步登录已禁用。" + "message": "两步登录已停用" }, "recovered2fa": { - "message": "已从两步登录中恢复账户。" + "message": "账户已从两步登录中恢复" }, "failedLogin": { "message": "登录失败,密码不正确。" @@ -2509,13 +2502,13 @@ "message": "登录失败,两步登录不正确。" }, "exportedVault": { - "message": "已导出密码库。" + "message": "密码库已导出" }, "exportedOrganizationVault": { - "message": "已导出组织密码库。" + "message": "组织密码库已导出" }, "editedOrgSettings": { - "message": "已编辑组织设置。" + "message": "组织设置已保存" }, "createdItemId": { "message": "已创建项目 $ID$。", @@ -2536,7 +2529,7 @@ } }, "deletedItemId": { - "message": "项目 $ID$ 已发送到回收站。", + "message": "发送项目 $ID$ 到回收站。", "placeholders": { "id": { "content": "$1", @@ -2692,7 +2685,7 @@ } }, "removedUserId": { - "message": "已删除用户 $ID$。", + "message": "已移除用户 $ID$。", "placeholders": { "id": { "content": "$1", @@ -2893,7 +2886,7 @@ "message": "确认用户" }, "hasBeenConfirmed": { - "message": "已确认 $USER$。", + "message": "$USER$ 已确认。", "placeholders": { "user": { "content": "$1", @@ -2926,13 +2919,13 @@ "message": "检查您的电子邮件收件箱以获取验证链接。" }, "emailVerified": { - "message": "您的电子邮件已验证。" + "message": "账户电子邮件已验证" }, "emailVerifiedFailed": { "message": "无法验证您的电子邮件。尝试发送新的验证电子邮件。" }, "emailVerificationRequired": { - "message": "需要验证电子邮件地址" + "message": "需要验证电子邮件" }, "emailVerificationRequiredDesc": { "message": "您必须验证您的电子邮件才能使用此功能。" @@ -2968,16 +2961,16 @@ } }, "rememberEmail": { - "message": "记住电子邮件" + "message": "记住电子邮件地址" }, "recoverAccountTwoStepDesc": { - "message": "如果您无法通过常规的两步登录方式访问您的账户,您可以使用两步登录恢复代码来禁用账户上的所有两步登录提供程序。" + "message": "如果您无法通过常规的两步登录方式访问您的账户,您可以使用两步登录恢复代码来停用账户上的所有两步登录提供程序。" }, "recoverAccountTwoStep": { "message": "恢复账户两步登录" }, "twoStepRecoverDisabled": { - "message": "您的账户已禁用两步登录。" + "message": "两步登录已在您的账户中停用。" }, "learnMore": { "message": "进一步了解" @@ -3025,7 +3018,7 @@ "message": "该组织和所有相关数据已删除。" }, "organizationUpdated": { - "message": "组织已更新" + "message": "组织已保存" }, "taxInformation": { "message": "税务信息" @@ -3074,7 +3067,7 @@ "message": "验证银行账户失败将会错过支付,您的订阅将失效。" }, "verifiedBankAccount": { - "message": "您的银行账户已验证。" + "message": "银行账户已验证" }, "bankAccount": { "message": "银行账户" @@ -3115,7 +3108,7 @@ "message": "为您的订阅设置席位限制。达到此限制后,您将无法邀请新的用户。" }, "maxSeatLimit": { - "message": "最大席位限制(可选)", + "message": "席位限制(可选)", "description": "Upper limit of seats to allow through autoscaling" }, "maxSeatCost": { @@ -3328,10 +3321,10 @@ "description": "ex. Date this password was updated" }, "organizationIsDisabled": { - "message": "该组织已被禁用。" + "message": "组织已暂停" }, "disabledOrganizationFilterError": { - "message": "无法访问已禁用组织中的项目。请联系您的组织所有者获取协助。" + "message": "无法访问已暂停组织中的项目。请联系您的组织所有者获取帮助。" }, "licenseIsExpired": { "message": "授权已过期" @@ -3479,7 +3472,7 @@ "message": "非所有者或管理员并且其账户未启用两步登录的组织成员将从组织中移除,并将收到一封关于此更改的电子邮件通知。" }, "twoStepLoginPolicyUserWarning": { - "message": "您的组织要求您在您的用户账户上启用两步登录。如果您禁用所有两步登录提供程序,您将被自动从这些组织中移除。" + "message": "您的组织要求您在您的用户账户上启用两步登录。如果您停用所有两步登录提供程序,您将被自动从这些组织中移除。" }, "passwordGeneratorPolicyDesc": { "message": "设置密码生成器要求。" @@ -3562,7 +3555,7 @@ "message": "永久删除" }, "permanentlyDeleteSelected": { - "message": "永久删除选中项目" + "message": "永久删除所选" }, "permanentlyDeleteItem": { "message": "永久删除项目" @@ -3571,10 +3564,10 @@ "message": "您确定要永久删除此项目吗?" }, "permanentlyDeletedItem": { - "message": "已永久删除项目" + "message": "项目已永久删除" }, "permanentlyDeletedItems": { - "message": "已永久删除项目" + "message": "项目已永久删除" }, "permanentlyDeleteSelectedItemsDesc": { "message": "您选择了要永久删除 $COUNT$ 个项目。确定要永久删除所有这些项目吗?", @@ -3586,7 +3579,7 @@ } }, "permanentlyDeletedItemId": { - "message": "已永久删除项目 $ID$。", + "message": "项目 $ID$ 已永久删除", "placeholders": { "id": { "content": "$1", @@ -3598,16 +3591,16 @@ "message": "恢复" }, "restoreSelected": { - "message": "恢复选中的项目" + "message": "恢复所选" }, "restoreItem": { "message": "恢复项目" }, "restoredItem": { - "message": "已恢复项目" + "message": "项目已恢复" }, "restoredItems": { - "message": "已恢复项目" + "message": "项目已恢复" }, "restoreItemConfirmation": { "message": "确定要恢复此项目吗?" @@ -3625,7 +3618,7 @@ } }, "restoredItemId": { - "message": "已恢复项目 $ID$。", + "message": "项目 $ID$ 已恢复", "placeholders": { "id": { "content": "$1", @@ -3670,7 +3663,7 @@ "message": "要使用您组织的单点登录门户登录。请首先输入您组织的标识符。" }, "enterpriseSingleSignOn": { - "message": "企业单点登录(SSO)" + "message": "企业单点登录" }, "ssoHandOff": { "message": "您现在可以在扩展中关闭此标签页并继续。" @@ -3736,7 +3729,7 @@ "message": "文本" }, "createSend": { - "message": "创建新 Send", + "message": "创建 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -3744,11 +3737,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send 已创建", + "message": "Send 已保存", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send 已编辑", + "message": "Send 已保存", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { @@ -3792,7 +3785,7 @@ "message": "当前访问次数" }, "sendPasswordDesc": { - "message": "可选,用户需要提供密码才能访问此 Send。", + "message": "可选。用户需要提供密码才能访问此 Send。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNotesDesc": { @@ -3826,7 +3819,7 @@ "message": "对收件人隐藏我的电子邮件地址。" }, "disableThisSend": { - "message": "禁用此 Send 以阻止任何人访问它。", + "message": "停用此 Send 则任何人无法访问它。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "allSends": { @@ -4001,7 +3994,7 @@ } }, "emergencyApproved": { - "message": "紧急访问已批准。" + "message": "紧急访问已批准" }, "emergencyRejected": { "message": "紧急访问已拒绝" @@ -4146,7 +4139,7 @@ "message": "管理密码重置" }, "disableRequiredError": { - "message": "您必须先手动禁用 $POLICYNAME$ 策略,然后才能禁用此策略。", + "message": "您必须先手动切换 $POLICYNAME$ 策略,然后才能停用此策略。", "placeholders": { "policyName": { "content": "$1", @@ -4158,13 +4151,13 @@ "message": "组织策略正在影响您的所有权选项。" }, "personalOwnershipPolicyInEffectImports": { - "message": "组织策略已禁用将项目导入您的个人密码库。" + "message": "组织策略已阻止将项目导入您的个人密码库。" }, "personalOwnershipCheckboxDesc": { - "message": "为组织用户禁用个人所有权" + "message": "移除组织用户的个人所有权" }, "textHiddenByDefault": { - "message": "访问 Send 时,默认将隐藏文本", + "message": "访问此 Send 时,默认隐藏文本内容", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNameDesc": { @@ -4175,7 +4168,7 @@ "message": "您想要发送的文本。" }, "sendFileDesc": { - "message": "您要发送的文件。" + "message": "您想要发送的文件。" }, "copySendLinkOnSave": { "message": "保存时复制链接到剪贴板以便分享此 Send。" @@ -4437,16 +4430,16 @@ "message": "浅色" }, "confirmSelected": { - "message": "确认选择" + "message": "确认所选" }, "bulkConfirmStatus": { "message": "批量操作状态" }, "bulkConfirmMessage": { - "message": "确认成功。" + "message": "确认成功" }, "bulkReinviteMessage": { - "message": "重新邀请成功。" + "message": "重新邀请成功" }, "bulkRemovedMessage": { "message": "移除成功" @@ -4476,7 +4469,7 @@ "message": "错误" }, "resetPasswordManageUsers": { - "message": "必须启用管理密码重置权限后才能启用管理用户" + "message": "管理用户也必须获得管理密码重置权限" }, "setupProvider": { "message": "提供商设置" @@ -4491,7 +4484,7 @@ "message": "提供商名称" }, "providerSetup": { - "message": "已完成提供商设置。" + "message": "提供商设置成功" }, "clients": { "message": "客户" @@ -4509,7 +4502,7 @@ "message": "服务用户可以访问和管理所有客户组织。" }, "providerInviteUserDesc": { - "message": "通过在下面输入他们的 Bitwarden 账户电子邮件地址,邀请新用户加入您的提供商。如果他们还没有 Bitwarden 账户,将被提示创建一个新的账户。" + "message": "通过在下面输入他们的 Bitwarden 账户电子邮件地址,以邀请新用户加入您的提供商。如果他们还没有 Bitwarden 账户,将提示他们创建一个。" }, "joinProvider": { "message": "加入提供商" @@ -4524,16 +4517,16 @@ "message": "管理员确认您的成员资格后,您就可以访问此提供商了。届时我们会向您发送一封电子邮件。" }, "providerUsersNeedConfirmed": { - "message": "您有用户接受了他们的邀请,但仍然需要确认。用户在确认之前将无法访问提供商。" + "message": "您有用户已接受他们的邀请,但仍然需要确认。用户在确认之前将无法访问提供商。" }, "provider": { "message": "提供商" }, "newClientOrganization": { - "message": "新客户组织" + "message": "新建客户组织" }, "newClientOrganizationDesc": { - "message": "创建一个新的客户组织,该组织将作为提供商与你关联。您可以访问和管理这个组织。" + "message": "创建一个新的客户组织,该组织将作为提供商与你关联。您将可以访问和管理这个组织。" }, "addExistingOrganization": { "message": "添加现有组织" @@ -4567,10 +4560,10 @@ } }, "providerIsDisabled": { - "message": "提供商已被禁用。" + "message": "提供商已停用" }, "providerUpdated": { - "message": "提供商已更新" + "message": "提供商已保存" }, "yourProviderIs": { "message": "您的提供商是 $PROVIDER$。他们对您的组织拥有管理和计费权限。", @@ -4597,16 +4590,16 @@ "message": "添加" }, "updatedMasterPassword": { - "message": "已更新主密码。" + "message": "主密码已保存" }, "updateMasterPassword": { "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问此密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "masterPasswordInvalidWarning": { - "message": "您的主密码不符合此组织的策略要求。要加入此组织,您必须现在更新您的主密码。继续操作将您注销当前会话,要求您重新登录。在其他设备上的活动会话可能继续活动长达一小时。" + "message": "您的主密码不符合此组织的策略要求。要加入此组织,必须立即更新您的主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "maximumVaultTimeout": { "message": "密码库超时时间" @@ -4775,7 +4768,7 @@ "message": "签名身份验证请求" }, "ssoSettingsSaved": { - "message": "单点登录配置已保存。" + "message": "单点登录配置已保存" }, "sponsoredFamilies": { "message": "免费 Bitwarden 家庭" @@ -4814,16 +4807,16 @@ "message": "输入您的个人电子邮件以兑换 Bitwarden 家庭" }, "sponsoredFamiliesLeaveCopy": { - "message": "如果您移除邀请或被赞助组织移除,您的家庭赞助将在下一个续费日到期。" + "message": "如果您移除邀请或邀请被赞助组织移除,您的家庭赞助将在下一个续费日到期。" }, "acceptBitwardenFamiliesHelp": { "message": "接受现有组织的邀请或创建一个新的家庭组织。" }, "setupSponsoredFamiliesLoginDesc": { - "message": "您已被邀请加入 Bitwarden 家庭计划组织。要继续,您需要登录到接受邀请的账户。" + "message": "您已被邀请加入免费的 Bitwarden 家庭计划组织。要继续,您需要登录到接收邀请的账户。" }, "sponsoredFamiliesAcceptFailed": { - "message": "无法接受邀请。请重新发送来自您企业账户的邀请邮件,然后重试。" + "message": "无法接受邀请。请通过您的企业账户重新发送邀请邮件,然后重试。" }, "sponsoredFamiliesAcceptFailedShort": { "message": "无法接受邀请。$DESCRIPTION$", @@ -4844,7 +4837,7 @@ "message": "已兑换" }, "redeemedAccount": { - "message": "已兑换账户" + "message": "账户已兑换" }, "revokeAccount": { "message": "撤销账户 $NAME$", @@ -4856,7 +4849,7 @@ } }, "resendEmailLabel": { - "message": "重新发送赞助邮件到 $NAME$", + "message": "重新给 $NAME$ 发送赞助邮件", "placeholders": { "name": { "content": "$1", @@ -4928,13 +4921,13 @@ } }, "leaveOrganization": { - "message": "离开组织" + "message": "退出组织" }, "removeMasterPassword": { "message": "移除主密码" }, "removedMasterPassword": { - "message": "主密码已移除。" + "message": "主密码已移除" }, "allowSso": { "message": "允许 SSO 身份验证" @@ -4973,19 +4966,19 @@ "message": "「SSO 登录和 Key Connector 解密」已启用。此策略仅适用于所有者和管理员。" }, "enabledSso": { - "message": "已启用 SSO" + "message": "SSO 已启用" }, "disabledSso": { "message": "已禁用 SSO" }, "enabledKeyConnector": { - "message": "已启用 Key Connector" + "message": "Key Connector 已启用" }, "disabledKeyConnector": { - "message": "已禁用 Key Connector" + "message": "Key Connector 已停用" }, "keyConnectorWarning": { - "message": "一旦成员开始使用 Key Connector,您的组织就无法恢复到使用主密码解密。仅当您可以轻松地部署和管理密钥服务器时才继续。" + "message": "一旦成员开始使用 Key Connector,您的组织就无法恢复为使用主密码解密。仅当您可以轻松地部署和管理密钥服务器时才继续操作。" }, "migratedKeyConnector": { "message": "已迁移到 Key Connector" @@ -5057,7 +5050,7 @@ "message": "要在您自己的服务器上设置您的组织,您需要上传您的许可证文件。要为您的自托管组织提供免费家庭计划和高级计费功能,您需要设置计费同步。" }, "billingSyncApiKeyRotated": { - "message": "令牌已轮换。" + "message": "令牌已轮换" }, "billingSync": { "message": "计费同步" @@ -5129,13 +5122,13 @@ "message": "您的会话已超时。请返回并尝试重新登录。" }, "exportingPersonalVaultTitle": { - "message": "导出个人密码库" + "message": "正在导出个人密码库" }, "exportingOrganizationVaultTitle": { - "message": "导出组织密码库" + "message": "正在导出组织密码库" }, "exportingPersonalVaultDescription": { - "message": "仅会导出与 $EMAIL$ 关联的个人密码库。组织密码库的项目不会导出。", + "message": "仅会导出与 $EMAIL$ 关联的个人密码库项目。组织密码库的项目不会导出。", "placeholders": { "email": { "content": "$1", @@ -5153,7 +5146,7 @@ } }, "accessDenied": { - "message": "访问被拒绝。您没有查看此页面的权限。" + "message": "访问被拒绝。您没有权限查看此页面。" }, "masterPassword": { "message": "主密码" @@ -5165,7 +5158,7 @@ "message": "密钥" }, "billingHistory": { - "message": "账单历史" + "message": "费用历史记录" }, "backToReports": { "message": "返回到报告" @@ -5294,13 +5287,13 @@ "message": "启用设备验证" }, "deviceVerificationDesc": { - "message": "启用后,在未识别的设备上登录时,验证码会发送到您的电子邮箱" + "message": "登录未识别的设备时,验证码会发送到您的电子邮件地址" }, "updatedDeviceVerification": { - "message": "设备验证已更新" + "message": "已更新设备验证" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { - "message": "您确定要启用设备验证吗?验证码邮件将发送到 $EMAIL$", + "message": "您确定要开启设备验证吗?验证码邮件将发送到 $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -5362,7 +5355,7 @@ "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "scimSettingsSaved": { - "message": "已成功保存 SCIM 设置", + "message": "SCIM 设置已保存", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "inputRequired": { diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index e84c97623f9..511f99a5f6e 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1235,13 +1235,6 @@ "faviconDesc": { "message": "在每個登入資料旁顯示一個可辨識的圖片。" }, - "enableGravatars": { - "message": "顯示 Gravatars", - "description": "Use avatar images loaded from gravatar.com." - }, - "enableGravatarsDesc": { - "message": "載入 gravatar.com 上的頭像圖片。" - }, "enableFullWidth": { "message": "顯示全寬度佈局", "description": "Allows scaling the web vault UI's width" From 6cad2534423a6e781482fe50fe604c99d61e1b22 Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Fri, 28 Oct 2022 15:09:07 +0200 Subject: [PATCH 07/83] Revert "[PS-1465] Fix #2806 - The "Import Data" page's file selector button cannot be translated (#3502)" (#3900) This reverts commit 768de03269882d0cd5f3b0d7803c819eaa219010. --- .../app/tools/import-export/import.component.html | 13 ------------- .../src/app/tools/import-export/import.component.ts | 6 ------ 2 files changed, 19 deletions(-) diff --git a/apps/web/src/app/tools/import-export/import.component.html b/apps/web/src/app/tools/import-export/import.component.html index 15c1109654d..ba2eb69925c 100644 --- a/apps/web/src/app/tools/import-export/import.component.html +++ b/apps/web/src/app/tools/import-export/import.component.html @@ -291,25 +291,12 @@
-
-
- - {{ this.fileSelected ? this.fileSelected.name : ("noFileChosen" | i18n) }} -
diff --git a/apps/web/src/app/tools/import-export/import.component.ts b/apps/web/src/app/tools/import-export/import.component.ts index 5c062b09fb9..3f362232855 100644 --- a/apps/web/src/app/tools/import-export/import.component.ts +++ b/apps/web/src/app/tools/import-export/import.component.ts @@ -25,7 +25,6 @@ export class ImportComponent implements OnInit, OnDestroy { importOptions: ImportOption[]; format: ImportType = null; fileContents: string; - fileSelected: File; formPromise: Promise; loading = false; importBlockedByPolicy$ = this.policyService.policyAppliesToActiveUser$( @@ -185,11 +184,6 @@ export class ImportComponent implements OnInit, OnDestroy { }); } - setSelectedFile(event: Event) { - const fileInputEl = event.target; - this.fileSelected = fileInputEl.files.length > 0 ? fileInputEl.files[0] : null; - } - private async error(error: Error) { await Swal.fire({ heightAuto: false, From 2ffa5811f30602683a63ae1857d597d6b16d2bbd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Oct 2022 15:34:38 +0200 Subject: [PATCH 08/83] Autosync the updated translations (#3919) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/de/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index e1f067d12f7..7aea585b047 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -342,7 +342,7 @@ "message": "Identität verifizieren" }, "yourVaultIsLocked": { - "message": "Your vault is locked. Verify your identity to continue." + "message": "Ihr Tresor ist gesperrt. Bestätigen Sie Ihre Identität um fortzufahren." }, "unlock": { "message": "Entsperren" From aa256b8a701d7a2a57b05368b1636cdf72a60483 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 28 Oct 2022 17:42:36 +0200 Subject: [PATCH 09/83] [SM-260] Hide email verification prompt if already verified (#3922) Co-authored-by: Sammy Chang --- .../app/settings/verify-email.component.ts | 32 ++++++++++++++----- apps/web/src/app/vault/vault.component.html | 7 +++- apps/web/src/app/vault/vault.component.ts | 4 +++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/apps/web/src/app/settings/verify-email.component.ts b/apps/web/src/app/settings/verify-email.component.ts index 665bb9975a7..c49b377968b 100644 --- a/apps/web/src/app/settings/verify-email.component.ts +++ b/apps/web/src/app/settings/verify-email.component.ts @@ -1,9 +1,10 @@ -import { Component } from "@angular/core"; +import { Component, EventEmitter, Output } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { TokenService } from "@bitwarden/common/abstractions/token.service"; @Component({ selector: "app-verify-email", @@ -12,25 +13,40 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti export class VerifyEmailComponent { actionPromise: Promise; + @Output() onVerified = new EventEmitter(); + constructor( private apiService: ApiService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, - private logService: LogService + private logService: LogService, + private tokenService: TokenService ) {} + async verifyEmail(): Promise { + await this.apiService.refreshIdentityToken(); + if (await this.tokenService.getEmailVerified()) { + this.onVerified.emit(true); + this.platformUtilsService.showToast("success", null, this.i18nService.t("emailVerified")); + return; + } + + await this.apiService.postAccountVerifyEmail(); + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("checkInboxForVerification") + ); + } + async send() { if (this.actionPromise != null) { return; } + try { - this.actionPromise = this.apiService.postAccountVerifyEmail(); + this.actionPromise = this.verifyEmail(); await this.actionPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("checkInboxForVerification") - ); } catch (e) { this.logService.error(e); } diff --git a/apps/web/src/app/vault/vault.component.html b/apps/web/src/app/vault/vault.component.html index 151354e4dc1..1ea77b34b10 100644 --- a/apps/web/src/app/vault/vault.component.html +++ b/apps/web/src/app/vault/vault.component.html @@ -78,7 +78,12 @@ - + +
diff --git a/apps/web/src/app/vault/vault.component.ts b/apps/web/src/app/vault/vault.component.ts index a511d44e36d..9f9c0d37803 100644 --- a/apps/web/src/app/vault/vault.component.ts +++ b/apps/web/src/app/vault/vault.component.ts @@ -169,6 +169,10 @@ export class VaultComponent implements OnInit, OnDestroy { ); } + emailVerified(verified: boolean) { + this.showVerifyEmail = !verified; + } + ngOnDestroy() { this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } From 2cd65939d54e725cca62d1ad170a2d69318f112a Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Fri, 28 Oct 2022 14:54:55 -0400 Subject: [PATCH 10/83] Two-Step Login (#3852) * [SG-163] Two step login flow web (#3648) * two step login flow * moved code from old branch and reafctored * fixed review comments * [SG-164] Two Step Login Flow - Browser (#3793) * Add new messages * Remove SSO button from home component * Change create account button to text * Add top padding to create account link * Add email input to HomeComponent * Add continue button to email input * Add form to home component * Retreive email from state service * Redirect to login after submit * Add error message for invalid email * Remove email input from login component * Remove loggingInTo from under MP input * Style the MP hint link * Add self hosted domain to email form * Made the mp hint link bold * Add the new login button * Style app-private-mode-warning in its component * Bitwarden -> Login text change * Remove the old login button * Cancel -> Close text change * Add avatar to login header * Login -> LoginWithMasterPassword text change * Add SSO button to login screen * Add not you button * Allow all clients to use the email query param on the login component * Introduct HomeGuard * Clear remembered email when clicking Not You * Make remember email opt-in * Use formGroup.patchValue instead of directly patching individual controls * [SG-165] Desktop login flow changes (#3814) * two step login flow * moved code from old branch and reafctored * fixed review comments * Make toggleValidateEmail in base class public * Add desktop login messages * Desktop login flow changes * Fix known device api error * Only submit if email has been validated * Clear remembered email when switching accounts * Fix merge issue * Add 'login with another device' button * Remove 'log in with another device' button for now * Pin login pag content to top instead of center justified * Leave email if 'Not you?' is clicked * Continue when enter is hit on email input Co-authored-by: gbubemismith * [SG-750] and [SG-751] Web two step login bug fixes (#3843) * Continue when enter is hit on email input * Mark email input as touched on 'continue' so field is validated * disable login with device on self-hosted (#3895) * [SG-753] Keep email after hint component is launched in browser (#3883) * Keep email after hint component is launched in browser * Use query params instead of state for consistency * Send email and rememberEmail to home component on navigation (#3897) * removed avatar and close button from the password screen (#3901) * [SG-781] Remove extra login page and remove rememberEmail code (#3902) * Remove browser home guard * Always remember email for browser * Remove login landing page button * [SG-782] Add login service to streamline login form data persistence (#3911) * Add login service and abstraction * Inject login service into apps * Inject and use new service in login component * Use service in hint component to prefill email * Add method in LoginService to clear service values * Add LoginService to two-factor component to clear values * make login.service variables private Co-authored-by: Gbubemi Smith Co-authored-by: Addison Beck Co-authored-by: Robyn MacCallum Co-authored-by: gbubemismith --- apps/browser/src/_locales/en/messages.json | 15 ++ .../src/popup/accounts/hint.component.html | 4 +- .../src/popup/accounts/hint.component.ts | 13 +- .../src/popup/accounts/home.component.html | 31 ++- .../src/popup/accounts/home.component.ts | 81 ++++--- .../src/popup/accounts/login.component.html | 43 ++-- .../src/popup/accounts/login.component.ts | 61 +++++- .../popup/accounts/two-factor.component.ts | 8 +- .../private-mode-warning.component.html | 2 +- apps/browser/src/popup/scss/base.scss | 9 + apps/browser/src/popup/scss/box.scss | 5 + apps/browser/src/popup/scss/misc.scss | 4 + apps/browser/src/popup/scss/pages.scss | 10 +- .../src/popup/services/services.module.ts | 6 + .../src/app/accounts/hint.component.ts | 6 +- .../src/app/accounts/login.component.html | 165 ++++++++++----- .../src/app/accounts/login.component.ts | 45 +++- .../src/app/accounts/two-factor.component.ts | 8 +- .../layout/account-switcher.component.html | 2 +- .../app/layout/account-switcher.component.ts | 4 + .../src/app/services/services.module.ts | 6 + apps/desktop/src/locales/en/messages.json | 27 +++ apps/desktop/src/scss/misc.scss | 9 + apps/desktop/src/scss/pages.scss | 2 + apps/web/src/app/accounts/hint.component.ts | 6 +- .../app/accounts/login/login.component.html | 200 ++++++++++-------- .../src/app/accounts/login/login.component.ts | 22 +- .../src/app/accounts/two-factor.component.ts | 8 +- apps/web/src/app/core/core.module.ts | 6 + apps/web/src/locales/en/messages.json | 13 +- libs/angular/src/components/hint.component.ts | 12 +- .../angular/src/components/login.component.ts | 85 +++++++- .../src/components/two-factor.component.ts | 7 +- .../src/services/jslib-services.module.ts | 6 + libs/common/src/abstractions/api.service.ts | 1 + libs/common/src/abstractions/login.service.ts | 7 + libs/common/src/services/api.service.ts | 6 + libs/common/src/services/login.service.ts | 27 +++ 38 files changed, 703 insertions(+), 269 deletions(-) create mode 100644 libs/common/src/abstractions/login.service.ts create mode 100644 libs/common/src/services/login.service.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 7a320e409e4..c166273e355 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2028,5 +2028,20 @@ "example": "Jun 15, 2015" } } + }, + "loginWithMasterPassword": { + "message": "Log in with master password" + }, + "loggingInAs": { + "message": "Logging in as" + }, + "notYou": { + "message": "Not you?" + }, + "newAroundHere": { + "message": "New around here?" + }, + "rememberEmail": { + "message": "Remember email" } } diff --git a/apps/browser/src/popup/accounts/hint.component.html b/apps/browser/src/popup/accounts/hint.component.html index 828af9c5583..4f5f975ccf9 100644 --- a/apps/browser/src/popup/accounts/hint.component.html +++ b/apps/browser/src/popup/accounts/hint.component.html @@ -1,7 +1,9 @@
- +

{{ "passwordHint" | i18n }} diff --git a/apps/browser/src/popup/accounts/hint.component.ts b/apps/browser/src/popup/accounts/hint.component.ts index 7ecc9ef1cbb..4948407e5a3 100644 --- a/apps/browser/src/popup/accounts/hint.component.ts +++ b/apps/browser/src/popup/accounts/hint.component.ts @@ -1,10 +1,11 @@ import { Component } from "@angular/core"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { HintComponent as BaseHintComponent } from "@bitwarden/angular/components/hint.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @Component({ @@ -17,8 +18,14 @@ export class HintComponent extends BaseHintComponent { platformUtilsService: PlatformUtilsService, i18nService: I18nService, apiService: ApiService, - logService: LogService + logService: LogService, + private route: ActivatedRoute, + loginService: LoginService ) { - super(router, i18nService, apiService, platformUtilsService, logService); + super(router, i18nService, apiService, platformUtilsService, logService, loginService); + + super.onSuccessfulSubmit = async () => { + this.router.navigate([this.successRoute]); + }; } } diff --git a/apps/browser/src/popup/accounts/home.component.html b/apps/browser/src/popup/accounts/home.component.html index 5f80204a27b..46eeb92e5b3 100644 --- a/apps/browser/src/popup/accounts/home.component.html +++ b/apps/browser/src/popup/accounts/home.component.html @@ -2,15 +2,28 @@

{{ "loginOrCreateNewAccount" | i18n }}

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

-
-

- {{ "appName" | i18n }} +

+ {{ "logIn" | i18n }}

-
- -
-
- - -
@@ -52,13 +39,27 @@
+
-

- {{ "loggingInTo" | i18n: selfHostedDomain }} -

-

- -

+
diff --git a/apps/browser/src/popup/accounts/login.component.ts b/apps/browser/src/popup/accounts/login.component.ts index 5028af06bfb..d44847b35e0 100644 --- a/apps/browser/src/popup/accounts/login.component.ts +++ b/apps/browser/src/popup/accounts/login.component.ts @@ -1,27 +1,33 @@ import { Component, NgZone } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/components/login.component"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; +import { Utils } from "@bitwarden/common/misc/utils"; @Component({ selector: "app-login", templateUrl: "login.component.html", }) export class LoginComponent extends BaseLoginComponent { - protected alwaysRememberEmail = true; + protected skipRememberEmail = true; constructor( + apiService: ApiService, + appIdService: AppIdService, authService: AuthService, router: Router, protected platformUtilsService: PlatformUtilsService, @@ -34,9 +40,13 @@ export class LoginComponent extends BaseLoginComponent { logService: LogService, ngZone: NgZone, formBuilder: FormBuilder, - formValidationErrorService: FormValidationErrorsService + formValidationErrorService: FormValidationErrorsService, + route: ActivatedRoute, + loginService: LoginService ) { super( + apiService, + appIdService, authService, router, platformUtilsService, @@ -48,7 +58,9 @@ export class LoginComponent extends BaseLoginComponent { logService, ngZone, formBuilder, - formValidationErrorService + formValidationErrorService, + route, + loginService ); super.onSuccessfulLogin = async () => { await syncService.fullSync(true); @@ -59,4 +71,45 @@ export class LoginComponent extends BaseLoginComponent { settings() { this.router.navigate(["environment"]); } + + async launchSsoBrowser() { + // Generate necessary sso params + const passwordOptions: any = { + type: "password", + length: 64, + uppercase: true, + lowercase: true, + numbers: true, + special: false, + }; + + const state = + (await this.passwordGenerationService.generatePassword(passwordOptions)) + + ":clientId=browser"; + const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256"); + const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + + await this.stateService.setSsoCodeVerifier(codeVerifier); + await this.stateService.setSsoState(state); + + let url = this.environmentService.getWebVaultUrl(); + if (url == null) { + url = "https://vault.bitwarden.com"; + } + + const redirectUri = url + "/sso-connector.html"; + + // Launch browser + this.platformUtilsService.launchUri( + url + + "/#/sso?clientId=browser" + + "&redirectUri=" + + encodeURIComponent(redirectUri) + + "&state=" + + state + + "&codeChallenge=" + + codeChallenge + ); + } } diff --git a/apps/browser/src/popup/accounts/two-factor.component.ts b/apps/browser/src/popup/accounts/two-factor.component.ts index 55294ef2e1e..5bfb637e4ca 100644 --- a/apps/browser/src/popup/accounts/two-factor.component.ts +++ b/apps/browser/src/popup/accounts/two-factor.component.ts @@ -10,6 +10,7 @@ import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.s import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; @@ -44,7 +45,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { private messagingService: MessagingService, logService: LogService, twoFactorService: TwoFactorService, - appIdService: AppIdService + appIdService: AppIdService, + loginService: LoginService ) { super( authService, @@ -58,9 +60,11 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { route, logService, twoFactorService, - appIdService + appIdService, + loginService ); super.onSuccessfulLogin = () => { + this.loginService.clearValues(); return syncService.fullSync(true); }; super.successRoute = "/tabs/vault"; diff --git a/apps/browser/src/popup/components/private-mode-warning.component.html b/apps/browser/src/popup/components/private-mode-warning.component.html index 848b69c92d5..5acbb1acd08 100644 --- a/apps/browser/src/popup/components/private-mode-warning.component.html +++ b/apps/browser/src/popup/components/private-mode-warning.component.html @@ -1,4 +1,4 @@ - + {{ "privateModeWarning" | i18n }} {{ "learnMore" | i18n diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 1222f66aaac..6cfa632451b 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -174,6 +174,11 @@ header { .right { justify-content: flex-end; + align-items: center; + app-avatar { + max-height: 30px; + margin-right: 5px; + } } .center { @@ -183,6 +188,10 @@ header { min-width: 0; } + .login-center { + margin: auto; + } + app-pop-out > button, div > button, div > a { diff --git a/apps/browser/src/popup/scss/box.scss b/apps/browser/src/popup/scss/box.scss index 7be206cbf3a..844a397a147 100644 --- a/apps/browser/src/popup/scss/box.scss +++ b/apps/browser/src/popup/scss/box.scss @@ -83,6 +83,11 @@ margin: 5px 10px; font-size: $font-size-small; + button.btn { + font-size: $font-size-small; + padding: 0; + } + @include themify($themes) { color: themed("mutedColor"); } diff --git a/apps/browser/src/popup/scss/misc.scss b/apps/browser/src/popup/scss/misc.scss index a4d5dcba96a..d891731c754 100644 --- a/apps/browser/src/popup/scss/misc.scss +++ b/apps/browser/src/popup/scss/misc.scss @@ -440,3 +440,7 @@ app-vault-view .box-footer { html.force_redraw { animation: redraw 1s linear infinite; } + +.rounded-circle { + border-radius: 50% !important; +} diff --git a/apps/browser/src/popup/scss/pages.scss b/apps/browser/src/popup/scss/pages.scss index 9ff96f048b3..ddba48f27aa 100644 --- a/apps/browser/src/popup/scss/pages.scss +++ b/apps/browser/src/popup/scss/pages.scss @@ -88,7 +88,7 @@ app-home { } } -app-private-mode-warning { +.app-private-mode-warning { display: block; padding-top: 1rem; } @@ -115,3 +115,11 @@ body.body-full { } } } + +.createAccountLink { + padding-top: 30px; +} + +.login-buttons > button { + margin: 15px 0 15px 0; +} diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index da96f7f9037..236ff4e5b18 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -24,6 +24,7 @@ import { FolderService } from "@bitwarden/common/abstractions/folder/folder.serv import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service"; +import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstractions/login.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; @@ -48,6 +49,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service"; import { AuthService } from "@bitwarden/common/services/auth.service"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; +import { LoginService } from "@bitwarden/common/services/login.service"; import { SearchService } from "@bitwarden/common/services/search.service"; import MainBackground from "../../background/main.background"; @@ -309,6 +311,10 @@ function getBgService(service: keyof MainBackground) { provide: FileDownloadService, useClass: BrowserFileDownloadService, }, + { + provide: LoginServiceAbstraction, + useClass: LoginService, + }, { provide: AbstractThemingService, useFactory: () => { diff --git a/apps/desktop/src/app/accounts/hint.component.ts b/apps/desktop/src/app/accounts/hint.component.ts index 7ecc9ef1cbb..8987efd6174 100644 --- a/apps/desktop/src/app/accounts/hint.component.ts +++ b/apps/desktop/src/app/accounts/hint.component.ts @@ -5,6 +5,7 @@ import { HintComponent as BaseHintComponent } from "@bitwarden/angular/component import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @Component({ @@ -17,8 +18,9 @@ export class HintComponent extends BaseHintComponent { platformUtilsService: PlatformUtilsService, i18nService: I18nService, apiService: ApiService, - logService: LogService + logService: LogService, + loginService: LoginService ) { - super(router, i18nService, apiService, platformUtilsService, logService); + super(router, i18nService, apiService, platformUtilsService, logService, loginService); } } diff --git a/apps/desktop/src/app/accounts/login.component.html b/apps/desktop/src/app/accounts/login.component.html index c11ed881b00..b81ef6ee5a9 100644 --- a/apps/desktop/src/app/accounts/login.component.html +++ b/apps/desktop/src/app/accounts/login.component.html @@ -22,78 +22,135 @@
Bitwarden

{{ "loginOrCreateNewAccount" | i18n }}

-
-
-
- - -
-
- diff --git a/apps/desktop/src/app/accounts/login.component.ts b/apps/desktop/src/app/accounts/login.component.ts index 33eefbd57ec..43909423ee2 100644 --- a/apps/desktop/src/app/accounts/login.component.ts +++ b/apps/desktop/src/app/accounts/login.component.ts @@ -1,9 +1,11 @@ import { Component, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/components/login.component"; import { ModalService } from "@bitwarden/angular/services/modal.service"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; @@ -11,6 +13,7 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -29,13 +32,23 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy { @ViewChild("environment", { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef; - showingModal = false; + webVaultHostname = ""; - protected alwaysRememberEmail = true; + showingModal = false; private deferFocus: boolean = null; + get loggedEmail() { + return this.formGroup.value.email; + } + + get selfHostedDomain() { + return this.environmentService.hasBaseUrl() ? this.environmentService.getWebVaultUrl() : null; + } + constructor( + apiService: ApiService, + appIdService: AppIdService, authService: AuthService, router: Router, i18nService: I18nService, @@ -51,9 +64,13 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy { private messagingService: MessagingService, logService: LogService, formBuilder: FormBuilder, - formValidationErrorService: FormValidationErrorsService + formValidationErrorService: FormValidationErrorsService, + route: ActivatedRoute, + loginService: LoginService ) { super( + apiService, + appIdService, authService, router, platformUtilsService, @@ -65,7 +82,9 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy { logService, ngZone, formBuilder, - formValidationErrorService + formValidationErrorService, + route, + loginService ); super.onSuccessfulLogin = () => { return syncService.fullSync(true); @@ -127,7 +146,23 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy { this.showPassword = false; } + async continue() { + await super.validateEmail(); + if (!this.formGroup.controls.email.valid) { + this.platformUtilsService.showToast( + "error", + this.i18nService.t("errorOccured"), + this.i18nService.t("invalidEmail") + ); + return; + } + } + async submit() { + if (!this.validatedEmail) { + return; + } + await super.submit(); if (this.captchaSiteKey) { const content = document.getElementById("content") as HTMLDivElement; diff --git a/apps/desktop/src/app/accounts/two-factor.component.ts b/apps/desktop/src/app/accounts/two-factor.component.ts index 51ad6e13297..858ddfc5ae7 100644 --- a/apps/desktop/src/app/accounts/two-factor.component.ts +++ b/apps/desktop/src/app/accounts/two-factor.component.ts @@ -9,6 +9,7 @@ import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; @@ -41,7 +42,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { route: ActivatedRoute, logService: LogService, twoFactorService: TwoFactorService, - appIdService: AppIdService + appIdService: AppIdService, + loginService: LoginService ) { super( authService, @@ -55,9 +57,11 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { route, logService, twoFactorService, - appIdService + appIdService, + loginService ); super.onSuccessfulLogin = () => { + this.loginService.clearValues(); return syncService.fullSync(true); }; } diff --git a/apps/desktop/src/app/layout/account-switcher.component.html b/apps/desktop/src/app/layout/account-switcher.component.html index 9b77638a6ca..4080b2e0314 100644 --- a/apps/desktop/src/app/layout/account-switcher.component.html +++ b/apps/desktop/src/app/layout/account-switcher.component.html @@ -90,7 +90,7 @@
- diff --git a/apps/desktop/src/app/layout/account-switcher.component.ts b/apps/desktop/src/app/layout/account-switcher.component.ts index 0f03464acb4..0c1ca74bd56 100644 --- a/apps/desktop/src/app/layout/account-switcher.component.ts +++ b/apps/desktop/src/app/layout/account-switcher.component.ts @@ -1,6 +1,7 @@ import { animate, state, style, transition, trigger } from "@angular/animations"; import { ConnectedPosition } from "@angular/cdk/overlay"; import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; import { concatMap, Subject, takeUntil } from "rxjs"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; @@ -91,6 +92,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { private stateService: StateService, private authService: AuthService, private messagingService: MessagingService, + private router: Router, private tokenService: TokenService ) {} @@ -142,6 +144,8 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { async addAccount() { this.close(); await this.stateService.setActiveUser(null); + await this.stateService.setRememberedEmail(null); + this.router.navigate(["/login"]); } private async createSwitcherAccounts(baseAccounts: { diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index aa4e9210899..6dc9b8b0dbb 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -23,6 +23,7 @@ import { LogService, LogService as LogServiceAbstraction, } from "@bitwarden/common/abstractions/log.service"; +import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstractions/login.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service"; import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service"; @@ -35,6 +36,7 @@ import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abs import { ClientType } from "@bitwarden/common/enums/clientType"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { GlobalState } from "@bitwarden/common/models/domain/global-state"; +import { LoginService } from "@bitwarden/common/services/login.service"; import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; import { SystemService } from "@bitwarden/common/services/system.service"; import { ElectronCryptoService } from "@bitwarden/electron/services/electronCrypto.service"; @@ -175,6 +177,10 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK"); EncryptedMessageHandlerService, ], }, + { + provide: LoginServiceAbstraction, + useClass: LoginService, + }, ], }) export class ServicesModule {} diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index c896cd3e267..fd168510b38 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2021,5 +2021,32 @@ }, "vault": { "message": "Vault" + }, + "loginWithMasterPassword": { + "message": "Log in with master password" + }, + "loggingInAs": { + "message": "Logging in as" + }, + "rememberEmail": { + "message": "Remember email" + }, + "notYou": { + "message": "Not you?" + }, + "newAroundHere": { + "message": "New around here?" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "logInWithAnotherDevice": { + "message": "Log in with another device" } } diff --git a/apps/desktop/src/scss/misc.scss b/apps/desktop/src/scss/misc.scss index 50ba40ba707..5818b194710 100644 --- a/apps/desktop/src/scss/misc.scss +++ b/apps/desktop/src/scss/misc.scss @@ -310,6 +310,11 @@ form, margin-top: 4px; margin-left: -18px; } + + &.remember-email { + padding-left: 20px; + padding-bottom: 5px; + } } .radio { @@ -482,6 +487,10 @@ app-root > #loading, margin-top: 15px; } +.password-hint-btn { + margin-bottom: 10px; +} + .set-pin-modal { .box { margin-bottom: 15px; diff --git a/apps/desktop/src/scss/pages.scss b/apps/desktop/src/scss/pages.scss index 87862bbef3a..f5ca996c447 100644 --- a/apps/desktop/src/scss/pages.scss +++ b/apps/desktop/src/scss/pages.scss @@ -189,6 +189,8 @@ #login-page { flex-direction: column; + justify-content: unset; + padding-top: 20px; .login-header { align-self: flex-start; diff --git a/apps/web/src/app/accounts/hint.component.ts b/apps/web/src/app/accounts/hint.component.ts index f6d3153b031..aaf58fe9499 100644 --- a/apps/web/src/app/accounts/hint.component.ts +++ b/apps/web/src/app/accounts/hint.component.ts @@ -5,6 +5,7 @@ import { HintComponent as BaseHintComponent } from "@bitwarden/angular/component import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @Component({ @@ -17,8 +18,9 @@ export class HintComponent extends BaseHintComponent { i18nService: I18nService, apiService: ApiService, platformUtilsService: PlatformUtilsService, - logService: LogService + logService: LogService, + loginService: LoginService ) { - super(router, i18nService, apiService, platformUtilsService, logService); + super(router, i18nService, apiService, platformUtilsService, logService, loginService); } } diff --git a/apps/web/src/app/accounts/login/login.component.html b/apps/web/src/app/accounts/login/login.component.html index 6152b585953..98544035487 100644 --- a/apps/web/src/app/accounts/login/login.component.html +++ b/apps/web/src/app/accounts/login/login.component.html @@ -16,102 +16,122 @@
- - {{ "resetPasswordAutoEnrollInviteWarning" | i18n }} - - -
- - {{ "emailAddress" | i18n }} - - -
- -
- -
-
- + +
+ + {{ "emailAddress" | i18n }} + +
- - {{ "rememberEmail" | i18n }} - -
-
+
+
+ +
+ + {{ "rememberEmail" | i18n }} + +
-
- -
+
+ +
-
- +
- - - {{ "createAccount" | i18n }} - -
- -
- -
- - +

+ {{ "newAroundHere" | i18n }} + {{ "createAccount" | i18n }} +

+
+ + +
+ + {{ "masterPass" | i18n }} + + + + {{ "getMasterPasswordHint" | i18n }} + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + +
+ +
+

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

+ {{ "notYou" | i18n }} +
+
diff --git a/apps/web/src/app/accounts/login/login.component.ts b/apps/web/src/app/accounts/login/login.component.ts index 1c842650498..d461c04c81e 100644 --- a/apps/web/src/app/accounts/login/login.component.ts +++ b/apps/web/src/app/accounts/login/login.component.ts @@ -6,12 +6,14 @@ import { first } from "rxjs/operators"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/components/login.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -39,15 +41,16 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest private destroy$ = new Subject(); constructor( + apiService: ApiService, + appIdService: AppIdService, authService: AuthService, router: Router, i18nService: I18nService, - private route: ActivatedRoute, + route: ActivatedRoute, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService, - private apiService: ApiService, private policyApiService: PolicyApiServiceAbstraction, private policyService: InternalPolicyService, logService: LogService, @@ -56,9 +59,12 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest private messagingService: MessagingService, private routerService: RouterService, formBuilder: FormBuilder, - formValidationErrorService: FormValidationErrorsService + formValidationErrorService: FormValidationErrorsService, + loginService: LoginService ) { super( + apiService, + appIdService, authService, router, platformUtilsService, @@ -70,7 +76,9 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest logService, ngZone, formBuilder, - formValidationErrorService + formValidationErrorService, + route, + loginService ); this.onSuccessfulLogin = async () => { this.messagingService.send("setFullWidth"); @@ -82,9 +90,6 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest async ngOnInit() { // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.queryParams.pipe(first()).subscribe(async (qParams) => { - if (qParams.email != null && qParams.email.indexOf("@") > -1) { - this.formGroup.get("email")?.setValue(qParams.email); - } if (qParams.premium != null) { this.routerService.setPreviousUrl("/settings/premium"); } else if (qParams.org != null) { @@ -102,8 +107,6 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest this.routerService.setPreviousUrl(route.toString()); } await super.ngOnInit(); - const rememberEmail = await this.stateService.getRememberEmail(); - this.formGroup.get("rememberEmail")?.setValue(rememberEmail); }); const invite = await this.stateService.getOrganizationInvitation(); @@ -176,6 +179,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest if (previousUrl) { this.router.navigateByUrl(previousUrl); } else { + this.loginService.clearValues(); this.router.navigate([this.successRoute]); } } diff --git a/apps/web/src/app/accounts/two-factor.component.ts b/apps/web/src/app/accounts/two-factor.component.ts index 5ebf3ea02ed..4fb6f6e3655 100644 --- a/apps/web/src/app/accounts/two-factor.component.ts +++ b/apps/web/src/app/accounts/two-factor.component.ts @@ -9,6 +9,7 @@ import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; @@ -40,7 +41,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { logService: LogService, twoFactorService: TwoFactorService, appIdService: AppIdService, - private routerService: RouterService + private routerService: RouterService, + loginService: LoginService ) { super( authService, @@ -54,7 +56,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { route, logService, twoFactorService, - appIdService + appIdService, + loginService ); this.onSuccessfulLoginNavigate = this.goAfterLogIn; } @@ -79,6 +82,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { } async goAfterLogIn() { + this.loginService.clearValues(); const previousUrl = this.routerService.getPreviousUrl(); if (previousUrl) { this.router.navigateByUrl(previousUrl); diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index ba22f4422c4..ac349561c8f 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -13,6 +13,7 @@ import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services. import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service"; import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service"; +import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstractions/login.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service"; import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -20,6 +21,7 @@ import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/a import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service"; import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; +import { LoginService } from "@bitwarden/common/services/login.service"; import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; import { BroadcasterMessagingService } from "./broadcaster-messaging.service"; @@ -98,6 +100,10 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service"; provide: FileDownloadService, useClass: WebFileDownloadService, }, + { + provide: LoginServiceAbstraction, + useClass: LoginService, + }, ], }) export class CoreModule { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index ab1fe4f8cfe..0edc70d0a6e 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -569,12 +569,15 @@ "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, - "loginWithDevice" : { + "loginWithDevice": { "message": "Log in with device" }, "loginWithDeviceEnabledInfo": { "message": "Log in with device must be set up in the settings of the Bitwarden mobile app. Need another option?" }, + "loginWithMasterPassword": { + "message": "Log in with master password" + }, "createAccount": { "message": "Create account" }, @@ -717,7 +720,7 @@ "noOrganizationsList": { "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." }, - "notificationSentDevice":{ + "notificationSentDevice": { "message": "A notification has been sent to your device." }, "versionNumber": { @@ -5394,6 +5397,12 @@ "numberOfUsers": { "message": "Number of users" }, + "loggingInAs": { + "message": "Logging in as" + }, + "notYou": { + "message": "Not you?" + }, "multiSelectPlaceholder": { "message": "-- Type to Filter --" }, diff --git a/libs/angular/src/components/hint.component.ts b/libs/angular/src/components/hint.component.ts index 7e802722bf3..de49e28c86e 100644 --- a/libs/angular/src/components/hint.component.ts +++ b/libs/angular/src/components/hint.component.ts @@ -1,12 +1,15 @@ +import { Directive, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PasswordHintRequest } from "@bitwarden/common/models/request/password-hint.request"; -export class HintComponent { +@Directive() +export class HintComponent implements OnInit { email = ""; formPromise: Promise; @@ -18,9 +21,14 @@ export class HintComponent { protected i18nService: I18nService, protected apiService: ApiService, protected platformUtilsService: PlatformUtilsService, - private logService: LogService + private logService: LogService, + private loginService: LoginService ) {} + ngOnInit(): void { + this.email = this.loginService.getEmail() ?? ""; + } + async submit() { if (this.email == null || this.email === "") { this.platformUtilsService.showToast( diff --git a/libs/angular/src/components/login.component.ts b/libs/angular/src/components/login.component.ts index ab0e4df57e6..6d4b6966bbb 100644 --- a/libs/angular/src/components/login.component.ts +++ b/libs/angular/src/components/login.component.ts @@ -1,8 +1,10 @@ import { Directive, NgZone, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { Router } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { take } from "rxjs/operators"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; @@ -12,6 +14,7 @@ import { } from "@bitwarden/common/abstractions/formValidationErrors.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; @@ -29,20 +32,30 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit onSuccessfulLoginNavigate: () => Promise; onSuccessfulLoginTwoFactorNavigate: () => Promise; onSuccessfulLoginForceResetNavigate: () => Promise; - selfHosted = false; + private selfHosted = false; + showLoginWithDevice: boolean; + validatedEmail = false; + paramEmailSet = false; formGroup = this.formBuilder.group({ email: ["", [Validators.required, Validators.email]], masterPassword: ["", [Validators.required, Validators.minLength(8)]], - rememberEmail: [true], + rememberEmail: [false], }); protected twoFactorRoute = "2fa"; protected successRoute = "vault"; protected forcePasswordResetRoute = "update-temp-password"; protected alwaysRememberEmail = false; + protected skipRememberEmail = false; + + get loggedEmail() { + return this.formGroup.value.email; + } constructor( + protected apiService: ApiService, + protected appIdService: AppIdService, protected authService: AuthService, protected router: Router, platformUtilsService: PlatformUtilsService, @@ -54,7 +67,9 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit protected logService: LogService, protected ngZone: NgZone, protected formBuilder: FormBuilder, - protected formValidationErrorService: FormValidationErrorsService + protected formValidationErrorService: FormValidationErrorsService, + protected route: ActivatedRoute, + protected loginService: LoginService ) { super(environmentService, i18nService, platformUtilsService); this.selfHosted = platformUtilsService.isSelfHost(); @@ -65,19 +80,35 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit } async ngOnInit() { - let email = this.formGroup.value.email; + this.route?.queryParams.subscribe((params) => { + if (params != null) { + const queryParamsEmail = params["email"]; + if (queryParamsEmail != null && queryParamsEmail.indexOf("@") > -1) { + this.formGroup.get("email").setValue(queryParamsEmail); + this.paramEmailSet = true; + } + } + }); + let email = this.loginService.getEmail(); + if (email == null || email === "") { email = await this.stateService.getRememberedEmail(); - this.formGroup.get("email")?.setValue(email); + } - if (email == null) { - this.formGroup.get("email")?.setValue(""); - } + if (!this.paramEmailSet) { + this.formGroup.get("email")?.setValue(email ?? ""); } if (!this.alwaysRememberEmail) { - const rememberEmail = (await this.stateService.getRememberedEmail()) != null; + let rememberEmail = this.loginService.getRememberEmail(); + if (rememberEmail == null) { + rememberEmail = (await this.stateService.getRememberedEmail()) != null; + } this.formGroup.get("rememberEmail")?.setValue(rememberEmail); } + + if (email) { + this.validateEmail(); + } } async submit(showToast = true) { @@ -108,6 +139,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit ); this.formPromise = this.authService.logIn(credentials); const response = await this.formPromise; + this.setFormValues(); if (data.rememberEmail || this.alwaysRememberEmail) { await this.stateService.setRememberedEmail(data.email); } else { @@ -130,6 +162,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit } else { const disableFavicon = await this.stateService.getDisableFavicon(); await this.stateService.setDisableFavicon(!!disableFavicon); + this.loginService.clearValues(); if (this.onSuccessfulLogin != null) { this.onSuccessfulLogin(); } @@ -191,6 +224,25 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit ); } + async validateEmail() { + this.formGroup.controls.email.markAsTouched(); + const emailInvalid = this.formGroup.get("email").invalid; + if (!emailInvalid) { + this.toggleValidateEmail(true); + await this.getLoginWithDevice(this.loggedEmail); + } + } + + toggleValidateEmail(value: boolean) { + this.validatedEmail = value; + this.formGroup.controls.masterPassword.reset(); + } + + setFormValues() { + this.loginService.setEmail(this.formGroup.value.email); + this.loginService.setRememberEmail(this.formGroup.value.rememberEmail); + } + private getErrorToastMessage() { const error: AllValidationErrors = this.formValidationErrorService .getFormValidationErrors(this.formGroup.controls) @@ -213,8 +265,19 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit return `${error.controlName}${name}`; } + private async getLoginWithDevice(email: string) { + try { + const deviceIdentifier = await this.appIdService.getAppId(); + const res = await this.apiService.getKnownDevice(email, deviceIdentifier); + //ensure the application is not self-hosted + this.showLoginWithDevice = res && !this.selfHosted; + } catch (e) { + this.showLoginWithDevice = false; + } + } + protected focusInput() { - const email = this.formGroup.value.email; + const email = this.loggedEmail; document.getElementById(email == null || email === "" ? "email" : "masterPassword").focus(); } } diff --git a/libs/angular/src/components/two-factor.component.ts b/libs/angular/src/components/two-factor.component.ts index 1c2f4ff60b0..f61e54710b2 100644 --- a/libs/angular/src/components/two-factor.component.ts +++ b/libs/angular/src/components/two-factor.component.ts @@ -9,6 +9,7 @@ import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; @@ -59,7 +60,8 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI protected route: ActivatedRoute, protected logService: LogService, protected twoFactorService: TwoFactorService, - protected appIdService: AppIdService + protected appIdService: AppIdService, + protected loginService: LoginService ) { super(environmentService, i18nService, platformUtilsService); this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); @@ -204,6 +206,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI return; } if (this.onSuccessfulLogin != null) { + this.loginService.clearValues(); this.onSuccessfulLogin(); } if (response.resetMasterPassword) { @@ -213,8 +216,10 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI this.successRoute = "update-temp-password"; } if (this.onSuccessfulLoginNavigate != null) { + this.loginService.clearValues(); this.onSuccessfulLoginNavigate(); } else { + this.loginService.clearValues(); this.router.navigate([this.successRoute], { queryParams: { identifier: this.identifier, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 83725838a58..5f49041bd76 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -31,6 +31,7 @@ import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/abstractions/keyConnector.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstractions/login.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service"; import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction"; @@ -88,6 +89,7 @@ import { FolderApiService } from "@bitwarden/common/services/folder/folder-api.s import { FolderService } from "@bitwarden/common/services/folder/folder.service"; import { FormValidationErrorsService } from "@bitwarden/common/services/formValidationErrors.service"; import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service"; +import { LoginService } from "@bitwarden/common/services/login.service"; import { NotificationsService } from "@bitwarden/common/services/notifications.service"; import { OrganizationApiService } from "@bitwarden/common/services/organization/organization-api.service"; import { OrganizationService } from "@bitwarden/common/services/organization/organization.service"; @@ -578,6 +580,10 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; useClass: ValidationService, deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction], }, + { + provide: LoginServiceAbstraction, + useClass: LoginService, + }, ], }) export class JslibServicesModule {} diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 14dc26cc59f..74f9edebd6f 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -479,6 +479,7 @@ export abstract class ApiService { putDeviceVerificationSettings: ( request: DeviceVerificationRequest ) => Promise; + getKnownDevice: (email: string, deviceIdentifier: string) => Promise; getEmergencyAccessTrusted: () => Promise>; getEmergencyAccessGranted: () => Promise>; diff --git a/libs/common/src/abstractions/login.service.ts b/libs/common/src/abstractions/login.service.ts new file mode 100644 index 00000000000..0823611546c --- /dev/null +++ b/libs/common/src/abstractions/login.service.ts @@ -0,0 +1,7 @@ +export abstract class LoginService { + getEmail: () => string; + getRememberEmail: () => boolean; + setEmail: (value: string) => void; + setRememberEmail: (value: boolean) => void; + clearValues: () => void; +} diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index bab17eb43f7..7c39958bce3 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -1518,6 +1518,12 @@ export class ApiService implements ApiServiceAbstraction { return new DeviceVerificationResponse(r); } + async getKnownDevice(email: string, deviceIdentifier: string): Promise { + const path = `/devices/knowndevice/${email}/${deviceIdentifier}`; + const r = await this.send("GET", path, null, false, true); + return r as boolean; + } + // Emergency Access APIs async getEmergencyAccessTrusted(): Promise> { diff --git a/libs/common/src/services/login.service.ts b/libs/common/src/services/login.service.ts new file mode 100644 index 00000000000..8a06f6b7a0d --- /dev/null +++ b/libs/common/src/services/login.service.ts @@ -0,0 +1,27 @@ +import { LoginService as LoginServiceAbstraction } from "../abstractions/login.service"; + +export class LoginService implements LoginServiceAbstraction { + private _email: string; + private _rememberEmail: boolean; + + getEmail() { + return this._email; + } + + getRememberEmail() { + return this._rememberEmail; + } + + setEmail(value: string) { + this._email = value; + } + + setRememberEmail(value: boolean) { + this._rememberEmail = value; + } + + clearValues() { + this._email = null; + this._rememberEmail = null; + } +} From 5cb84927ea0718c1295ddc739ea880175caee1df Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 28 Oct 2022 18:10:10 -0400 Subject: [PATCH 11/83] 400s only log out on invalid grant error (#3924) --- libs/common/src/services/api.service.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 7c39958bce3..6c15d85c0d1 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -2360,16 +2360,6 @@ export class ApiService implements ApiServiceAbstraction { tokenError: boolean, authed: boolean ): Promise { - if ( - authed && - ((tokenError && response.status === 400) || - response.status === 401 || - response.status === 403) - ) { - await this.logoutCallback(true); - return null; - } - let responseJson: any = null; if (this.isJsonResponse(response)) { responseJson = await response.json(); @@ -2377,6 +2367,20 @@ export class ApiService implements ApiServiceAbstraction { responseJson = { Message: await response.text() }; } + if (authed) { + if ( + response.status === 401 || + response.status === 403 || + (tokenError && + response.status === 400 && + responseJson != null && + responseJson.error === "invalid_grant") + ) { + await this.logoutCallback(true); + return null; + } + } + return new ErrorResponse(responseJson, response.status, tokenError); } From b637c2f17270f17aecc61822d73f44d376429214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Mon, 31 Oct 2022 14:29:41 +0100 Subject: [PATCH 12/83] Fix rust tests apt-get install (#3933) --- .github/workflows/test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5ff1343f2e..b0ca237c0cc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,7 +70,9 @@ jobs: steps: - name: Install gnome-keyring if: ${{ matrix.os=='ubuntu-latest' }} - run: sudo apt-get install -y gnome-keyring dbus-x11 + run: | + sudo apt-get update + sudo apt-get install -y gnome-keyring dbus-x11 - name: Checkout repo uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 From 05fe75d96f942d0c1b7f626220083b6c1acda743 Mon Sep 17 00:00:00 2001 From: Gbubemi Smith Date: Mon, 31 Oct 2022 17:37:42 -0400 Subject: [PATCH 13/83] Added focus to the email and master password fields (#3934) --- apps/web/src/app/accounts/login/login.component.html | 2 ++ libs/angular/src/components/login.component.ts | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/accounts/login/login.component.html b/apps/web/src/app/accounts/login/login.component.html index 98544035487..80c4c1455d8 100644 --- a/apps/web/src/app/accounts/login/login.component.html +++ b/apps/web/src/app/accounts/login/login.component.html @@ -25,6 +25,7 @@ bitInput type="email" formControlName="email" + appAutofocus (keyup.enter)="validateEmail()" /> @@ -79,6 +80,7 @@ bitInput type="{{ showPassword ? 'text' : 'password' }}" formControlName="masterPassword" + appAutofocus />
-
+
button { margin: 15px 0 15px 0; } + +app-options { + .box { + margin: 10px 0; + + & + .box { + margin-top: 10px; + } + } +} + +app-vault-view, +app-vault-add-edit, +app-generator { + .box { + margin: 15px 0 25px 0; + + & + .box { + margin-top: 25px; + } + } +} diff --git a/apps/browser/src/popup/scss/variables.scss b/apps/browser/src/popup/scss/variables.scss index 049954bc335..14b2c312b1f 100644 --- a/apps/browser/src/popup/scss/variables.scss +++ b/apps/browser/src/popup/scss/variables.scss @@ -6,6 +6,8 @@ $font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-s $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; $font-size-base: 14px; $font-size-large: 18px; +$font-size-xlarge: 22px; +$font-size-xxlarge: 28px; $font-size-small: 12px; $text-color: #000000; $border-color: #f0f0f0; @@ -13,8 +15,9 @@ $border-color-dark: #ddd; $list-item-hover: #fbfbfb; $list-icon-color: #767679; $disabled-box-opacity: 1; -$border-radius: 3px; +$border-radius: 6px; $line-height-base: 1.42857143; +$icon-hover-color: lighten($text-color, 50%); $gray: #555; $gray-light: #777; @@ -32,6 +35,7 @@ $background-color: #f0f0f0; $box-background-color: white; $box-background-hover-color: $list-item-hover; $box-border-color: $border-color; +$border-color-alt: #c3c5c7; $button-border-color: darken($border-color-dark, 12%); $button-background-color: white; @@ -59,8 +63,10 @@ $solarizedDarkGreen: #859900; $themes: ( light: ( textColor: $text-color, + hoverColorTransparent: rgba($text-color, 0.15), borderColor: $border-color-dark, backgroundColor: $background-color, + borderColorAlt: $border-color-alt, backgroundColorAlt: #ffffff, scrollbarColor: rgba(100, 100, 100, 0.2), scrollbarHoverColor: rgba(100, 100, 100, 0.4), @@ -107,11 +113,14 @@ $themes: ( calloutBackgroundColor: $box-background-color, toastTextColor: #ffffff, svgSuffix: "-light.svg", + transparentColor: rgba(0, 0, 0, 0), ), dark: ( textColor: #ffffff, + hoverColorTransparent: rgba($text-color, 0.15), borderColor: #161c26, backgroundColor: #161c26, + borderColorAlt: #6e788a, backgroundColorAlt: #2f343d, scrollbarColor: #6e788a, scrollbarHoverColor: #8d94a5, @@ -158,11 +167,14 @@ $themes: ( calloutBackgroundColor: #3c424e, toastTextColor: #1f242e, svgSuffix: "-dark.svg", + transparentColor: rgba(0, 0, 0, 0), ), nord: ( textColor: $nord5, + hoverColorTransparent: rgba($text-color, 0.15), borderColor: $nord0, backgroundColor: $nord1, + borderColorAlt: $nord5, backgroundColorAlt: $nord2, scrollbarColor: $nord4, scrollbarHoverColor: $nord6, @@ -209,16 +221,19 @@ $themes: ( calloutBackgroundColor: $nord2, toastTextColor: #ffffff, svgSuffix: "-dark.svg", + transparentColor: rgba(0, 0, 0, 0), ), solarizedDark: ( textColor: $solarizedDarkBase2, + hoverColorTransparent: rgba($text-color, 0.15), borderColor: $solarizedDarkBase03, backgroundColor: $solarizedDarkBase03, + borderColorAlt: $solarizedDarkBase01, backgroundColorAlt: $solarizedDarkBase02, scrollbarColor: $solarizedDarkBase0, scrollbarHoverColor: $solarizedDarkBase2, - boxBackgroundColor: $solarizedDarkBase03, - boxBackgroundHoverColor: $solarizedDarkBase02, + boxBackgroundColor: $solarizedDarkBase02, + boxBackgroundHoverColor: lighten($solarizedDarkBase02, 5%), boxBorderColor: $solarizedDarkBase02, tabBackgroundColor: $solarizedDarkBase02, tabBackgroundHoverColor: $solarizedDarkBase01, @@ -230,7 +245,7 @@ $themes: ( headerInputBackgroundFocusColor: $solarizedDarkBase1, headerInputColor: $solarizedDarkBase01, headerInputPlaceholderColor: $solarizedDarkBase00, - listItemBackgroundHoverColor: $solarizedDarkBase02, + listItemBackgroundHoverColor: lighten($solarizedDarkBase02, 5%), disabledIconColor: $solarizedDarkBase0, disabledBoxOpacity: 0.5, headingColor: $solarizedDarkBase0, @@ -260,6 +275,7 @@ $themes: ( calloutBackgroundColor: $solarizedDarkBase01, toastTextColor: #ffffff, svgSuffix: "-solarized.svg", + transparentColor: rgba(0, 0, 0, 0), ), ); diff --git a/apps/browser/src/popup/vault/current-tab.component.html b/apps/browser/src/popup/vault/current-tab.component.html index f88fd2feca3..9ed3662bc5d 100644 --- a/apps/browser/src/popup/vault/current-tab.component.html +++ b/apps/browser/src/popup/vault/current-tab.component.html @@ -41,7 +41,7 @@ {{ "typeLogins" | i18n }} {{ loginCiphers.length }} -
+
Date: Tue, 1 Nov 2022 14:00:50 -0400 Subject: [PATCH 20/83] [SG-792] Added focus to master password field on browser and desktop (#3939) * Added focus to master password field on browser client * Added focus to master password field on desktop client --- apps/browser/src/popup/accounts/login.component.html | 1 + apps/desktop/src/app/accounts/login.component.ts | 6 ++++++ libs/angular/src/components/login.component.ts | 5 ----- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/popup/accounts/login.component.html b/apps/browser/src/popup/accounts/login.component.html index f2b7f522ec3..76bd1fff141 100644 --- a/apps/browser/src/popup/accounts/login.component.html +++ b/apps/browser/src/popup/accounts/login.component.html @@ -16,6 +16,7 @@ class="monospaced" formControlName="masterPassword" appInputVerbatim + appAutofocus />
diff --git a/apps/desktop/src/app/accounts/login.component.ts b/apps/desktop/src/app/accounts/login.component.ts index 43909423ee2..db77a783100 100644 --- a/apps/desktop/src/app/accounts/login.component.ts +++ b/apps/desktop/src/app/accounts/login.component.ts @@ -156,6 +156,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy { ); return; } + this.focusInput(); } async submit() { @@ -169,4 +170,9 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy { content.setAttribute("style", "width:335px"); } } + + private focusInput() { + const email = this.loggedEmail; + document.getElementById(email == null || email === "" ? "email" : "masterPassword").focus(); + } } diff --git a/libs/angular/src/components/login.component.ts b/libs/angular/src/components/login.component.ts index 6d4b6966bbb..0b35b827afc 100644 --- a/libs/angular/src/components/login.component.ts +++ b/libs/angular/src/components/login.component.ts @@ -275,9 +275,4 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit this.showLoginWithDevice = false; } } - - protected focusInput() { - const email = this.loggedEmail; - document.getElementById(email == null || email === "" ? "email" : "masterPassword").focus(); - } } From 6c7a76d6936036af62946fbb0fb83353e285407f Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Wed, 2 Nov 2022 20:07:34 +1000 Subject: [PATCH 21/83] Tell eslint & prettier to ignore storybook-static (#3946) --- .eslintignore | 1 + .prettierignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.eslintignore b/.eslintignore index 930b468de83..e1bc65d74b1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,6 +2,7 @@ **/dist **/coverage .angular +storybook-static **/node_modules diff --git a/.prettierignore b/.prettierignore index b1c9359fa1e..d4257ee2d03 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ **/coverage .angular documentation.json +storybook-static # External libraries / auto synced locales apps/browser/src/_locales From d12ef5c7341b5b44a0f1423d51f4553752e957f5 Mon Sep 17 00:00:00 2001 From: Gbubemi Smith Date: Wed, 2 Nov 2022 11:23:29 -0400 Subject: [PATCH 22/83] [SG-792] Fixed focus on master password when enter key is pressed (#3948) * Added focus to master password field on browser client * Added focus to master password field on desktop client * fixed focus on master password when enter is pressed --- apps/desktop/src/app/accounts/login.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/app/accounts/login.component.html b/apps/desktop/src/app/accounts/login.component.html index b81ef6ee5a9..39caecc44aa 100644 --- a/apps/desktop/src/app/accounts/login.component.html +++ b/apps/desktop/src/app/accounts/login.component.html @@ -33,7 +33,7 @@ type="email" formControlName="email" appInputVerbatim="false" - (keyup.enter)="validateEmail()" + (keyup.enter)="continue()" />
From 09c3bc8f1b529a973d4ce3c660a92ed6c7976d65 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 2 Nov 2022 09:57:25 -0700 Subject: [PATCH 23/83] [EC-7] Org Admin Vault Refresh Client V1 (#3925) * [EC-8] Restructure Tabs (#3109) * Cherry pick pending PR for tabs component [CL-17] Tabs - Routing * Update organization tabs from 4 to 6 * Create initial 'Members' tab * Create initial 'Groups' tab * Add initial "Reporting" tab * Use correct report label/layout by product type * Create initial 'Billing' tab * Breakup billing payment and billing history pages * Cleanup org routing and nav permission service * More org tab permission cleanup * Refactor organization billing to use a module * Refactor organization reporting to use module * Cherry pick finished/merged tabs component [CL-17] Tabs - Router (#2952) * This partially reverts commit 24bb775 to fix tracking of people.component.html rename. * Fix people component file rename * Recover lost member page changes * Undo members component rename as it was causing difficult merge conflicts * Fix member and group page container * Remove unnecessary organization lookup * [EC-8] Some PR suggestions * [EC-8] Reuse user billing history for orgs * [EC-8] Renamed user billing history component * [EC-8] Repurpose payment method component Update end user payment method component to be usable for organizations. * [EC-8] Fix missing verify bank condition * [EC-8] Remove org payment method component * [EC-8] Use CL in payment method component * [EC-8] Extend maxWidth Tailwind theme config * [EC-8] Add lazy loading to org reports * [EC-8] Add lazy loading to org billing * [EC-8] Prettier * [EC-8] Cleanup org reporting component redundancy * [EC-8] Use different class for negative margin * [EC-8] Make billing history component "dumb" * Revert "[EC-8] Cleanup org reporting component redundancy" This reverts commit eca337e89bb0be4600af4351640636aa8a498cff. * [EC-8] Create and export shared reports module * [EC-8] Use shared reports module in orgs * [EC-8] Use takeUntil pattern * [EC-8] Move org reporting module out of old modules folder * [EC-8] Move org billing module out of old modules folder * [EC-8] Fix some remaining merge conflicts * [EC-8] Move maxWidth into 'extend' key for Tailwind config * [EC-8] Remove unused module * [EC-8] Rename org report list component * Prettier Co-authored-by: Vincent Salucci * [EC-451] Org Admin Refresh Permissions Refactor (#3320) * [EC-451] Update new org permissions for new tabs * [EC-451] Remove redudant route guards * [EC-451] Remove canAccessManageTab() * [EC-451] Use canAccess* callbacks in org routing module * Fix org api service refactor and linting after pulling in master * Fix broken org people and group pages after merge * [EC-18] Reporting side nav direction (#3420) * [EC-18] Re-order side nav for org reports according to Figma * [EC-18] Fix rxjs linter errors and redundant org flag * [EC-526] Default to Event Logs page for Reporting Tab (#3470) * [EC-526] Default to the Events Logs page when navigating to the Reporting tab * [EC-526] Undo default routing redirect when the child path is missing. Avoids defaulting to "/events" in case a user/org doesn't have access to event logs. * [EC-19] Update Organization Settings Page (#3251) * [EC-19] Refactor existing organization settings components to its own module * [EC-19] Move SSO page to settings tab * [EC-19] Move Policies page to Settings tab Refactor Policy components into its own module * [EC-19] Move ImageSubscriptionHiddenComponent * [EC-19] Lazy load org settings module * [EC-19] Add SSO Id to SSO config view * [EC-19] Remove SSO identfier from org info page * [EC-19] Update org settings/policies to follow ADR-0011 * [EC-19] Update two-step login setup description * [EC-19] Revert nested policy components folder * [EC-19] Revert nested org setting components folder * [EC-19] Remove left over image component * [EC-19] Prettier * [EC-19] Fix missing i18n * [EC-19] Update SSO form to use CL * [EC-19] Remove unused SSO input components * [EC-19] Fix bad SSO locale identifier * [EC-19] Fix import order linting * [EC-19] Add explicit whitespace check for launch click directive * [EC-19] Add restricted import paths to eslint config * [EC-19] Tag deprecated field with Jira issue to cleanup in future release * [EC-19] Remove out of date comment * [EC-19] Move policy components to policies module * [EC-19] Remove dityRequired validator * [EC-19] Use explicit type for SSO config form * [EC-19] Fix rxjs linter errors * [EC-19] Fix RxJS eslint comments in org settings component * [EC-19] Use explicit ControlsOf helper for nested SSO form groups. * [EC-19] Attribute source of ControlsOf helper * [EC-19] Fix missing settings side nav links * [EC-19] Fix member/user language for policy modals * [EC-551] Update Event Logs Client Column (#3572) * [EC-551] Fix RxJS warnings * [EC-551] Update page to use CL components and Tailwind classes * [EC-551] Update Client column to use text instead of icon. Update language and i18n. * [EC-14] Refactor vault filter (#3440) * [EC-14] initial refactoring of vault filter * [EC-14] return observable trees for all filters with head node * [EC-14] Remove bindings on callbacks * [EC-14] fix formatting on disabled orgs * [EC-14] hide MyVault if personal org policy * [EC-14] add check for single org policy * [EC-14] add policies to org and change node constructor * [EC-14] don't show options if personal vault policy * [EC-14] default to all vaults * [EC-14] add default selection to filters * [EC-14] finish filter model callbacks * [EC-14] finish filter functionality and begin cleaning up * [EC-14] clean up old components and start on org vault * [EC-14] loop through filters for presentation * [EC-14] refactor VaultFilterService and put filter presentation data back into Vault Filter component. Remove VaultService * [EC-14] begin refactoring org vault * [EC-14] Refactor Vault Filter Service to use observables * [EC-14] finish org vault filter * [EC-14] fix vault model tests * [EC-14] fix org service calls * [EC-14] pull refactor out of shared code * [EC-14] include head node for collections even if collections aren't loaded yet * [EC-14] fix url params for vaults * [EC-14] remove comments * [EC-14] Remove unnecesary getter for org on vault filter * [EC-14] fix linter * [EC-14] fix prettier * [EC-14] add deprecated methods to collection service for desktop and browser * [EC-14] simplify cipher type node check * [EC-14] add getters to vault filter model * [EC-14] refactor how we build the filter list into methods * [EC-14] add getters to build filter method * [EC-14] remove param ids if false * [EC-14] fix collapsing nodes * [EC-14] add specific type to search placeholder * [EC-14] remove extra constructor and comment from org vault filter * [EC-14] extract subscription callback to methods * [EC-14] Remove unecessary await * [EC-14] Remove ternary operators while building org filter * [EC-14] remove unnecessary deps array in vault filter service declaration * [EC-14] consolidate new models into one file * [EC-14] initialize nested observable inside of service Signed-off-by: Jacob Fink * [EC-14] change how we load orgs into the vault filter and select the default filter * [EC-14] remove get from getters name * [EC-14] remove eslint-disable comment * [EC-14] move vault filter service abstraction to angular folder and separate * [EC-14] rename filter types and delete VaultFilterLabel * [EC-14] remove changes to workspace file * [EC-14] remove deprecated service from jslib module * [EC-14] remove any remaining files from common code * [EC-14] consolidate vault filter components into components folder * [EC-14] simplify method call * [EC-14] refactor the vault filter service - orgs now have observable property - BehaviorSubjects have been migrated to ReplaySubjects if they don't need starting value - added unit tests - fix small error when selecting org badge of personal vault - renamed some properties * [EC-14] replace mergeMap with switchMap in vault filter service * [EC-14] early return to prevent nesting * [EC-14] clean up filterCollections method * [EC-14] use isDeleted helper in html * [EC-14] add jsdoc comments to ServiceUtils * [EC-14] fix linter * [EC-14] use array.slice instead of setting length * Update apps/web/src/app/vault/vault-filter/services/vault-filter.service.ts Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * [EC-14] add missing high level jsdoc description * [EC-14] fix storybook absolute imports * [EC-14] delete vault-shared.module * [EC-14] change search placeholder text to getter and add missing strings * [EC-14] remove two way binding from search text in vault filter * [EC-14] removed all binding from search text and just use input event * [EC-14] remove async from apply vault filter * [EC-14] remove circular observable calls in vault filter service Co-authored-by: Thomas Rittson * [EC-14] move collapsed nodes to vault filter section * [EC-14] deconstruct filter section inside component * [EC-14] fix merge conflicts and introduce refactored organization service to vault filter service * [EC-14] remove mutation from filter builders * [EC-14] fix styling on buildFolderTree * [EC-14] remove leftover folder-filters reference and use ternary for collapse icon * [EC-14] remove unecessary checks * [EC-14] stop rebuilding filters when the organization changes * [EC-14] Move subscription out of setter in vault filter section * [EC-14] remove extra policy service methods from vault filter service * [EC-14] remove new methods from old vault-filter.service * [EC-14] Use vault filter service in vault components * [EC-14] reload collections from vault now that we have vault filter service * [EC-14] remove currentFilterCollections in vault filter component * [EC-14] change VaultFilterType to more specific OrganizationFilter in organization-options * [EC-14] include org check in isNodeSelected * [EC-14] add getters to filter function, fix storybook, and add test for All Collections * [EC-14] show org options even if there's a personal vault policy * [EC-14] use !"AllCollections" instead of just !null * [EC-14] Remove extra org Subject in vault filter service * [EC-14] remove null check from vault search text * [EC-14] replace store/build names with set/get. Remove extra call to setOrganizationFilter * [EC-14] add take(1) to subscribe in test * [EC-14] move init logic in org vault filter component to ngOnInit * [EC-14] Fix linter * [EC-14] revert change to vault filter model * [EC-14] be specific about ignoring All Collections * [EC-14] move observable init logic to beforeEach in test * [EC-14] make buildAllFilters return something to reduce side effects Signed-off-by: Jacob Fink Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Thomas Rittson * [EC-97] Organization Billing Language / RxJS Warnings (#3688) * [EC-97] Update copy to use the word members in a few places * [EC-97] Cleanup RxJS warnings and unused properties in org billing components * [EC-599] Access Selector Component (#3717) * Add Access Selector Component and Stories * Cherry pick FormSelectionList * Fix some problems caused from cherry-pick * Fix some Web module problems caused from cherry-pick * Move AccessSelector out of the root components directory. Move UserType pipe to AccessSelectorModule * Fix broken member access selector story * Add organization feature module * Undo changes to messages.json * Fix messages.json * Remove redundant CommonModule * [EC-599] Fix avatar/icon sizing * [EC-599] Remove padding in permission column * [EC-599] Make FormSelectionList operations immutable * [EC-599] Integrate the multi-select component * [EC-599] Handle readonly/access all edge cases * [EC-599] Add initial unit tests Also cleans up public interface for the AccessSelectorComponent. Fixes a bug found during unit test creation. * [EC-599] Include item name in control labels * [EC-599] Cleanup member email display * [EC-599] Review suggestions - Change PermissionMode to Enum - Rename permControl to permissionControl to be more clear - Rename FormSelectionList file to kebab case. - Move permission row boolean logic to named function for readability * [EC-599] Cleanup AccessSelectorComponent tests - Clarify test states - Add tests for column rendering - Add tests for permission mode - Add id to column headers for testing - Fix small permissionControl bug found during testing * [EC-599] Add FormSelectionList unit tests * [EC-599] Fix unit test and linter * [EC-599] Update Enums to Pascal case * [EC-599] Undo change to Enum values * [EC-7] fix: broken build * [EC-646] Org Admin Vault Refresh November Release Prep (#3913) * [EC-646] Remove links from Manage component These links are no longer necessary as they are now located in the new OAVR tabs. * [EC-646] Re-introduce the canAccessManageTab helper * [EC-646] Re-introduce /manage route in Organization routing module - Add the parent /manage route - Add child routes for collections, people, and groups * [EC-646] Adjust Org admin tabs Re-introduce the Manage tab and remove Groups and Members tabs. * [EC-646] Change Members title back to People * [EC-646] Move missing billing components Some billing components were in the org settings module and needed to be moved the org billing module * [EC-646] Fix import file upload button -Update to use click event handler and tailwind class to hide input. Avoids inline styles/js blocked by CSP - Fix broken async pipe * [EC-646] Fix groups and people page overflow Remove the container and page-content wrapper as the pages are no longer on their own tab * [EC-646] Change People to Members Change the text regarding managing members from People to Members to more closely follow changes coming later in the OAVR. Also update the URL to use /manage/members * [EC-646] Cherry-pick ae39afe to fix tab text color * [EC-646] Fix org routing permissions helpers - Add canAccessVaultTab helper - Update canAccessOrgAdmin include check for vault tab access - Simplify canManageCollections * [EC-646] Fix Manage tab conditional logic - Add *ngIf condition for rendering Manage tab - Re-introduce dynamic route for Manage tab * Revert "[EC-14] Refactor vault filter (#3440)" (#3926) This reverts commit 4d83b81d824de467719e1cff68c0f22c1264d89d. * Remove old reference to bit-submit-button that no longer exists (#3927) * [EC-593] Top align event logs row content (#3813) * [EC-593] Top align event log row contents * [EC-593] Prevent event log timestamp from wrapping * [EC-593] Add alignContent input to bitRow directive * [EC-593] Remove ineffective inline styles (CSP) * [EC-593] Remove templated tailwind classes Tailwind minimizes the bundled stylesheet by removing classes that aren't used in code. Using a string template for the classes causes those classes to be ignored. * [EC-593] Introduce alignContent input to table story * [EC-657] Hide Billing History and Payment Method for selfhosted orgs (#3935) Signed-off-by: Jacob Fink Co-authored-by: Vincent Salucci Co-authored-by: Andreas Coroiu Co-authored-by: Jake Fink Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Thomas Rittson --- apps/web/.eslintrc.json | 2 + apps/web/src/app/app.component.ts | 20 +- apps/web/src/app/core/event.service.ts | 8 +- apps/web/src/app/core/policy-list.service.ts | 2 +- .../adjust-subscription.component.html | 0 .../adjust-subscription.component.ts | 0 .../billing-sync-api-key.component.html | 0 .../billing-sync-api-key.component.ts | 0 .../change-plan.component.html | 0 .../change-plan.component.ts | 0 .../download-license.component.html | 0 .../download-license.component.ts | 0 ...zation-billing-history-view.component.html | 27 + ...nization-billing-history-view.component.ts | 51 ++ .../organization-billing-routing.module.ts | 48 ++ .../organization-billing-tab.component.html | 33 ++ .../organization-billing-tab.component.ts | 14 + .../billing/organization-billing.module.ts | 26 + .../organization-subscription.component.html | 0 .../organization-subscription.component.ts | 47 +- .../subscription-hidden.icon.ts | 0 .../access-selector.component.html | 136 +++++ .../access-selector.component.spec.ts | 250 ++++++++ .../access-selector.component.ts | 290 ++++++++++ .../access-selector/access-selector.models.ts | 107 ++++ .../access-selector/access-selector.module.ts | 13 + .../access-selector.stories.ts | 302 ++++++++++ .../components/access-selector/index.ts | 3 + .../access-selector/user-type.pipe.ts | 29 + .../organization-layout.component.html | 71 +-- .../layouts/organization-layout.component.ts | 60 +- .../manage/events.component.html | 100 ++-- .../organizations/manage/events.component.ts | 37 +- .../organizations/manage/groups.component.ts | 2 +- .../manage/manage.component.html | 36 +- .../manage/people.component.html | 6 +- .../organizations/navigation-permissions.ts | 0 .../organization-routing.module.ts | 161 +----- .../app/organizations/organization.module.ts | 11 + .../src/app/organizations/policies/index.ts | 12 + .../policies.component.html | 0 .../policies.component.ts | 2 +- .../organizations/policies/policies.module.ts | 46 ++ .../policy-edit.component.html | 0 .../policy-edit.component.ts | 2 +- .../organization-reporting-routing.module.ts | 87 +++ .../organization-reporting.module.ts | 14 + .../reporting/reporting.component.html | 30 + .../reporting/reporting.component.ts | 36 ++ .../reporting/reports-home.component.html | 20 + .../reporting/reports-home.component.ts | 65 +++ .../settings/account.component.html | 37 +- .../settings/account.component.ts | 8 - .../src/app/organizations/settings/index.ts | 2 + .../organization-billing.component.html | 212 ------- .../organization-billing.component.ts | 153 ----- .../organization-settings-routing.module.ts | 52 ++ .../settings/organization-settings.module.ts | 21 + .../settings/settings.component.html | 45 +- .../settings/settings.component.ts | 38 +- ...families-for-enterprise-setup.component.ts | 2 +- apps/web/src/app/oss-routing.module.ts | 4 +- apps/web/src/app/reports/index.ts | 4 +- .../reports/pages/reports-home.component.ts | 3 +- apps/web/src/app/reports/reports.module.ts | 7 +- apps/web/src/app/reports/reports.ts | 2 +- apps/web/src/app/reports/shared/index.ts | 3 + .../{ => shared}/models/report-entry.ts | 0 .../{ => shared}/models/report-variant.ts | 0 .../report-card/report-card.component.html | 2 +- .../report-card/report-card.component.ts | 0 .../report-card/report-card.stories.ts | 4 +- .../report-list/report-list.component.html | 0 .../report-list/report-list.component.ts | 0 .../report-list/report-list.stories.ts | 6 +- .../reports/shared/reports-shared.module.ts | 14 + .../billing-history-view.component.html | 27 + ...t.ts => billing-history-view.component.ts} | 25 +- .../settings/billing-history.component.html | 65 +++ .../app/settings/billing-history.component.ts | 41 ++ .../settings/payment-method.component.html | 85 ++- .../app/settings/payment-method.component.ts | 124 +++- .../settings/subscription-routing.module.ts | 4 +- .../settings/two-factor-setup.component.html | 13 +- .../user-billing-history.component.html | 98 ---- .../src/app/shared/loose-components.module.ts | 71 +-- apps/web/src/app/shared/shared.module.ts | 11 +- .../tools/import-export/import.component.html | 2 +- .../organization-filter.component.html | 8 - apps/web/src/locales/en/messages.json | 120 +++- .../components/base-cva.component.ts | 7 +- .../input-text-readonly.component.html | 26 - .../input-text-readonly.component.ts | 25 - .../components/input-text.component.html | 33 -- .../components/input-text.component.ts | 48 -- .../components/select.component.html | 19 - .../components/select.component.ts | 14 - .../organizations/manage/sso.component.html | 533 ++++++++++-------- .../app/organizations/manage/sso.component.ts | 157 +++--- .../organizations-routing.module.ts | 10 +- .../app/organizations/organizations.module.ts | 14 +- .../src/directives/copy-click.directive.ts | 16 + .../src/directives/launch-click.directive.ts | 19 + libs/angular/src/jslib.module.ts | 6 + libs/angular/src/types/controls-of.ts | 20 + .../src/utils/form-selection-list.spec.ts | 253 +++++++++ libs/angular/src/utils/form-selection-list.ts | 201 +++++++ .../angular/src/validators/dirty.validator.ts | 9 - .../organization.service.abstraction.ts | 44 +- libs/common/src/models/api/sso-config.api.ts | 2 +- .../request/organization-update.request.ts | 4 + .../organization/organization-sso.request.ts | 1 + .../src/models/response/billing.response.ts | 4 + .../organization/organization-sso.response.ts | 2 + .../common/src/models/view/sso-config.view.ts | 2 +- .../src/form-field/error-summary.component.ts | 4 + .../src/form-field/error.component.ts | 2 + libs/components/src/index.ts | 1 + .../multi-select/models/select-item-view.ts | 4 +- libs/components/src/table/row.directive.ts | 18 +- libs/components/src/table/table.stories.ts | 19 +- .../tabs/shared/tab-list-item.directive.ts | 29 +- libs/components/tailwind.config.base.js | 5 +- 123 files changed, 3493 insertions(+), 1577 deletions(-) rename apps/web/src/app/organizations/{settings => billing}/adjust-subscription.component.html (100%) rename apps/web/src/app/organizations/{settings => billing}/adjust-subscription.component.ts (100%) rename apps/web/src/app/organizations/{settings => billing}/billing-sync-api-key.component.html (100%) rename apps/web/src/app/organizations/{settings => billing}/billing-sync-api-key.component.ts (100%) rename apps/web/src/app/organizations/{settings => billing}/change-plan.component.html (100%) rename apps/web/src/app/organizations/{settings => billing}/change-plan.component.ts (100%) rename apps/web/src/app/organizations/{settings => billing}/download-license.component.html (100%) rename apps/web/src/app/organizations/{settings => billing}/download-license.component.ts (100%) create mode 100644 apps/web/src/app/organizations/billing/organization-billing-history-view.component.html create mode 100644 apps/web/src/app/organizations/billing/organization-billing-history-view.component.ts create mode 100644 apps/web/src/app/organizations/billing/organization-billing-routing.module.ts create mode 100644 apps/web/src/app/organizations/billing/organization-billing-tab.component.html create mode 100644 apps/web/src/app/organizations/billing/organization-billing-tab.component.ts create mode 100644 apps/web/src/app/organizations/billing/organization-billing.module.ts rename apps/web/src/app/organizations/{settings => billing}/organization-subscription.component.html (100%) rename apps/web/src/app/organizations/{settings => billing}/organization-subscription.component.ts (93%) rename apps/web/src/app/organizations/{settings => billing}/subscription-hidden.icon.ts (100%) create mode 100644 apps/web/src/app/organizations/components/access-selector/access-selector.component.html create mode 100644 apps/web/src/app/organizations/components/access-selector/access-selector.component.spec.ts create mode 100644 apps/web/src/app/organizations/components/access-selector/access-selector.component.ts create mode 100644 apps/web/src/app/organizations/components/access-selector/access-selector.models.ts create mode 100644 apps/web/src/app/organizations/components/access-selector/access-selector.module.ts create mode 100644 apps/web/src/app/organizations/components/access-selector/access-selector.stories.ts create mode 100644 apps/web/src/app/organizations/components/access-selector/index.ts create mode 100644 apps/web/src/app/organizations/components/access-selector/user-type.pipe.ts create mode 100644 apps/web/src/app/organizations/navigation-permissions.ts create mode 100644 apps/web/src/app/organizations/organization.module.ts create mode 100644 apps/web/src/app/organizations/policies/index.ts rename apps/web/src/app/organizations/{manage => policies}/policies.component.html (100%) rename apps/web/src/app/organizations/{manage => policies}/policies.component.ts (98%) create mode 100644 apps/web/src/app/organizations/policies/policies.module.ts rename apps/web/src/app/organizations/{manage => policies}/policy-edit.component.html (100%) rename apps/web/src/app/organizations/{manage => policies}/policy-edit.component.ts (97%) create mode 100644 apps/web/src/app/organizations/reporting/organization-reporting-routing.module.ts create mode 100644 apps/web/src/app/organizations/reporting/organization-reporting.module.ts create mode 100644 apps/web/src/app/organizations/reporting/reporting.component.html create mode 100644 apps/web/src/app/organizations/reporting/reporting.component.ts create mode 100644 apps/web/src/app/organizations/reporting/reports-home.component.html create mode 100644 apps/web/src/app/organizations/reporting/reports-home.component.ts create mode 100644 apps/web/src/app/organizations/settings/index.ts delete mode 100644 apps/web/src/app/organizations/settings/organization-billing.component.html create mode 100644 apps/web/src/app/organizations/settings/organization-settings-routing.module.ts create mode 100644 apps/web/src/app/organizations/settings/organization-settings.module.ts create mode 100644 apps/web/src/app/reports/shared/index.ts rename apps/web/src/app/reports/{ => shared}/models/report-entry.ts (100%) rename apps/web/src/app/reports/{ => shared}/models/report-variant.ts (100%) rename apps/web/src/app/reports/{ => shared}/report-card/report-card.component.html (74%) rename apps/web/src/app/reports/{ => shared}/report-card/report-card.component.ts (100%) rename apps/web/src/app/reports/{ => shared}/report-card/report-card.stories.ts (88%) rename apps/web/src/app/reports/{ => shared}/report-list/report-list.component.html (100%) rename apps/web/src/app/reports/{ => shared}/report-list/report-list.component.ts (100%) rename apps/web/src/app/reports/{ => shared}/report-list/report-list.stories.ts (84%) create mode 100644 apps/web/src/app/reports/shared/reports-shared.module.ts create mode 100644 apps/web/src/app/settings/billing-history-view.component.html rename apps/web/src/app/settings/{user-billing-history.component.ts => billing-history-view.component.ts} (53%) create mode 100644 apps/web/src/app/settings/billing-history.component.html create mode 100644 apps/web/src/app/settings/billing-history.component.ts delete mode 100644 apps/web/src/app/settings/user-billing-history.component.html delete mode 100644 bitwarden_license/bit-web/src/app/organizations/components/input-text-readonly.component.html delete mode 100644 bitwarden_license/bit-web/src/app/organizations/components/input-text-readonly.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/organizations/components/input-text.component.html delete mode 100644 bitwarden_license/bit-web/src/app/organizations/components/input-text.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/organizations/components/select.component.html delete mode 100644 bitwarden_license/bit-web/src/app/organizations/components/select.component.ts create mode 100644 libs/angular/src/directives/copy-click.directive.ts create mode 100644 libs/angular/src/directives/launch-click.directive.ts create mode 100644 libs/angular/src/types/controls-of.ts create mode 100644 libs/angular/src/utils/form-selection-list.spec.ts create mode 100644 libs/angular/src/utils/form-selection-list.ts delete mode 100644 libs/angular/src/validators/dirty.validator.ts diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json index 6c519d70ae1..69dae5e732f 100644 --- a/apps/web/.eslintrc.json +++ b/apps/web/.eslintrc.json @@ -10,6 +10,8 @@ "**/app/core/*", "**/reports/*", "**/app/shared/*", + "**/organizations/settings/*", + "**/organizations/policies/*", "@bitwarden/web-vault/*", "src/**/*" ], diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index b1700a85513..9bd0fda6fcb 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -27,15 +27,17 @@ import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.ab import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; import { PolicyListService, RouterService } from "./core"; -import { DisableSendPolicy } from "./organizations/policies/disable-send.component"; -import { MasterPasswordPolicy } from "./organizations/policies/master-password.component"; -import { PasswordGeneratorPolicy } from "./organizations/policies/password-generator.component"; -import { PersonalOwnershipPolicy } from "./organizations/policies/personal-ownership.component"; -import { RequireSsoPolicy } from "./organizations/policies/require-sso.component"; -import { ResetPasswordPolicy } from "./organizations/policies/reset-password.component"; -import { SendOptionsPolicy } from "./organizations/policies/send-options.component"; -import { SingleOrgPolicy } from "./organizations/policies/single-org.component"; -import { TwoFactorAuthenticationPolicy } from "./organizations/policies/two-factor-authentication.component"; +import { + DisableSendPolicy, + MasterPasswordPolicy, + PasswordGeneratorPolicy, + PersonalOwnershipPolicy, + RequireSsoPolicy, + ResetPasswordPolicy, + SendOptionsPolicy, + SingleOrgPolicy, + TwoFactorAuthenticationPolicy, +} from "./organizations/policies"; const BroadcasterSubscriptionId = "AppComponent"; const IdleTimeout = 60000 * 10; // 10 minutes diff --git a/apps/web/src/app/core/event.service.ts b/apps/web/src/app/core/event.service.ts index 62054188100..4c4e488ff02 100644 --- a/apps/web/src/app/core/event.service.ts +++ b/apps/web/src/app/core/event.service.ts @@ -479,16 +479,14 @@ export class EventService implements OnInit, OnDestroy { private formatGroupId(ev: EventResponse) { const shortId = this.getShortId(ev.groupId); const a = this.makeAnchor(shortId); - a.setAttribute( - "href", - "#/organizations/" + ev.organizationId + "/manage/groups?search=" + shortId - ); + a.setAttribute("href", "#/organizations/" + ev.organizationId + "/groups?search=" + shortId); return a.outerHTML; } private formatCollectionId(ev: EventResponse) { const shortId = this.getShortId(ev.collectionId); const a = this.makeAnchor(shortId); + // TODO: Update view/edit collection link after EC-14 is completed a.setAttribute( "href", "#/organizations/" + ev.organizationId + "/manage/collections?search=" + shortId @@ -503,7 +501,7 @@ export class EventService implements OnInit, OnDestroy { "href", "#/organizations/" + ev.organizationId + - "/manage/people?search=" + + "/members?search=" + shortId + "&viewEvents=" + ev.organizationUserId diff --git a/apps/web/src/app/core/policy-list.service.ts b/apps/web/src/app/core/policy-list.service.ts index 70857ef8196..bb207006906 100644 --- a/apps/web/src/app/core/policy-list.service.ts +++ b/apps/web/src/app/core/policy-list.service.ts @@ -1,4 +1,4 @@ -import { BasePolicy } from "../organizations/policies/base-policy.component"; +import { BasePolicy } from "../organizations/policies"; export class PolicyListService { private policies: BasePolicy[] = []; diff --git a/apps/web/src/app/organizations/settings/adjust-subscription.component.html b/apps/web/src/app/organizations/billing/adjust-subscription.component.html similarity index 100% rename from apps/web/src/app/organizations/settings/adjust-subscription.component.html rename to apps/web/src/app/organizations/billing/adjust-subscription.component.html diff --git a/apps/web/src/app/organizations/settings/adjust-subscription.component.ts b/apps/web/src/app/organizations/billing/adjust-subscription.component.ts similarity index 100% rename from apps/web/src/app/organizations/settings/adjust-subscription.component.ts rename to apps/web/src/app/organizations/billing/adjust-subscription.component.ts diff --git a/apps/web/src/app/organizations/settings/billing-sync-api-key.component.html b/apps/web/src/app/organizations/billing/billing-sync-api-key.component.html similarity index 100% rename from apps/web/src/app/organizations/settings/billing-sync-api-key.component.html rename to apps/web/src/app/organizations/billing/billing-sync-api-key.component.html diff --git a/apps/web/src/app/organizations/settings/billing-sync-api-key.component.ts b/apps/web/src/app/organizations/billing/billing-sync-api-key.component.ts similarity index 100% rename from apps/web/src/app/organizations/settings/billing-sync-api-key.component.ts rename to apps/web/src/app/organizations/billing/billing-sync-api-key.component.ts diff --git a/apps/web/src/app/organizations/settings/change-plan.component.html b/apps/web/src/app/organizations/billing/change-plan.component.html similarity index 100% rename from apps/web/src/app/organizations/settings/change-plan.component.html rename to apps/web/src/app/organizations/billing/change-plan.component.html diff --git a/apps/web/src/app/organizations/settings/change-plan.component.ts b/apps/web/src/app/organizations/billing/change-plan.component.ts similarity index 100% rename from apps/web/src/app/organizations/settings/change-plan.component.ts rename to apps/web/src/app/organizations/billing/change-plan.component.ts diff --git a/apps/web/src/app/organizations/settings/download-license.component.html b/apps/web/src/app/organizations/billing/download-license.component.html similarity index 100% rename from apps/web/src/app/organizations/settings/download-license.component.html rename to apps/web/src/app/organizations/billing/download-license.component.html diff --git a/apps/web/src/app/organizations/settings/download-license.component.ts b/apps/web/src/app/organizations/billing/download-license.component.ts similarity index 100% rename from apps/web/src/app/organizations/settings/download-license.component.ts rename to apps/web/src/app/organizations/billing/download-license.component.ts diff --git a/apps/web/src/app/organizations/billing/organization-billing-history-view.component.html b/apps/web/src/app/organizations/billing/organization-billing-history-view.component.html new file mode 100644 index 00000000000..6622245ad1d --- /dev/null +++ b/apps/web/src/app/organizations/billing/organization-billing-history-view.component.html @@ -0,0 +1,27 @@ + + + + {{ "loading" | i18n }} + + + + diff --git a/apps/web/src/app/organizations/billing/organization-billing-history-view.component.ts b/apps/web/src/app/organizations/billing/organization-billing-history-view.component.ts new file mode 100644 index 00000000000..c352bb83f12 --- /dev/null +++ b/apps/web/src/app/organizations/billing/organization-billing-history-view.component.ts @@ -0,0 +1,51 @@ +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { concatMap, Subject, takeUntil } from "rxjs"; + +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction"; +import { BillingHistoryResponse } from "@bitwarden/common/models/response/billing-history.response"; + +@Component({ + selector: "app-org-billing-history-view", + templateUrl: "organization-billing-history-view.component.html", +}) +export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy { + loading = false; + firstLoaded = false; + billing: BillingHistoryResponse; + organizationId: string; + + private destroy$ = new Subject(); + + constructor( + private organizationApiService: OrganizationApiServiceAbstraction, + private route: ActivatedRoute + ) {} + + async ngOnInit() { + this.route.params + .pipe( + concatMap(async (params) => { + this.organizationId = params.organizationId; + await this.load(); + this.firstLoaded = true; + }), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + async load() { + if (this.loading) { + return; + } + this.loading = true; + this.billing = await this.organizationApiService.getBilling(this.organizationId); + this.loading = false; + } +} diff --git a/apps/web/src/app/organizations/billing/organization-billing-routing.module.ts b/apps/web/src/app/organizations/billing/organization-billing-routing.module.ts new file mode 100644 index 00000000000..0e410d9734d --- /dev/null +++ b/apps/web/src/app/organizations/billing/organization-billing-routing.module.ts @@ -0,0 +1,48 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { canAccessBillingTab } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; + +import { PaymentMethodComponent } from "../../settings/payment-method.component"; +import { OrganizationPermissionsGuard } from "../guards/org-permissions.guard"; + +import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component"; +import { OrganizationBillingTabComponent } from "./organization-billing-tab.component"; +import { OrganizationSubscriptionComponent } from "./organization-subscription.component"; + +const routes: Routes = [ + { + path: "", + component: OrganizationBillingTabComponent, + canActivate: [OrganizationPermissionsGuard], + data: { organizationPermissions: canAccessBillingTab }, + children: [ + { path: "", pathMatch: "full", redirectTo: "subscription" }, + { + path: "subscription", + component: OrganizationSubscriptionComponent, + data: { titleId: "subscription" }, + }, + { + path: "payment-method", + component: PaymentMethodComponent, + data: { + titleId: "paymentMethod", + }, + }, + { + path: "history", + component: OrgBillingHistoryViewComponent, + data: { + titleId: "billingHistory", + }, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class OrganizationBillingRoutingModule {} diff --git a/apps/web/src/app/organizations/billing/organization-billing-tab.component.html b/apps/web/src/app/organizations/billing/organization-billing-tab.component.html new file mode 100644 index 00000000000..7f755fccbb9 --- /dev/null +++ b/apps/web/src/app/organizations/billing/organization-billing-tab.component.html @@ -0,0 +1,33 @@ + diff --git a/apps/web/src/app/organizations/billing/organization-billing-tab.component.ts b/apps/web/src/app/organizations/billing/organization-billing-tab.component.ts new file mode 100644 index 00000000000..5eb207dee92 --- /dev/null +++ b/apps/web/src/app/organizations/billing/organization-billing-tab.component.ts @@ -0,0 +1,14 @@ +import { Component } from "@angular/core"; + +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; + +@Component({ + selector: "app-org-billing-tab", + templateUrl: "organization-billing-tab.component.html", +}) +export class OrganizationBillingTabComponent { + showPaymentAndHistory: boolean; + constructor(private platformUtilsService: PlatformUtilsService) { + this.showPaymentAndHistory = !this.platformUtilsService.isSelfHost(); + } +} diff --git a/apps/web/src/app/organizations/billing/organization-billing.module.ts b/apps/web/src/app/organizations/billing/organization-billing.module.ts new file mode 100644 index 00000000000..513b7ba766f --- /dev/null +++ b/apps/web/src/app/organizations/billing/organization-billing.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from "@angular/core"; + +import { LooseComponentsModule, SharedModule } from "../../shared"; + +import { AdjustSubscription } from "./adjust-subscription.component"; +import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component"; +import { ChangePlanComponent } from "./change-plan.component"; +import { DownloadLicenseComponent } from "./download-license.component"; +import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component"; +import { OrganizationBillingRoutingModule } from "./organization-billing-routing.module"; +import { OrganizationBillingTabComponent } from "./organization-billing-tab.component"; +import { OrganizationSubscriptionComponent } from "./organization-subscription.component"; + +@NgModule({ + imports: [SharedModule, LooseComponentsModule, OrganizationBillingRoutingModule], + declarations: [ + AdjustSubscription, + BillingSyncApiKeyComponent, + ChangePlanComponent, + DownloadLicenseComponent, + OrganizationBillingTabComponent, + OrganizationSubscriptionComponent, + OrgBillingHistoryViewComponent, + ], +}) +export class OrganizationBillingModule {} diff --git a/apps/web/src/app/organizations/settings/organization-subscription.component.html b/apps/web/src/app/organizations/billing/organization-subscription.component.html similarity index 100% rename from apps/web/src/app/organizations/settings/organization-subscription.component.html rename to apps/web/src/app/organizations/billing/organization-subscription.component.html diff --git a/apps/web/src/app/organizations/settings/organization-subscription.component.ts b/apps/web/src/app/organizations/billing/organization-subscription.component.ts similarity index 93% rename from apps/web/src/app/organizations/settings/organization-subscription.component.ts rename to apps/web/src/app/organizations/billing/organization-subscription.component.ts index dbcdfd907ae..803be05097c 100644 --- a/apps/web/src/app/organizations/settings/organization-subscription.component.ts +++ b/apps/web/src/app/organizations/billing/organization-subscription.component.ts @@ -1,5 +1,6 @@ -import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; +import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; +import { concatMap, Subject, takeUntil } from "rxjs"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; import { ModalService } from "@bitwarden/angular/services/modal.service"; @@ -27,17 +28,13 @@ import { SubscriptionHiddenIcon } from "./subscription-hidden.icon"; selector: "app-org-subscription", templateUrl: "organization-subscription.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class OrganizationSubscriptionComponent implements OnInit { +export class OrganizationSubscriptionComponent implements OnInit, OnDestroy { @ViewChild("setupBillingSyncTemplate", { read: ViewContainerRef, static: true }) setupBillingSyncModalRef: ViewContainerRef; loading = false; firstLoaded = false; organizationId: string; - adjustSeatsAdd = true; - showAdjustSeats = false; - showAdjustSeatAutoscale = false; adjustStorageAdd = true; showAdjustStorage = false; showUpdateLicense = false; @@ -61,6 +58,8 @@ export class OrganizationSubscriptionComponent implements OnInit { subscriptionHiddenIcon = SubscriptionHiddenIcon; + private destroy$ = new Subject(); + constructor( private apiService: ApiService, private platformUtilsService: PlatformUtilsService, @@ -76,19 +75,27 @@ export class OrganizationSubscriptionComponent implements OnInit { } async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - this.organizationId = params.organizationId; - await this.load(); - this.firstLoaded = true; - }); + this.route.params + .pipe( + concatMap(async (params) => { + this.organizationId = params.organizationId; + await this.load(); + this.firstLoaded = true; + }), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); } async load() { if (this.loading) { return; } - this.loading = true; this.userOrg = this.organizationService.get(this.organizationId); if (this.userOrg.canManageBilling) { @@ -175,7 +182,7 @@ export class OrganizationSubscriptionComponent implements OnInit { this.showChangePlan = !this.showChangePlan; } - closeChangePlan(changed: boolean) { + closeChangePlan() { this.showChangePlan = false; } @@ -192,10 +199,14 @@ export class OrganizationSubscriptionComponent implements OnInit { comp.hasBillingToken = this.hasBillingSyncToken; } ); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - ref.onClosed.subscribe(async () => { - await this.load(); - }); + ref.onClosed + .pipe( + concatMap(async () => { + await this.load(); + }), + takeUntil(this.destroy$) + ) + .subscribe(); } closeDownloadLicense() { diff --git a/apps/web/src/app/organizations/settings/subscription-hidden.icon.ts b/apps/web/src/app/organizations/billing/subscription-hidden.icon.ts similarity index 100% rename from apps/web/src/app/organizations/settings/subscription-hidden.icon.ts rename to apps/web/src/app/organizations/billing/subscription-hidden.icon.ts diff --git a/apps/web/src/app/organizations/components/access-selector/access-selector.component.html b/apps/web/src/app/organizations/components/access-selector/access-selector.component.html new file mode 100644 index 00000000000..10390b3e053 --- /dev/null +++ b/apps/web/src/app/organizations/components/access-selector/access-selector.component.html @@ -0,0 +1,136 @@ +
+ + {{ "permission" | i18n }} + + + + + {{ selectorLabelText }} + + {{ selectorHelpText }} + +
+ + + + + {{ columnHeader }} + + {{ "permission" | i18n }} + + {{ "role" | i18n }} + {{ "group" | i18n }} + + + + + + +
+ +
+
+ {{ item.labelName }} + + {{ "invited" | i18n }} + +
+
{{ item.email }}
+
+
+ +
+ + {{ item.labelName }} +
+ + + + + + + + + +
+ {{ "canEdit" | i18n }} + +
+ +
+ {{ permissionLabelId(item.readonlyPermission) | i18n }} +
+
+ + + + {{ item.role | userType: "-" }} + + + + {{ item.viaGroupName ?? "-" }} + + + + + + + + {{ emptySelectionText }} + +
+
diff --git a/apps/web/src/app/organizations/components/access-selector/access-selector.component.spec.ts b/apps/web/src/app/organizations/components/access-selector/access-selector.component.spec.ts new file mode 100644 index 00000000000..3b2ba911aac --- /dev/null +++ b/apps/web/src/app/organizations/components/access-selector/access-selector.component.spec.ts @@ -0,0 +1,250 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; +import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; +import { + AvatarModule, + BadgeModule, + ButtonModule, + FormFieldModule, + IconButtonModule, + TableModule, + TabsModule, +} from "@bitwarden/components"; +import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view"; + +import { PreloadedEnglishI18nModule } from "../../../tests/preloaded-english-i18n.module"; + +import { AccessSelectorComponent, PermissionMode } from "./access-selector.component"; +import { AccessItemType, CollectionPermission } from "./access-selector.models"; +import { UserTypePipe } from "./user-type.pipe"; + +/** + * Helper class that makes it easier to test the AccessSelectorComponent by + * exposing some protected methods/properties + */ +class TestableAccessSelectorComponent extends AccessSelectorComponent { + selectItems(items: SelectItemView[]) { + super.selectItems(items); + } + deselectItem(id: string) { + this.selectionList.deselectItem(id); + } + + /** + * Helper used to simulate a user selecting a new permission for a table row + * @param index - "Row" index + * @param perm - The new permission value + */ + changeSelectedItemPerm(index: number, perm: CollectionPermission) { + this.selectionList.formArray.at(index).patchValue({ + permission: perm, + }); + } +} + +describe("AccessSelectorComponent", () => { + let component: TestableAccessSelectorComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + ButtonModule, + FormFieldModule, + AvatarModule, + BadgeModule, + ReactiveFormsModule, + FormsModule, + TabsModule, + TableModule, + PreloadedEnglishI18nModule, + JslibModule, + IconButtonModule, + ], + declarations: [TestableAccessSelectorComponent, UserTypePipe], + providers: [], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TestableAccessSelectorComponent); + component = fixture.componentInstance; + + component.emptySelectionText = "Nothing selected"; + + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + describe("item selection", () => { + beforeEach(() => { + component.items = [ + { + id: "123", + type: AccessItemType.Group, + labelName: "Group 1", + listName: "Group 1", + }, + ]; + fixture.detectChanges(); + }); + + it("should show the empty row when nothing is selected", () => { + const emptyTableCell = fixture.nativeElement.querySelector("tbody tr td"); + expect(emptyTableCell?.textContent).toEqual("Nothing selected"); + }); + + it("should show one row when one value is selected", () => { + component.selectItems([{ id: "123" } as any]); + fixture.detectChanges(); + const firstColSpan = fixture.nativeElement.querySelector("tbody tr td span"); + expect(firstColSpan.textContent).toEqual("Group 1"); + }); + + it("should emit value change when a value is selected", () => { + // Arrange + const mockChange = jest.fn(); + component.registerOnChange(mockChange); + component.permissionMode = PermissionMode.Edit; + + // Act + component.selectItems([{ id: "123" } as any]); + + // Assert + expect(mockChange.mock.calls.length).toEqual(1); + expect(mockChange.mock.lastCall[0]).toHaveProperty("[0].id", "123"); + }); + + it("should emit value change when a row is modified", () => { + // Arrange + const mockChange = jest.fn(); + component.permissionMode = PermissionMode.Edit; + component.selectItems([{ id: "123" } as any]); + component.registerOnChange(mockChange); // Register change listener after setup + + // Act + component.changeSelectedItemPerm(0, CollectionPermission.Edit); + + // Assert + expect(mockChange.mock.calls.length).toEqual(1); + expect(mockChange.mock.lastCall[0]).toHaveProperty("[0].id", "123"); + expect(mockChange.mock.lastCall[0]).toHaveProperty( + "[0].permission", + CollectionPermission.Edit + ); + }); + + it("should emit value change when a row is removed", () => { + // Arrange + const mockChange = jest.fn(); + component.permissionMode = PermissionMode.Edit; + component.selectItems([{ id: "123" } as any]); + component.registerOnChange(mockChange); // Register change listener after setup + + // Act + component.deselectItem("123"); + + // Assert + expect(mockChange.mock.calls.length).toEqual(1); + expect(mockChange.mock.lastCall[0].length).toEqual(0); + }); + + it("should emit permission values when in edit mode", () => { + // Arrange + const mockChange = jest.fn(); + component.registerOnChange(mockChange); + component.permissionMode = PermissionMode.Edit; + + // Act + component.selectItems([{ id: "123" } as any]); + + // Assert + expect(mockChange.mock.calls.length).toEqual(1); + expect(mockChange.mock.lastCall[0]).toHaveProperty("[0].id", "123"); + expect(mockChange.mock.lastCall[0]).toHaveProperty("[0].permission"); + }); + + it("should not emit permission values when not in edit mode", () => { + // Arrange + const mockChange = jest.fn(); + component.registerOnChange(mockChange); + component.permissionMode = PermissionMode.Hidden; + + // Act + component.selectItems([{ id: "123" } as any]); + + // Assert + expect(mockChange.mock.calls.length).toEqual(1); + expect(mockChange.mock.lastCall[0]).toHaveProperty("[0].id", "123"); + expect(mockChange.mock.lastCall[0]).not.toHaveProperty("[0].permission"); + }); + }); + + describe("column rendering", () => { + beforeEach(() => { + component.items = [ + { + id: "g1", + type: AccessItemType.Group, + labelName: "Group 1", + listName: "Group 1", + }, + { + id: "m1", + type: AccessItemType.Member, + labelName: "Member 1", + listName: "Member 1 (member1@email.com)", + email: "member1@email.com", + role: OrganizationUserType.Manager, + status: OrganizationUserStatusType.Confirmed, + }, + ]; + fixture.detectChanges(); + }); + + test.each([true, false])("should show the role column when enabled", (columnEnabled) => { + // Act + component.showMemberRoles = columnEnabled; + fixture.detectChanges(); + + // Assert + const colHeading = fixture.nativeElement.querySelector("#roleColHeading"); + expect(!!colHeading).toEqual(columnEnabled); + }); + + test.each([true, false])("should show the group column when enabled", (columnEnabled) => { + // Act + component.showGroupColumn = columnEnabled; + fixture.detectChanges(); + + // Assert + const colHeading = fixture.nativeElement.querySelector("#groupColHeading"); + expect(!!colHeading).toEqual(columnEnabled); + }); + + const permissionColumnCases = [ + [PermissionMode.Hidden, false], + [PermissionMode.Edit, true], + [PermissionMode.Readonly, true], + ]; + + test.each(permissionColumnCases)( + "should show the permission column when enabled", + (mode: PermissionMode, shouldShowColumn) => { + // Act + component.permissionMode = mode; + fixture.detectChanges(); + + // Assert + const colHeading = fixture.nativeElement.querySelector("#permissionColHeading"); + expect(!!colHeading).toEqual(shouldShowColumn); + } + ); + }); +}); diff --git a/apps/web/src/app/organizations/components/access-selector/access-selector.component.ts b/apps/web/src/app/organizations/components/access-selector/access-selector.component.ts new file mode 100644 index 00000000000..98a49d5c3ab --- /dev/null +++ b/apps/web/src/app/organizations/components/access-selector/access-selector.component.ts @@ -0,0 +1,290 @@ +import { Component, forwardRef, Input, OnDestroy, OnInit } from "@angular/core"; +import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from "@angular/forms"; +import { Subject, takeUntil } from "rxjs"; + +import { FormSelectionList } from "@bitwarden/angular/utils/form-selection-list"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view"; + +import { + AccessItemType, + AccessItemValue, + AccessItemView, + CollectionPermission, +} from "./access-selector.models"; + +export enum PermissionMode { + /** + * No permission controls or column present. No permission values are emitted. + */ + Hidden = "hidden", + + /** + * No permission controls. Column rendered an if available on an item. No permission values are emitted + */ + Readonly = "readonly", + + /** + * Permission Controls and column present. Permission values are emitted. + */ + Edit = "edit", +} + +@Component({ + selector: "bit-access-selector", + templateUrl: "access-selector.component.html", + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AccessSelectorComponent), + multi: true, + }, + ], +}) +export class AccessSelectorComponent implements ControlValueAccessor, OnInit, OnDestroy { + private destroy$ = new Subject(); + private notifyOnChange: (v: unknown) => void; + private notifyOnTouch: () => void; + private pauseChangeNotification: boolean; + + /** + * The internal selection list that tracks the value of this form control / component. + * It's responsible for keeping items sorted and synced with the rendered form controls + * @protected + */ + protected selectionList = new FormSelectionList((item) => { + const permissionControl = this.formBuilder.control(this.initialPermission); + + const fg = this.formBuilder.group({ + id: item.id, + type: item.type, + permission: permissionControl, + }); + + // Disable entire row form group if readonly + if (item.readonly) { + fg.disable(); + } + + // Disable permission control if accessAllItems is enabled + if (item.accessAllItems || this.permissionMode != PermissionMode.Edit) { + permissionControl.disable(); + } + + return fg; + }, this._itemComparator.bind(this)); + + /** + * Internal form group for this component. + * @protected + */ + protected formGroup = this.formBuilder.group({ + items: this.selectionList.formArray, + }); + + protected itemType = AccessItemType; + protected permissionList = [ + { perm: CollectionPermission.View, labelId: "canView" }, + { perm: CollectionPermission.ViewExceptPass, labelId: "canViewExceptPass" }, + { perm: CollectionPermission.Edit, labelId: "canEdit" }, + { perm: CollectionPermission.EditExceptPass, labelId: "canEditExceptPass" }, + ]; + protected initialPermission = CollectionPermission.View; + + disabled: boolean; + + /** + * List of all selectable items that. Sorted internally. + */ + @Input() + get items(): AccessItemView[] { + return this.selectionList.allItems; + } + + set items(val: AccessItemView[]) { + const selected = (this.selectionList.formArray.getRawValue() ?? []).concat( + val.filter((m) => m.readonly) + ); + this.selectionList.populateItems( + val.map((m) => { + m.icon = m.icon ?? this.itemIcon(m); // Ensure an icon is set + return m; + }), + selected + ); + } + + /** + * Permission mode that controls if the permission form controls and column should be present. + */ + @Input() + get permissionMode(): PermissionMode { + return this._permissionMode; + } + + set permissionMode(value: PermissionMode) { + this._permissionMode = value; + // Toggle any internal permission controls + for (const control of this.selectionList.formArray.controls) { + if (value == PermissionMode.Edit) { + control.get("permission").enable(); + } else { + control.get("permission").disable(); + } + } + } + private _permissionMode: PermissionMode = PermissionMode.Hidden; + + /** + * Column header for the selected items table + */ + @Input() columnHeader: string; + + /** + * Label used for the ng selector + */ + @Input() selectorLabelText: string; + + /** + * Helper text displayed under the ng selector + */ + @Input() selectorHelpText: string; + + /** + * Text that is shown in the table when no items are selected + */ + @Input() emptySelectionText: string; + + /** + * Flag for if the member roles column should be present + */ + @Input() showMemberRoles: boolean; + + /** + * Flag for if the group column should be present + */ + @Input() showGroupColumn: boolean; + + constructor( + private readonly formBuilder: FormBuilder, + private readonly i18nService: I18nService + ) {} + + /** Required for NG_VALUE_ACCESSOR */ + registerOnChange(fn: any): void { + this.notifyOnChange = fn; + } + + /** Required for NG_VALUE_ACCESSOR */ + registerOnTouched(fn: any): void { + this.notifyOnTouch = fn; + } + + /** Required for NG_VALUE_ACCESSOR */ + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + + // Keep the internal FormGroup in sync + if (this.disabled) { + this.formGroup.disable(); + } else { + this.formGroup.enable(); + } + } + + /** Required for NG_VALUE_ACCESSOR */ + writeValue(selectedItems: AccessItemValue[]): void { + // Modifying the selection list, mistakenly fires valueChanges in the + // internal form array, so we need to know to pause external notification + this.pauseChangeNotification = true; + + // Always clear the internal selection list on a new value + this.selectionList.deselectAll(); + + // We need to also select any read only items to appear in the table + this.selectionList.selectItems(this.items.filter((m) => m.readonly).map((m) => m.id)); + + // If the new value is null, then we're done + if (selectedItems == null) { + this.pauseChangeNotification = false; + return; + } + + // Unable to handle other value types, throw + if (!Array.isArray(selectedItems)) { + throw new Error("The access selector component only supports Array form values!"); + } + + // Iterate and internally select each item + for (const value of selectedItems) { + this.selectionList.selectItem(value.id, value); + } + + this.pauseChangeNotification = false; + } + + ngOnInit() { + // Watch the internal formArray for changes and propagate them + this.selectionList.formArray.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((v) => { + if (!this.notifyOnChange || this.pauseChangeNotification) { + return; + } + this.notifyOnChange(v); + }); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + protected handleBlur() { + if (!this.notifyOnTouch) { + return; + } + + this.notifyOnTouch(); + } + + protected selectItems(items: SelectItemView[]) { + this.pauseChangeNotification = true; + this.selectionList.selectItems(items.map((i) => i.id)); + this.pauseChangeNotification = false; + if (this.notifyOnChange != undefined) { + this.notifyOnChange(this.selectionList.formArray.value); + } + } + + protected itemIcon(item: AccessItemView) { + switch (item.type) { + case AccessItemType.Collection: + return "bwi-collection"; + case AccessItemType.Group: + return "bwi-users"; + case AccessItemType.Member: + return "bwi-user"; + } + } + + protected permissionLabelId(perm: CollectionPermission) { + return this.permissionList.find((p) => p.perm == perm)?.labelId; + } + + protected accessAllLabelId(item: AccessItemView) { + return item.type == AccessItemType.Group ? "groupAccessAll" : "memberAccessAll"; + } + + protected canEditItemPermission(item: AccessItemView) { + return this.permissionMode == PermissionMode.Edit && !item.readonly && !item.accessAllItems; + } + + private _itemComparator(a: AccessItemView, b: AccessItemView) { + if (a.type != b.type) { + return a.type - b.type; + } + return this.i18nService.collator.compare( + a.listName + a.labelName + a.readonly, + b.listName + b.labelName + b.readonly + ); + } +} diff --git a/apps/web/src/app/organizations/components/access-selector/access-selector.models.ts b/apps/web/src/app/organizations/components/access-selector/access-selector.models.ts new file mode 100644 index 00000000000..d621de271e0 --- /dev/null +++ b/apps/web/src/app/organizations/components/access-selector/access-selector.models.ts @@ -0,0 +1,107 @@ +import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; +import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; +import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; +import { SelectionReadOnlyResponse } from "@bitwarden/common/models/response/selection-read-only.response"; +import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view"; + +/** + * Permission options that replace/correspond with readOnly and hidePassword server fields. + */ +export enum CollectionPermission { + View = "view", + ViewExceptPass = "viewExceptPass", + Edit = "edit", + EditExceptPass = "editExceptPass", +} + +export enum AccessItemType { + Collection, + Group, + Member, +} + +/** + * A "generic" type that describes an item that can be selected from a + * ng-select list and have its collection permission modified. + * + * Currently, it supports Collections, Groups, and Members. Members require some additional + * details to render in the AccessSelectorComponent so their type is defined separately + * and then joined back with the base type. + * + */ +export type AccessItemView = + | SelectItemView & { + /** + * Flag that this group/member can access all items. + * This will disable the permission editor for this item. + */ + accessAllItems?: boolean; + + /** + * Flag that this item cannot be modified. + * This will disable the permission editor and will keep + * the item always selected. + */ + readonly?: boolean; + + /** + * Optional permission that will be rendered for this + * item if it set to readonly. + */ + readonlyPermission?: CollectionPermission; + } & ( + | { + type: AccessItemType.Collection; + viaGroupName?: string; + } + | { + type: AccessItemType.Group; + } + | { + type: AccessItemType.Member; // Members have a few extra details required to display, so they're added here + email: string; + role: OrganizationUserType; + status: OrganizationUserStatusType; + } + ); + +/** + * A type that is emitted as a value for the ngControl + */ +export type AccessItemValue = { + id: string; + permission?: CollectionPermission; + type: AccessItemType; +}; + +/** + * Converts the older SelectionReadOnly interface to one of the new CollectionPermission values + * for the dropdown in the AccessSelectorComponent + * @param value + */ +export const convertToPermission = (value: SelectionReadOnlyResponse) => { + if (value.readOnly) { + return value.hidePasswords ? CollectionPermission.ViewExceptPass : CollectionPermission.View; + } else { + return value.hidePasswords ? CollectionPermission.EditExceptPass : CollectionPermission.Edit; + } +}; + +/** + * Converts an AccessItemValue back into a SelectionReadOnly class using the CollectionPermission + * to determine the values for `readOnly` and `hidePassword` + * @param value + */ +export const convertToSelectionReadOnly = (value: AccessItemValue) => { + return new SelectionReadOnlyRequest( + value.id, + readOnly(value.permission), + hidePassword(value.permission) + ); +}; + +const readOnly = (perm: CollectionPermission) => + [CollectionPermission.View, CollectionPermission.ViewExceptPass].includes(perm); + +const hidePassword = (perm: CollectionPermission) => + [CollectionPermission.ViewExceptPass, CollectionPermission.EditExceptPass].includes(perm); diff --git a/apps/web/src/app/organizations/components/access-selector/access-selector.module.ts b/apps/web/src/app/organizations/components/access-selector/access-selector.module.ts new file mode 100644 index 00000000000..cbb01137b4d --- /dev/null +++ b/apps/web/src/app/organizations/components/access-selector/access-selector.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from "@angular/core"; + +import { SharedModule } from "../../../shared"; + +import { AccessSelectorComponent } from "./access-selector.component"; +import { UserTypePipe } from "./user-type.pipe"; + +@NgModule({ + imports: [SharedModule], + declarations: [AccessSelectorComponent, UserTypePipe], + exports: [AccessSelectorComponent], +}) +export class AccessSelectorModule {} diff --git a/apps/web/src/app/organizations/components/access-selector/access-selector.stories.ts b/apps/web/src/app/organizations/components/access-selector/access-selector.stories.ts new file mode 100644 index 00000000000..059fb1c430c --- /dev/null +++ b/apps/web/src/app/organizations/components/access-selector/access-selector.stories.ts @@ -0,0 +1,302 @@ +import { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { action } from "@storybook/addon-actions"; +import { Meta, moduleMetadata, Story } from "@storybook/angular"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; +import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; +import { + AvatarModule, + BadgeModule, + ButtonModule, + FormFieldModule, + IconButtonModule, + TableModule, + TabsModule, +} from "@bitwarden/components"; + +import { PreloadedEnglishI18nModule } from "../../../tests/preloaded-english-i18n.module"; + +import { AccessSelectorComponent } from "./access-selector.component"; +import { AccessItemType, AccessItemView, CollectionPermission } from "./access-selector.models"; +import { UserTypePipe } from "./user-type.pipe"; + +export default { + title: "Web/Organizations/Access Selector", + decorators: [ + moduleMetadata({ + declarations: [AccessSelectorComponent, UserTypePipe], + imports: [ + ButtonModule, + FormFieldModule, + AvatarModule, + BadgeModule, + ReactiveFormsModule, + FormsModule, + TabsModule, + TableModule, + PreloadedEnglishI18nModule, + JslibModule, + IconButtonModule, + ], + providers: [], + }), + ], + parameters: {}, + argTypes: { + formObj: { table: { disable: true } }, + }, +} as Meta; + +const actionsData = { + onValueChanged: action("onValueChanged"), + onSubmit: action("onSubmit"), +}; + +/** + * Factory to help build semi-realistic looking items + * @param n - The number of items to build + * @param type - Which type to build + */ +const itemsFactory = (n: number, type: AccessItemType) => { + return [...Array(n)].map((_: unknown, id: number) => { + const item: AccessItemView = { + id: id.toString(), + type: type, + } as AccessItemView; + + switch (item.type) { + case AccessItemType.Collection: + item.labelName = item.listName = `Collection ${id}`; + item.id = item.id + "c"; + item.parentGrouping = "Collection Parent Group " + ((id % 2) + 1); + break; + case AccessItemType.Group: + item.labelName = item.listName = `Group ${id}`; + item.id = item.id + "g"; + break; + case AccessItemType.Member: + item.id = item.id + "m"; + item.email = `member${id}@email.com`; + item.status = id % 3 == 0 ? 0 : 2; + item.labelName = item.status == 2 ? `Member ${id}` : item.email; + item.listName = item.status == 2 ? `${item.labelName} (${item.email})` : item.email; + item.role = id % 5; + break; + } + + return item; + }); +}; + +const sampleMembers = itemsFactory(10, AccessItemType.Member); +const sampleGroups = itemsFactory(6, AccessItemType.Group); + +const StandaloneAccessSelectorTemplate: Story = ( + args: AccessSelectorComponent +) => ({ + props: { + items: [], + valueChanged: actionsData.onValueChanged, + initialValue: [], + ...args, + }, + template: ` + +`, +}); + +const memberCollectionAccessItems = itemsFactory(3, AccessItemType.Collection).concat([ + { + id: "c1-group1", + type: AccessItemType.Collection, + labelName: "Collection 1", + listName: "Collection 1", + viaGroupName: "Group 1", + readonlyPermission: CollectionPermission.View, + readonly: true, + }, + { + id: "c1-group2", + type: AccessItemType.Collection, + labelName: "Collection 1", + listName: "Collection 1", + viaGroupName: "Group 2", + readonlyPermission: CollectionPermission.ViewExceptPass, + readonly: true, + }, +]); + +export const MemberCollectionAccess = StandaloneAccessSelectorTemplate.bind({}); +MemberCollectionAccess.args = { + permissionMode: "edit", + showMemberRoles: false, + showGroupColumn: true, + columnHeader: "Collection", + selectorLabelText: "Select Collections", + selectorHelpText: "Some helper text describing what this does", + emptySelectionText: "No collections added", + disabled: false, + initialValue: [], + items: memberCollectionAccessItems, +}; +MemberCollectionAccess.story = { + parameters: { + docs: { + storyDescription: ` + Example of an access selector for modifying the collections a member has access to. + Includes examples of a readonly group and member that cannot be edited. + `, + }, + }, +}; + +export const MemberGroupAccess = StandaloneAccessSelectorTemplate.bind({}); +MemberGroupAccess.args = { + permissionMode: "readonly", + showMemberRoles: false, + columnHeader: "Groups", + selectorLabelText: "Select Groups", + selectorHelpText: "Some helper text describing what this does", + emptySelectionText: "No groups added", + disabled: false, + initialValue: [{ id: "3g" }, { id: "0g" }], + items: itemsFactory(4, AccessItemType.Group).concat([ + { + id: "admin", + type: AccessItemType.Group, + listName: "Admin Group", + labelName: "Admin Group", + accessAllItems: true, + }, + ]), +}; +MemberGroupAccess.story = { + parameters: { + docs: { + storyDescription: ` + Example of an access selector for selecting which groups an individual member belongs too. + `, + }, + }, +}; + +export const GroupMembersAccess = StandaloneAccessSelectorTemplate.bind({}); +GroupMembersAccess.args = { + permissionMode: "hidden", + showMemberRoles: true, + columnHeader: "Members", + selectorLabelText: "Select Members", + selectorHelpText: "Some helper text describing what this does", + emptySelectionText: "No members added", + disabled: false, + initialValue: [{ id: "2m" }, { id: "0m" }], + items: sampleMembers, +}; +GroupMembersAccess.story = { + parameters: { + docs: { + storyDescription: ` + Example of an access selector for selecting which members belong to an specific group. + `, + }, + }, +}; + +export const CollectionAccess = StandaloneAccessSelectorTemplate.bind({}); +CollectionAccess.args = { + permissionMode: "edit", + showMemberRoles: false, + columnHeader: "Groups/Members", + selectorLabelText: "Select groups and members", + selectorHelpText: + "Permissions set for a member will replace permissions set by that member's group", + emptySelectionText: "No members or groups added", + disabled: false, + initialValue: [ + { id: "3g", permission: CollectionPermission.EditExceptPass }, + { id: "0m", permission: CollectionPermission.View }, + ], + items: sampleGroups.concat(sampleMembers).concat([ + { + id: "admin-group", + type: AccessItemType.Group, + listName: "Admin Group", + labelName: "Admin Group", + accessAllItems: true, + readonly: true, + }, + { + id: "admin-member", + type: AccessItemType.Member, + listName: "Admin Member (admin@email.com)", + labelName: "Admin Member", + status: OrganizationUserStatusType.Confirmed, + role: OrganizationUserType.Admin, + email: "admin@email.com", + accessAllItems: true, + readonly: true, + }, + ]), +}; +GroupMembersAccess.story = { + parameters: { + docs: { + storyDescription: ` + Example of an access selector for selecting which members/groups have access to a specific collection. + `, + }, + }, +}; + +const fb = new FormBuilder(); + +const ReactiveFormAccessSelectorTemplate: Story = ( + args: AccessSelectorComponent +) => ({ + props: { + items: [], + onSubmit: actionsData.onSubmit, + ...args, + }, + template: ` +
+ + +
+`, +}); + +export const ReactiveForm = ReactiveFormAccessSelectorTemplate.bind({}); +ReactiveForm.args = { + formObj: fb.group({ formItems: [[{ id: "1g" }]] }), + permissionMode: "edit", + showMemberRoles: false, + columnHeader: "Groups/Members", + selectorLabelText: "Select groups and members", + selectorHelpText: + "Permissions set for a member will replace permissions set by that member's group", + emptySelectionText: "No members or groups added", + items: sampleGroups.concat(sampleMembers), +}; diff --git a/apps/web/src/app/organizations/components/access-selector/index.ts b/apps/web/src/app/organizations/components/access-selector/index.ts new file mode 100644 index 00000000000..86624f8e941 --- /dev/null +++ b/apps/web/src/app/organizations/components/access-selector/index.ts @@ -0,0 +1,3 @@ +export * from "./access-selector.component"; +export * from "./access-selector.module"; +export * from "./access-selector.models"; diff --git a/apps/web/src/app/organizations/components/access-selector/user-type.pipe.ts b/apps/web/src/app/organizations/components/access-selector/user-type.pipe.ts new file mode 100644 index 00000000000..6ef78cb65ea --- /dev/null +++ b/apps/web/src/app/organizations/components/access-selector/user-type.pipe.ts @@ -0,0 +1,29 @@ +import { Pipe, PipeTransform } from "@angular/core"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; + +@Pipe({ + name: "userType", +}) +export class UserTypePipe implements PipeTransform { + constructor(private i18nService: I18nService) {} + + transform(value?: OrganizationUserType, unknownText?: string): string { + if (value == null) { + return unknownText ?? this.i18nService.t("unknown"); + } + switch (value) { + case OrganizationUserType.Owner: + return this.i18nService.t("owner"); + case OrganizationUserType.Admin: + return this.i18nService.t("admin"); + case OrganizationUserType.User: + return this.i18nService.t("user"); + case OrganizationUserType.Manager: + return this.i18nService.t("manager"); + case OrganizationUserType.Custom: + return this.i18nService.t("custom"); + } + } +} diff --git a/apps/web/src/app/organizations/layouts/organization-layout.component.html b/apps/web/src/app/organizations/layouts/organization-layout.component.html index 7c04938cf30..5f5aa497f78 100644 --- a/apps/web/src/app/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/organizations/layouts/organization-layout.component.html @@ -1,49 +1,32 @@ - -
-
- +
+
+
+ + + {{ "vault" | i18n }} + + {{ "manage" | i18n }} + + + {{ getReportTabLabel(organization) | i18n }} + + {{ + "billing" | i18n + }} + {{ + "settings" | i18n + }} +
- +
+ diff --git a/apps/web/src/app/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/organizations/layouts/organization-layout.component.ts index fe9d9de8b1a..8c871f74db1 100644 --- a/apps/web/src/app/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/organizations/layouts/organization-layout.component.ts @@ -3,11 +3,14 @@ import { ActivatedRoute } from "@angular/router"; import { map, mergeMap, Observable, Subject, takeUntil } from "rxjs"; import { - OrganizationService, - getOrganizationById, + canAccessBillingTab, + canAccessGroupsTab, canAccessManageTab, + canAccessMembersTab, + canAccessReportingTab, canAccessSettingsTab, - canAccessToolsTab, + getOrganizationById, + OrganizationService, } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/models/domain/organization"; @@ -17,7 +20,6 @@ import { Organization } from "@bitwarden/common/models/domain/organization"; }) export class OrganizationLayoutComponent implements OnInit, OnDestroy { organization$: Observable; - businessTokenPromise: Promise; private _destroy = new Subject(); @@ -43,27 +45,43 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { this._destroy.complete(); } - canShowManageTab(organization: Organization): boolean { - return canAccessManageTab(organization); - } - - canShowToolsTab(organization: Organization): boolean { - return canAccessToolsTab(organization); - } - canShowSettingsTab(organization: Organization): boolean { return canAccessSettingsTab(organization); } - getToolsRoute(organization: Organization): string { - return organization.canAccessImportExport ? "tools/import" : "tools/exposed-passwords-report"; + canShowManageTab(organization: Organization): boolean { + return canAccessManageTab(organization); + } + + canShowMembersTab(organization: Organization): boolean { + return canAccessMembersTab(organization); + } + + canShowGroupsTab(organization: Organization): boolean { + return canAccessGroupsTab(organization); + } + + canShowReportsTab(organization: Organization): boolean { + return canAccessReportingTab(organization); + } + + canShowBillingTab(organization: Organization): boolean { + return canAccessBillingTab(organization); + } + + getReportTabLabel(organization: Organization): string { + return organization.useEvents ? "reporting" : "reports"; + } + + getReportRoute(organization: Organization): string { + return organization.useEvents ? "reporting/events" : "reporting/reports"; } getManageRoute(organization: Organization): string { let route: string; switch (true) { case organization.canManageUsers: - route = "manage/people"; + route = "manage/members"; break; case organization.canViewAssignedCollections || organization.canViewAllCollections: route = "manage/collections"; @@ -71,18 +89,6 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { case organization.canManageGroups: route = "manage/groups"; break; - case organization.canManagePolicies: - route = "manage/policies"; - break; - case organization.canManageSso: - route = "manage/sso"; - break; - case organization.canManageScim: - route = "manage/scim"; - break; - case organization.canAccessEventLogs: - route = "manage/events"; - break; } return route; } diff --git a/apps/web/src/app/organizations/manage/events.component.html b/apps/web/src/app/organizations/manage/events.component.html index d27b53e8d9d..a7468bcf378 100644 --- a/apps/web/src/app/organizations/manage/events.component.html +++ b/apps/web/src/app/organizations/manage/events.component.html @@ -1,54 +1,57 @@ -