From 80f5a883e088e941c415f224d1fa7af3dc2b6cd7 Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Fri, 18 Nov 2022 13:20:19 +0100 Subject: [PATCH 001/205] [PS-1884] [TDL-189] [TDL-203] Move libs/node files to CLI and rename per ADR12 (#4069) * Extract files only used in cli out of libs/node Move commands from libs/node to cli Move program from libs/node to cli Move services from libs/node to cli Move specs from libs/node to cli Naming changes based on ADR 12 Rename commands Rename models/request Rename models/response Remove entries from whitelist-capital-letters.txt * Merge lowDbStorageService into base class Move logic from extended lowdbStorage.service.ts into base-lowdb-storage.service.ts Delete lowdb-storage.service.ts Rename base-lowdb-storage.service.ts to lowdb-storage.service.ts * Merge login.command with base class program.ts - changed import temporarily to make it easier to review Remove passing in clientId, set "cli" when constructing ssoRedirectUri call Remove setting callbacks, use private methods instead Remove i18nService from constructor params Add syncService, keyConnectorService and logoutCallback to constructor Merge successCallback with handleSuccessResponse Remove validatedParams callback and added private method Move options(program.OptionValues) and set in run() Delete login.command.ts * Rename base-login.command.ts to login.command.ts * Merge base.program.ts with program.ts --- .github/whitelist-capital-letters.txt | 31 - apps/cli/jest.config.js | 5 +- apps/cli/src/bw.ts | 10 +- apps/cli/src/commands/completion.command.ts | 4 +- apps/cli/src/commands/config.command.ts | 7 +- apps/cli/src/commands/confirm.command.ts | 3 +- ...ts => convert-to-key-connector.command.ts} | 5 +- apps/cli/src/commands/create.command.ts | 10 +- apps/cli/src/commands/delete.command.ts | 2 +- apps/cli/src/commands/download.command.ts | 4 +- apps/cli/src/commands/edit.command.ts | 10 +- apps/cli/src/commands/encode.command.ts | 5 +- apps/cli/src/commands/export.command.ts | 2 +- apps/cli/src/commands/generate.command.ts | 4 +- apps/cli/src/commands/get.command.ts | 22 +- apps/cli/src/commands/import.command.ts | 4 +- apps/cli/src/commands/list.command.ts | 14 +- apps/cli/src/commands/lock.command.ts | 5 +- apps/cli/src/commands/login.command.ts | 711 ++++++++++++++++-- .../cli/src}/commands/logout.command.ts | 2 +- apps/cli/src/commands/restore.command.ts | 3 +- apps/cli/src/commands/send/create.command.ts | 6 +- apps/cli/src/commands/send/delete.command.ts | 3 +- apps/cli/src/commands/send/edit.command.ts | 4 +- apps/cli/src/commands/send/get.command.ts | 4 +- apps/cli/src/commands/send/list.command.ts | 6 +- apps/cli/src/commands/send/receive.command.ts | 4 +- ....command.ts => remove-password.command.ts} | 4 +- apps/cli/src/commands/serve.command.ts | 6 +- apps/cli/src/commands/share.command.ts | 4 +- apps/cli/src/commands/status.command.ts | 4 +- apps/cli/src/commands/sync.command.ts | 6 +- apps/cli/src/commands/unlock.command.ts | 6 +- .../cli/src}/commands/update.command.ts | 2 +- ....ts => organization-collection.request.ts} | 2 +- .../cli => apps/cli/src}/models/response.ts | 2 +- ...mentResponse.ts => attachment.response.ts} | 0 .../cli/src/models/response/base.response.ts | 0 .../{cipherResponse.ts => cipher.response.ts} | 8 +- ...tionResponse.ts => collection.response.ts} | 3 +- .../cli/src/models/response/file.response.ts | 2 +- .../{folderResponse.ts => folder.response.ts} | 3 +- .../cli/src/models/response/list.response.ts | 2 +- .../{loginResponse.ts => login.response.ts} | 0 .../src/models/response/message.response.ts | 2 +- ...ts => organization-collection.response.ts} | 4 +- ...ponse.ts => organization-user.response.ts} | 3 +- ...onResponse.ts => organization.response.ts} | 3 +- ...sponse.ts => password-history.response.ts} | 0 ...essResponse.ts => send-access.response.ts} | 6 +- ...dFileResponse.ts => send-file.response.ts} | 0 ...dTextResponse.ts => send-text.response.ts} | 0 .../{sendResponse.ts => send.response.ts} | 6 +- .../src/models/response/string.response.ts | 2 +- ...mplateResponse.ts => template.response.ts} | 2 +- ...tionReadOnly.ts => selection-read-only.ts} | 0 apps/cli/src/program.ts | 120 ++- apps/cli/src/send.program.ts | 10 +- .../services/cli-platform-utils.service.ts | 0 .../src/services/console-log.service.spec.ts | 4 +- .../cli/src/services/console-log.service.ts | 0 .../cli/src/services/lowdb-storage.service.ts | 26 +- apps/cli/src/services/lowdbStorage.service.ts | 40 - .../cli/src/services/node-api.service.ts | 0 ....ts => node-env-secure-storage.service.ts} | 0 apps/cli/src/utils.ts | 5 +- apps/cli/src/vault.program.ts | 3 +- apps/cli/tsconfig.json | 3 +- libs/node/src/cli/baseProgram.ts | 113 --- libs/node/src/cli/commands/login.command.ts | 632 ---------------- 70 files changed, 896 insertions(+), 1032 deletions(-) rename apps/cli/src/commands/{convertToKeyConnector.command.ts => convert-to-key-connector.command.ts} (95%) rename {libs/node/src/cli => apps/cli/src}/commands/logout.command.ts (89%) rename apps/cli/src/commands/send/{removePassword.command.ts => remove-password.command.ts} (79%) rename {libs/node/src/cli => apps/cli/src}/commands/update.command.ts (97%) rename apps/cli/src/models/request/{organizationCollectionRequest.ts => organization-collection.request.ts} (89%) rename {libs/node/src/cli => apps/cli/src}/models/response.ts (95%) rename apps/cli/src/models/response/{attachmentResponse.ts => attachment.response.ts} (100%) rename libs/node/src/cli/models/response/baseResponse.ts => apps/cli/src/models/response/base.response.ts (100%) rename apps/cli/src/models/response/{cipherResponse.ts => cipher.response.ts} (80%) rename apps/cli/src/models/response/{collectionResponse.ts => collection.response.ts} (82%) rename libs/node/src/cli/models/response/fileResponse.ts => apps/cli/src/models/response/file.response.ts (83%) rename apps/cli/src/models/response/{folderResponse.ts => folder.response.ts} (81%) rename libs/node/src/cli/models/response/listResponse.ts => apps/cli/src/models/response/list.response.ts (79%) rename apps/cli/src/models/response/{loginResponse.ts => login.response.ts} (100%) rename libs/node/src/cli/models/response/messageResponse.ts => apps/cli/src/models/response/message.response.ts (85%) rename apps/cli/src/models/response/{organizationCollectionResponse.ts => organization-collection.response.ts} (73%) rename apps/cli/src/models/response/{organizationUserResponse.ts => organization-user.response.ts} (85%) rename apps/cli/src/models/response/{organizationResponse.ts => organization.response.ts} (89%) rename apps/cli/src/models/response/{passwordHistoryResponse.ts => password-history.response.ts} (100%) rename apps/cli/src/models/response/{sendAccessResponse.ts => send-access.response.ts} (82%) rename apps/cli/src/models/response/{sendFileResponse.ts => send-file.response.ts} (100%) rename apps/cli/src/models/response/{sendTextResponse.ts => send-text.response.ts} (100%) rename apps/cli/src/models/response/{sendResponse.ts => send.response.ts} (95%) rename libs/node/src/cli/models/response/stringResponse.ts => apps/cli/src/models/response/string.response.ts (78%) rename apps/cli/src/models/response/{templateResponse.ts => template.response.ts} (70%) rename apps/cli/src/models/{selectionReadOnly.ts => selection-read-only.ts} (100%) rename libs/node/src/cli/services/cliPlatformUtils.service.ts => apps/cli/src/services/cli-platform-utils.service.ts (100%) rename libs/node/spec/cli/consoleLog.service.spec.ts => apps/cli/src/services/console-log.service.spec.ts (84%) rename libs/node/src/cli/services/consoleLog.service.ts => apps/cli/src/services/console-log.service.ts (100%) rename libs/node/src/services/lowdbStorage.service.ts => apps/cli/src/services/lowdb-storage.service.ts (87%) delete mode 100644 apps/cli/src/services/lowdbStorage.service.ts rename libs/node/src/services/nodeApi.service.ts => apps/cli/src/services/node-api.service.ts (100%) rename apps/cli/src/services/{nodeEnvSecureStorage.service.ts => node-env-secure-storage.service.ts} (100%) delete mode 100644 libs/node/src/cli/baseProgram.ts delete mode 100644 libs/node/src/cli/commands/login.command.ts diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index e1c74a70943..5dd8cd621ba 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -162,17 +162,6 @@ ./libs/common/src/services/bitwardenFileUpload.service.ts ./libs/common/src/services/webCryptoFunction.service.ts ./libs/common/src/interfaces/IEncrypted.ts -./libs/node/spec/cli/consoleLog.service.spec.ts -./libs/node/src/cli/models/response/baseResponse.ts -./libs/node/src/cli/models/response/stringResponse.ts -./libs/node/src/cli/models/response/fileResponse.ts -./libs/node/src/cli/models/response/messageResponse.ts -./libs/node/src/cli/models/response/listResponse.ts -./libs/node/src/cli/baseProgram.ts -./libs/node/src/cli/services/consoleLog.service.ts -./libs/node/src/cli/services/cliPlatformUtils.service.ts -./libs/node/src/services/nodeApi.service.ts -./libs/node/src/services/lowdbStorage.service.ts ./libs/electron/spec/services/electronLog.service.spec.ts ./libs/electron/src/baseMenu.ts ./libs/electron/src/services/electronLog.service.ts @@ -235,26 +224,6 @@ ./apps/desktop/src/services/nativeMessageHandler.service.ts ./apps/cli/stores/chocolatey/tools/VERIFICATION.txt ./apps/cli/README.md -./apps/cli/src/models/response/sendFileResponse.ts -./apps/cli/src/models/response/organizationCollectionResponse.ts -./apps/cli/src/models/response/collectionResponse.ts -./apps/cli/src/models/response/templateResponse.ts -./apps/cli/src/models/response/passwordHistoryResponse.ts -./apps/cli/src/models/response/folderResponse.ts -./apps/cli/src/models/response/loginResponse.ts -./apps/cli/src/models/response/sendAccessResponse.ts -./apps/cli/src/models/response/sendResponse.ts -./apps/cli/src/models/response/cipherResponse.ts -./apps/cli/src/models/response/organizationResponse.ts -./apps/cli/src/models/response/organizationUserResponse.ts -./apps/cli/src/models/response/sendTextResponse.ts -./apps/cli/src/models/response/attachmentResponse.ts -./apps/cli/src/models/selectionReadOnly.ts -./apps/cli/src/models/request/organizationCollectionRequest.ts -./apps/cli/src/commands/convertToKeyConnector.command.ts -./apps/cli/src/commands/send/removePassword.command.ts -./apps/cli/src/services/lowdbStorage.service.ts -./apps/cli/src/services/nodeEnvSecureStorage.service.ts ./apps/browser/README.md ./apps/browser/store/windows/AppxManifest.xml ./apps/browser/src/background/nativeMessaging.background.ts diff --git a/apps/cli/jest.config.js b/apps/cli/jest.config.js index 4f0dab845ec..67116d394b3 100644 --- a/apps/cli/jest.config.js +++ b/apps/cli/jest.config.js @@ -1,11 +1,12 @@ const { pathsToModuleNameMapper } = require("ts-jest"); - -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("./tsconfig.json"); const sharedConfig = require("../../libs/shared/jest.config.base"); module.exports = { + ...sharedConfig, preset: "ts-jest", + testEnvironment: "node", setupFilesAfterEnv: ["/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "/", diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 7efee8eb3e5..8f877e7fbf0 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -49,16 +49,16 @@ import { UserVerificationApiService } from "@bitwarden/common/services/userVerif import { UserVerificationService } from "@bitwarden/common/services/userVerification/userVerification.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service"; -import { CliPlatformUtilsService } from "@bitwarden/node/cli/services/cliPlatformUtils.service"; -import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.service"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; -import { NodeApiService } from "@bitwarden/node/services/nodeApi.service"; import { Program } from "./program"; import { SendProgram } from "./send.program"; +import { CliPlatformUtilsService } from "./services/cli-platform-utils.service"; +import { ConsoleLogService } from "./services/console-log.service"; import { I18nService } from "./services/i18n.service"; -import { LowdbStorageService } from "./services/lowdbStorage.service"; -import { NodeEnvSecureStorageService } from "./services/nodeEnvSecureStorage.service"; +import { LowdbStorageService } from "./services/lowdb-storage.service"; +import { NodeApiService } from "./services/node-api.service"; +import { NodeEnvSecureStorageService } from "./services/node-env-secure-storage.service"; import { VaultProgram } from "./vault.program"; // Polyfills diff --git a/apps/cli/src/commands/completion.command.ts b/apps/cli/src/commands/completion.command.ts index c175f9b6a13..17a3eff5a83 100644 --- a/apps/cli/src/commands/completion.command.ts +++ b/apps/cli/src/commands/completion.command.ts @@ -1,7 +1,7 @@ import * as program from "commander"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; interface IOption { long?: string; diff --git a/apps/cli/src/commands/config.command.ts b/apps/cli/src/commands/config.command.ts index 2fa33c9edc5..9110b46bf29 100644 --- a/apps/cli/src/commands/config.command.ts +++ b/apps/cli/src/commands/config.command.ts @@ -1,9 +1,10 @@ import * as program from "commander"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; +import { StringResponse } from "../models/response/string.response"; export class ConfigCommand { constructor(private environmentService: EnvironmentService) {} diff --git a/apps/cli/src/commands/confirm.command.ts b/apps/cli/src/commands/confirm.command.ts index 79d30dfec65..824bd5c8e85 100644 --- a/apps/cli/src/commands/confirm.command.ts +++ b/apps/cli/src/commands/confirm.command.ts @@ -2,7 +2,8 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { Utils } from "@bitwarden/common/misc/utils"; import { OrganizationUserConfirmRequest } from "@bitwarden/common/models/request/organization-user-confirm.request"; -import { Response } from "@bitwarden/node/cli/models/response"; + +import { Response } from "../models/response"; export class ConfirmCommand { constructor(private apiService: ApiService, private cryptoService: CryptoService) {} diff --git a/apps/cli/src/commands/convertToKeyConnector.command.ts b/apps/cli/src/commands/convert-to-key-connector.command.ts similarity index 95% rename from apps/cli/src/commands/convertToKeyConnector.command.ts rename to apps/cli/src/commands/convert-to-key-connector.command.ts index 00fb2f72910..82756edc985 100644 --- a/apps/cli/src/commands/convertToKeyConnector.command.ts +++ b/apps/cli/src/commands/convert-to-key-connector.command.ts @@ -4,8 +4,9 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; export class ConvertToKeyConnectorCommand { constructor( diff --git a/apps/cli/src/commands/create.command.ts b/apps/cli/src/commands/create.command.ts index 334573b3dfc..92855ee6f79 100644 --- a/apps/cli/src/commands/create.command.ts +++ b/apps/cli/src/commands/create.command.ts @@ -13,12 +13,12 @@ import { CollectionExport } from "@bitwarden/common/models/export/collection.exp import { FolderExport } from "@bitwarden/common/models/export/folder.export"; import { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse"; +import { OrganizationCollectionRequest } from "../models/request/organization-collection.request"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { OrganizationCollectionResponse } from "../models/response/organization-collection.response"; import { CliUtils } from "../utils"; export class CreateCommand { diff --git a/apps/cli/src/commands/delete.command.ts b/apps/cli/src/commands/delete.command.ts index 5866e2a6fd6..86bfc8cbbc2 100644 --- a/apps/cli/src/commands/delete.command.ts +++ b/apps/cli/src/commands/delete.command.ts @@ -4,8 +4,8 @@ import { FolderApiServiceAbstraction } from "@bitwarden/common/abstractions/fold import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; +import { Response } from "../models/response"; import { CliUtils } from "../utils"; export class DeleteCommand { diff --git a/apps/cli/src/commands/download.command.ts b/apps/cli/src/commands/download.command.ts index eb40cf5a2d7..8d7a6130c0d 100644 --- a/apps/cli/src/commands/download.command.ts +++ b/apps/cli/src/commands/download.command.ts @@ -3,9 +3,9 @@ import * as fet from "node-fetch"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { EncArrayBuffer } from "@bitwarden/common/models/domain/enc-array-buffer"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { FileResponse } from "@bitwarden/node/cli/models/response/fileResponse"; +import { Response } from "../models/response"; +import { FileResponse } from "../models/response/file.response"; import { CliUtils } from "../utils"; export abstract class DownloadCommand { diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index f486acbe991..d6cd120da0d 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -9,12 +9,12 @@ import { CollectionExport } from "@bitwarden/common/models/export/collection.exp import { FolderExport } from "@bitwarden/common/models/export/folder.export"; import { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse"; +import { OrganizationCollectionRequest } from "../models/request/organization-collection.request"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { OrganizationCollectionResponse } from "../models/response/organization-collection.response"; import { CliUtils } from "../utils"; export class EditCommand { diff --git a/apps/cli/src/commands/encode.command.ts b/apps/cli/src/commands/encode.command.ts index 1c957d26fa6..4598e1b230c 100644 --- a/apps/cli/src/commands/encode.command.ts +++ b/apps/cli/src/commands/encode.command.ts @@ -1,6 +1,5 @@ -import { Response } from "@bitwarden/node/cli/models/response"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; - +import { Response } from "../models/response"; +import { StringResponse } from "../models/response/string.response"; import { CliUtils } from "../utils"; export class EncodeCommand { diff --git a/apps/cli/src/commands/export.command.ts b/apps/cli/src/commands/export.command.ts index 50446d7e027..9e9594c6ca0 100644 --- a/apps/cli/src/commands/export.command.ts +++ b/apps/cli/src/commands/export.command.ts @@ -5,8 +5,8 @@ import { ExportFormat, ExportService } from "@bitwarden/common/abstractions/expo import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/enums/policyType"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; +import { Response } from "../models/response"; import { CliUtils } from "../utils"; export class ExportCommand { diff --git a/apps/cli/src/commands/generate.command.ts b/apps/cli/src/commands/generate.command.ts index 3fb8a7fd8b9..aebc084a06b 100644 --- a/apps/cli/src/commands/generate.command.ts +++ b/apps/cli/src/commands/generate.command.ts @@ -1,8 +1,8 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; +import { Response } from "../models/response"; +import { StringResponse } from "../models/response/string.response"; import { CliUtils } from "../utils"; export class GenerateCommand { diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 594dc5c183d..6cd57fe7cc1 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -26,18 +26,18 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { CipherView } from "@bitwarden/common/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/models/view/collection.view"; import { FolderView } from "@bitwarden/common/models/view/folder.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; -import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { CollectionResponse } from "../models/response/collectionResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse"; -import { OrganizationResponse } from "../models/response/organizationResponse"; -import { SendResponse } from "../models/response/sendResponse"; -import { TemplateResponse } from "../models/response/templateResponse"; -import { SelectionReadOnly } from "../models/selectionReadOnly"; +import { OrganizationCollectionRequest } from "../models/request/organization-collection.request"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { CollectionResponse } from "../models/response/collection.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { OrganizationCollectionResponse } from "../models/response/organization-collection.response"; +import { OrganizationResponse } from "../models/response/organization.response"; +import { SendResponse } from "../models/response/send.response"; +import { StringResponse } from "../models/response/string.response"; +import { TemplateResponse } from "../models/response/template.response"; +import { SelectionReadOnly } from "../models/selection-read-only"; import { CliUtils } from "../utils"; import { DownloadCommand } from "./download.command"; diff --git a/apps/cli/src/commands/import.command.ts b/apps/cli/src/commands/import.command.ts index fcb2e215bbc..97145771ef1 100644 --- a/apps/cli/src/commands/import.command.ts +++ b/apps/cli/src/commands/import.command.ts @@ -5,9 +5,9 @@ import { ImportService } from "@bitwarden/common/abstractions/import.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { ImportType } from "@bitwarden/common/enums/importOptions"; import { Importer } from "@bitwarden/common/importers/importer"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; import { CliUtils } from "../utils"; export class ImportCommand { diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index 5322026284d..cc40c5bc79f 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -13,14 +13,14 @@ import { } from "@bitwarden/common/models/response/collection.response"; import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/list.response"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { ListResponse } from "@bitwarden/node/cli/models/response/listResponse"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { CollectionResponse } from "../models/response/collectionResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationResponse } from "../models/response/organizationResponse"; -import { OrganizationUserResponse } from "../models/response/organizationUserResponse"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { CollectionResponse } from "../models/response/collection.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { ListResponse } from "../models/response/list.response"; +import { OrganizationUserResponse } from "../models/response/organization-user.response"; +import { OrganizationResponse } from "../models/response/organization.response"; import { CliUtils } from "../utils"; export class ListCommand { diff --git a/apps/cli/src/commands/lock.command.ts b/apps/cli/src/commands/lock.command.ts index ee993376fd8..f9f136a6b92 100644 --- a/apps/cli/src/commands/lock.command.ts +++ b/apps/cli/src/commands/lock.command.ts @@ -1,6 +1,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; export class LockCommand { constructor(private vaultTimeoutService: VaultTimeoutService) {} diff --git a/apps/cli/src/commands/login.command.ts b/apps/cli/src/commands/login.command.ts index d88a4d6d2f0..6284f8b94b5 100644 --- a/apps/cli/src/commands/login.command.ts +++ b/apps/cli/src/commands/login.command.ts @@ -1,11 +1,15 @@ +import * as http from "http"; + import * as program from "commander"; +import * as inquirer from "inquirer"; +import Separator from "inquirer/lib/objects/separator"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -13,87 +17,644 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; +import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; +import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; import { Utils } from "@bitwarden/common/misc/utils"; -import { LoginCommand as BaseLoginCommand } from "@bitwarden/node/cli/commands/login.command"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { AuthResult } from "@bitwarden/common/models/domain/auth-result"; +import { + UserApiLogInCredentials, + PasswordLogInCredentials, + SsoLogInCredentials, +} from "@bitwarden/common/models/domain/log-in-credentials"; +import { TokenTwoFactorRequest } from "@bitwarden/common/models/request/identity-token/token-two-factor.request"; +import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/two-factor-email.request"; +import { UpdateTempPasswordRequest } from "@bitwarden/common/models/request/update-temp-password.request"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -export class LoginCommand extends BaseLoginCommand { +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; + +export class LoginCommand { + protected canInteract: boolean; + protected clientSecret: string; + protected email: string; + + private ssoRedirectUri: string = null; private options: program.OptionValues; constructor( - authService: AuthService, - apiService: ApiService, - cryptoFunctionService: CryptoFunctionService, - i18nService: I18nService, - environmentService: EnvironmentService, - passwordGenerationService: PasswordGenerationService, - platformUtilsService: PlatformUtilsService, - stateService: StateService, - cryptoService: CryptoService, - policyService: PolicyService, - twoFactorService: TwoFactorService, - private syncService: SyncService, - private keyConnectorService: KeyConnectorService, - private logoutCallback: () => Promise - ) { - super( - authService, - apiService, - i18nService, - environmentService, - passwordGenerationService, - cryptoFunctionService, - platformUtilsService, - stateService, - cryptoService, - policyService, - twoFactorService, - "cli" - ); - this.logout = this.logoutCallback; - this.validatedParams = async () => { - const key = await cryptoFunctionService.randomBytes(64); - process.env.BW_SESSION = Utils.fromBufferToB64(key); - }; - this.success = async () => { - await this.syncService.fullSync(true); + protected authService: AuthService, + protected apiService: ApiService, + protected cryptoFunctionService: CryptoFunctionService, + protected environmentService: EnvironmentService, + protected passwordGenerationService: PasswordGenerationService, + protected platformUtilsService: PlatformUtilsService, + protected stateService: StateService, + protected cryptoService: CryptoService, + protected policyService: PolicyService, + protected twoFactorService: TwoFactorService, + protected syncService: SyncService, + protected keyConnectorService: KeyConnectorService, + protected logoutCallback: () => Promise + ) {} - const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); - - if ( - (this.options.sso != null || this.options.apikey != null) && - this.canInteract && - !usesKeyConnector - ) { - const res = new MessageResponse( - "You are logged in!", - "\n" + "To unlock your vault, use the `unlock` command. ex:\n" + "$ bw unlock" - ); - return res; - } else { - const res = new MessageResponse( - "You are logged in!", - "\n" + - "To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" + - '$ export BW_SESSION="' + - process.env.BW_SESSION + - '"\n' + - '> $env:BW_SESSION="' + - process.env.BW_SESSION + - '"\n\n' + - "You can also pass the session key to any command with the `--session` option. ex:\n" + - "$ bw list items --session " + - process.env.BW_SESSION - ); - res.raw = process.env.BW_SESSION; - return res; - } - }; - } - - run(email: string, password: string, options: program.OptionValues) { + async run(email: string, password: string, options: program.OptionValues) { this.options = options; this.email = email; - return super.run(email, password, options); + + this.canInteract = process.env.BW_NOINTERACTION !== "true"; + + let ssoCodeVerifier: string = null; + let ssoCode: string = null; + let orgIdentifier: string = null; + + let clientId: string = null; + let clientSecret: string = null; + + let selectedProvider: any = null; + + if (options.apikey != null) { + const apiIdentifiers = await this.apiIdentifiers(); + clientId = apiIdentifiers.clientId; + clientSecret = apiIdentifiers.clientSecret; + } else if (options.sso != null && this.canInteract) { + const passwordOptions: any = { + type: "password", + length: 64, + uppercase: true, + lowercase: true, + numbers: true, + special: false, + }; + const state = await this.passwordGenerationService.generatePassword(passwordOptions); + ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); + const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + try { + const ssoParams = await this.openSsoPrompt(codeChallenge, state); + ssoCode = ssoParams.ssoCode; + orgIdentifier = ssoParams.orgIdentifier; + } catch { + return Response.badRequest("Something went wrong. Try again."); + } + } else { + if ((email == null || email === "") && this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "email", + message: "Email address:", + }); + email = answer.email; + } + if (email == null || email.trim() === "") { + return Response.badRequest("Email address is required."); + } + if (email.indexOf("@") === -1) { + return Response.badRequest("Email address is invalid."); + } + this.email = email; + + if (password == null || password === "") { + if (options.passwordfile) { + password = await NodeUtils.readFirstLine(options.passwordfile); + } else if (options.passwordenv && process.env[options.passwordenv]) { + password = process.env[options.passwordenv]; + } else if (this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "password", + name: "password", + message: "Master password:", + }); + password = answer.password; + } + } + + if (password == null || password === "") { + return Response.badRequest("Master password is required."); + } + } + + let twoFactorToken: string = options.code; + let twoFactorMethod: TwoFactorProviderType = null; + try { + if (options.method != null) { + twoFactorMethod = parseInt(options.method, null); + } + } catch (e) { + return Response.error("Invalid two-step login method."); + } + + const twoFactor = + twoFactorToken == null + ? null + : new TokenTwoFactorRequest(twoFactorMethod, twoFactorToken, false); + + try { + await this.validatedParams(); + + let response: AuthResult = null; + if (clientId != null && clientSecret != null) { + if (!clientId.startsWith("user")) { + return Response.error("Invalid API Key; Organization API Key currently not supported"); + } + response = await this.authService.logIn( + new UserApiLogInCredentials(clientId, clientSecret) + ); + } else if (ssoCode != null && ssoCodeVerifier != null) { + response = await this.authService.logIn( + new SsoLogInCredentials( + ssoCode, + ssoCodeVerifier, + this.ssoRedirectUri, + orgIdentifier, + twoFactor + ) + ); + } else { + response = await this.authService.logIn( + new PasswordLogInCredentials(email, password, null, twoFactor) + ); + } + if (response.captchaSiteKey) { + const credentials = new PasswordLogInCredentials(email, password); + const handledResponse = await this.handleCaptchaRequired(twoFactor, credentials); + + // Error Response + if (handledResponse instanceof Response) { + return handledResponse; + } else { + response = handledResponse; + } + } + if (response.requiresTwoFactor) { + const twoFactorProviders = this.twoFactorService.getSupportedProviders(null); + if (twoFactorProviders.length === 0) { + return Response.badRequest("No providers available for this client."); + } + + if (twoFactorMethod != null) { + try { + selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; + } catch (e) { + return Response.error("Invalid two-step login method."); + } + } + + if (selectedProvider == null) { + if (twoFactorProviders.length === 1) { + selectedProvider = twoFactorProviders[0]; + } else if (this.canInteract) { + const twoFactorOptions: (string | Separator)[] = twoFactorProviders.map((p) => p.name); + twoFactorOptions.push(new inquirer.Separator()); + twoFactorOptions.push("Cancel"); + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "list", + name: "method", + message: "Two-step login method:", + choices: twoFactorOptions, + }); + const i = twoFactorOptions.indexOf(answer.method); + if (i === twoFactorOptions.length - 1) { + return Response.error("Login failed."); + } + selectedProvider = twoFactorProviders[i]; + } + if (selectedProvider == null) { + return Response.error("Login failed. No provider selected."); + } + } + + if ( + twoFactorToken == null && + response.twoFactorProviders.size > 1 && + selectedProvider.type === TwoFactorProviderType.Email + ) { + const emailReq = new TwoFactorEmailRequest(); + emailReq.email = this.authService.email; + emailReq.masterPasswordHash = this.authService.masterPasswordHash; + await this.apiService.postTwoFactorEmail(emailReq); + } + + if (twoFactorToken == null) { + if (this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "token", + message: "Two-step login code:", + }); + twoFactorToken = answer.token; + } + if (twoFactorToken == null || twoFactorToken === "") { + return Response.badRequest("Code is required."); + } + } + + response = await this.authService.logInTwoFactor( + new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken), + null + ); + } + + if (response.captchaSiteKey) { + const twoFactorRequest = new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken); + const handledResponse = await this.handleCaptchaRequired(twoFactorRequest); + + // Error Response + if (handledResponse instanceof Response) { + return handledResponse; + } else { + response = handledResponse; + } + } + + if (response.requiresTwoFactor) { + return Response.error("Login failed."); + } + + if (response.resetMasterPassword) { + return Response.error( + "In order to log in with SSO from the CLI, you must first log in" + + " through the web vault to set your master password." + ); + } + + // Handle Updating Temp Password if NOT using an API Key for authentication + if (response.forcePasswordReset && clientId == null && clientSecret == null) { + return await this.updateTempPassword(); + } + + return await this.handleSuccessResponse(); + } catch (e) { + return Response.error(e); + } + } + + private async validatedParams() { + const key = await this.cryptoFunctionService.randomBytes(64); + process.env.BW_SESSION = Utils.fromBufferToB64(key); + } + + private async handleSuccessResponse(): Promise { + await this.syncService.fullSync(true); + + const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); + + if ( + (this.options.sso != null || this.options.apikey != null) && + this.canInteract && + !usesKeyConnector + ) { + const res = new MessageResponse( + "You are logged in!", + "\n" + "To unlock your vault, use the `unlock` command. ex:\n" + "$ bw unlock" + ); + return Response.success(res); + } + + const res = new MessageResponse( + "You are logged in!", + "\n" + + "To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" + + '$ export BW_SESSION="' + + process.env.BW_SESSION + + '"\n' + + '> $env:BW_SESSION="' + + process.env.BW_SESSION + + '"\n\n' + + "You can also pass the session key to any command with the `--session` option. ex:\n" + + "$ bw list items --session " + + process.env.BW_SESSION + ); + res.raw = process.env.BW_SESSION; + return Response.success(res); + } + + private async updateTempPassword(error?: string): Promise { + // If no interaction available, alert user to use web vault + if (!this.canInteract) { + await this.logoutCallback(); + this.authService.logOut(() => { + /* Do nothing */ + }); + return Response.error( + new MessageResponse( + "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now via the web vault. You have been logged out.", + null + ) + ); + } + + if (this.email == null || this.email === "undefined") { + this.email = await this.stateService.getEmail(); + } + + // Get New Master Password + const baseMessage = + "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.\n" + + "Master password: "; + const firstMessage = error != null ? error + baseMessage : baseMessage; + const mp: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ + type: "password", + name: "password", + message: firstMessage, + }); + const masterPassword = mp.password; + + // Master Password Validation + if (masterPassword == null || masterPassword === "") { + return this.updateTempPassword("Master password is required.\n"); + } + + if (masterPassword.length < 8) { + return this.updateTempPassword("Master password must be at least 8 characters long.\n"); + } + + // Strength & Policy Validation + const strengthResult = this.passwordGenerationService.passwordStrength( + masterPassword, + this.getPasswordStrengthUserInput() + ); + + // Get New Master Password Re-type + const reTypeMessage = "Re-type New Master password (Strength: " + strengthResult.score + ")"; + const retype: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ + type: "password", + name: "password", + message: reTypeMessage, + }); + const masterPasswordRetype = retype.password; + + // Re-type Validation + if (masterPassword !== masterPasswordRetype) { + return this.updateTempPassword("Master password confirmation does not match.\n"); + } + + // Get Hint (optional) + const hint: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ + type: "input", + name: "input", + message: "Master Password Hint (optional):", + }); + const masterPasswordHint = hint.input; + + // Retrieve details for key generation + const enforcedPolicyOptions = await firstValueFrom( + this.policyService.masterPasswordPolicyOptions$() + ); + const kdf = await this.stateService.getKdfType(); + const kdfIterations = await this.stateService.getKdfIterations(); + + if ( + enforcedPolicyOptions != null && + !this.policyService.evaluateMasterPassword( + strengthResult.score, + masterPassword, + enforcedPolicyOptions + ) + ) { + return this.updateTempPassword( + "Your new master password does not meet the policy requirements.\n" + ); + } + + try { + // Create new key and hash new password + const newKey = await this.cryptoService.makeKey( + masterPassword, + this.email.trim().toLowerCase(), + kdf, + kdfIterations + ); + const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey); + + // Grab user's current enc key + const userEncKey = await this.cryptoService.getEncKey(); + + // Create new encKey for the User + const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey); + + // Create request + const request = new UpdateTempPasswordRequest(); + request.key = newEncKey[1].encryptedString; + request.newMasterPasswordHash = newPasswordHash; + request.masterPasswordHint = masterPasswordHint; + + // Update user's password + await this.apiService.putUpdateTempPassword(request); + return this.handleSuccessResponse(); + } catch (e) { + await this.logoutCallback(); + this.authService.logOut(() => { + /* Do nothing */ + }); + return Response.error(e); + } + } + + private async handleCaptchaRequired( + twoFactorRequest: TokenTwoFactorRequest, + credentials: PasswordLogInCredentials = null + ): Promise { + const badCaptcha = Response.badRequest( + "Your authentication request has been flagged and will require user interaction to proceed.\n" + + "Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" + + "(https://bitwarden.com/help/cli-auth-challenges)" + ); + + try { + const captchaClientSecret = await this.apiClientSecret(true); + if (Utils.isNullOrWhitespace(captchaClientSecret)) { + return badCaptcha; + } + + let authResultResponse: AuthResult = null; + if (credentials != null) { + credentials.captchaToken = captchaClientSecret; + credentials.twoFactor = twoFactorRequest; + authResultResponse = await this.authService.logIn(credentials); + } else { + authResultResponse = await this.authService.logInTwoFactor( + twoFactorRequest, + captchaClientSecret + ); + } + + return authResultResponse; + } catch (e) { + if ( + e instanceof ErrorResponse || + (e.constructor.name === ErrorResponse.name && + (e as ErrorResponse).message.includes("Captcha is invalid")) + ) { + return badCaptcha; + } else { + return Response.error(e); + } + } + } + + private getPasswordStrengthUserInput() { + let userInput: string[] = []; + const atPosition = this.email.indexOf("@"); + if (atPosition > -1) { + userInput = userInput.concat( + this.email + .substr(0, atPosition) + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/) + ); + } + return userInput; + } + + private async apiClientId(): Promise { + let clientId: string = null; + + const storedClientId: string = process.env.BW_CLIENTID; + if (storedClientId == null) { + if (this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "clientId", + message: "client_id:", + }); + clientId = answer.clientId; + } else { + clientId = null; + } + } else { + clientId = storedClientId; + } + + return clientId; + } + + private async apiClientSecret(isAdditionalAuthentication = false): Promise { + const additionalAuthenticationMessage = "Additional authentication required.\nAPI key "; + let clientSecret: string = null; + + const storedClientSecret: string = this.clientSecret || process.env.BW_CLIENTSECRET; + if (this.canInteract && storedClientSecret == null) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "clientSecret", + message: + (isAdditionalAuthentication ? additionalAuthenticationMessage : "") + "client_secret:", + }); + clientSecret = answer.clientSecret; + } else { + clientSecret = storedClientSecret; + } + + return clientSecret; + } + + private async apiIdentifiers(): Promise<{ clientId: string; clientSecret: string }> { + return { + clientId: await this.apiClientId(), + clientSecret: await this.apiClientSecret(), + }; + } + + private async openSsoPrompt( + codeChallenge: string, + state: string + ): Promise<{ ssoCode: string; orgIdentifier: string }> { + return new Promise((resolve, reject) => { + const callbackServer = http.createServer((req, res) => { + const urlString = "http://localhost" + req.url; + const url = new URL(urlString); + const code = url.searchParams.get("code"); + const receivedState = url.searchParams.get("state"); + const orgIdentifier = this.getOrgIdentifierFromState(receivedState); + res.setHeader("Content-Type", "text/html"); + if (code != null && receivedState != null && this.checkState(receivedState, state)) { + res.writeHead(200); + res.end( + "Success | Bitwarden CLI" + + "

Successfully authenticated with the Bitwarden CLI

" + + "

You may now close this tab and return to the terminal.

" + + "" + ); + callbackServer.close(() => + resolve({ + ssoCode: code, + orgIdentifier: orgIdentifier, + }) + ); + } else { + res.writeHead(400); + res.end( + "Failed | Bitwarden CLI" + + "

Something went wrong logging into the Bitwarden CLI

" + + "

You may now close this tab and return to the terminal.

" + + "" + ); + callbackServer.close(() => reject()); + } + }); + let foundPort = false; + const webUrl = this.environmentService.getWebVaultUrl(); + for (let port = 8065; port <= 8070; port++) { + try { + this.ssoRedirectUri = "http://localhost:" + port; + callbackServer.listen(port, () => { + this.platformUtilsService.launchUri( + webUrl + + "/#/sso?clientId=" + + "cli" + + "&redirectUri=" + + encodeURIComponent(this.ssoRedirectUri) + + "&state=" + + state + + "&codeChallenge=" + + codeChallenge + ); + }); + foundPort = true; + break; + } catch { + // Ignore error since we run the same command up to 5 times. + } + } + if (!foundPort) { + reject(); + } + }); + } + + private getOrgIdentifierFromState(state: string): string { + if (state === null || state === undefined) { + return null; + } + + const stateSplit = state.split("_identifier="); + return stateSplit.length > 1 ? stateSplit[1] : null; + } + + private checkState(state: string, checkState: string): boolean { + if (state === null || state === undefined) { + return false; + } + if (checkState === null || checkState === undefined) { + return false; + } + + const stateSplit = state.split("_identifier="); + const checkStateSplit = checkState.split("_identifier="); + return stateSplit[0] === checkStateSplit[0]; } } diff --git a/libs/node/src/cli/commands/logout.command.ts b/apps/cli/src/commands/logout.command.ts similarity index 89% rename from libs/node/src/cli/commands/logout.command.ts rename to apps/cli/src/commands/logout.command.ts index 9f8a544410a..3de76275761 100644 --- a/libs/node/src/cli/commands/logout.command.ts +++ b/apps/cli/src/commands/logout.command.ts @@ -2,7 +2,7 @@ import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { Response } from "../models/response"; -import { MessageResponse } from "../models/response/messageResponse"; +import { MessageResponse } from "../models/response/message.response"; export class LogoutCommand { constructor( diff --git a/apps/cli/src/commands/restore.command.ts b/apps/cli/src/commands/restore.command.ts index 75e50dabfd1..48e6b2aa9cc 100644 --- a/apps/cli/src/commands/restore.command.ts +++ b/apps/cli/src/commands/restore.command.ts @@ -1,5 +1,6 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; -import { Response } from "@bitwarden/node/cli/models/response"; + +import { Response } from "../models/response"; export class RestoreCommand { constructor(private cipherService: CipherService) {} diff --git a/apps/cli/src/commands/send/create.command.ts b/apps/cli/src/commands/send/create.command.ts index cfea2e863cf..2fab074f443 100644 --- a/apps/cli/src/commands/send/create.command.ts +++ b/apps/cli/src/commands/send/create.command.ts @@ -6,10 +6,10 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SendType } from "@bitwarden/common/enums/sendType"; import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; -import { SendTextResponse } from "../../models/response/sendTextResponse"; +import { Response } from "../../models/response"; +import { SendTextResponse } from "../../models/response/send-text.response"; +import { SendResponse } from "../../models/response/send.response"; import { CliUtils } from "../../utils"; export class SendCreateCommand { diff --git a/apps/cli/src/commands/send/delete.command.ts b/apps/cli/src/commands/send/delete.command.ts index 71778a6fc8c..6138382565c 100644 --- a/apps/cli/src/commands/send/delete.command.ts +++ b/apps/cli/src/commands/send/delete.command.ts @@ -1,5 +1,6 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; -import { Response } from "@bitwarden/node/cli/models/response"; + +import { Response } from "../../models/response"; export class SendDeleteCommand { constructor(private sendService: SendService) {} diff --git a/apps/cli/src/commands/send/edit.command.ts b/apps/cli/src/commands/send/edit.command.ts index bb4d467eafe..4fc92fb89a1 100644 --- a/apps/cli/src/commands/send/edit.command.ts +++ b/apps/cli/src/commands/send/edit.command.ts @@ -1,9 +1,9 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SendType } from "@bitwarden/common/enums/sendType"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; +import { Response } from "../../models/response"; +import { SendResponse } from "../../models/response/send.response"; import { CliUtils } from "../../utils"; import { SendGetCommand } from "./get.command"; diff --git a/apps/cli/src/commands/send/get.command.ts b/apps/cli/src/commands/send/get.command.ts index aeae1203a61..37367276a8d 100644 --- a/apps/cli/src/commands/send/get.command.ts +++ b/apps/cli/src/commands/send/get.command.ts @@ -6,9 +6,9 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SendService } from "@bitwarden/common/abstractions/send.service"; import { Utils } from "@bitwarden/common/misc/utils"; import { SendView } from "@bitwarden/common/models/view/send.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; +import { Response } from "../../models/response"; +import { SendResponse } from "../../models/response/send.response"; import { DownloadCommand } from "../download.command"; export class SendGetCommand extends DownloadCommand { diff --git a/apps/cli/src/commands/send/list.command.ts b/apps/cli/src/commands/send/list.command.ts index 149c34d69d1..c37040b2550 100644 --- a/apps/cli/src/commands/send/list.command.ts +++ b/apps/cli/src/commands/send/list.command.ts @@ -1,10 +1,10 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SendService } from "@bitwarden/common/abstractions/send.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { ListResponse } from "@bitwarden/node/cli/models/response/listResponse"; -import { SendResponse } from "../..//models/response/sendResponse"; +import { Response } from "../../models/response"; +import { ListResponse } from "../../models/response/list.response"; +import { SendResponse } from "../../models/response/send.response"; export class SendListCommand { constructor( diff --git a/apps/cli/src/commands/send/receive.command.ts b/apps/cli/src/commands/send/receive.command.ts index 9a9803e57f5..824f7d1a930 100644 --- a/apps/cli/src/commands/send/receive.command.ts +++ b/apps/cli/src/commands/send/receive.command.ts @@ -14,9 +14,9 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-cr import { SendAccessRequest } from "@bitwarden/common/models/request/send-access.request"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { SendAccessView } from "@bitwarden/common/models/view/send-access.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendAccessResponse } from "../../models/response/sendAccessResponse"; +import { Response } from "../../models/response"; +import { SendAccessResponse } from "../../models/response/send-access.response"; import { DownloadCommand } from "../download.command"; export class SendReceiveCommand extends DownloadCommand { diff --git a/apps/cli/src/commands/send/removePassword.command.ts b/apps/cli/src/commands/send/remove-password.command.ts similarity index 79% rename from apps/cli/src/commands/send/removePassword.command.ts rename to apps/cli/src/commands/send/remove-password.command.ts index 53e82b7d722..53805fa003c 100644 --- a/apps/cli/src/commands/send/removePassword.command.ts +++ b/apps/cli/src/commands/send/remove-password.command.ts @@ -1,7 +1,7 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; +import { Response } from "../../models/response"; +import { SendResponse } from "../../models/response/send.response"; export class SendRemovePasswordCommand { constructor(private sendService: SendService) {} diff --git a/apps/cli/src/commands/serve.command.ts b/apps/cli/src/commands/serve.command.ts index 9b2ea77784c..ddba5acbb7a 100644 --- a/apps/cli/src/commands/serve.command.ts +++ b/apps/cli/src/commands/serve.command.ts @@ -7,10 +7,10 @@ import * as koaJson from "koa-json"; import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { FileResponse } from "@bitwarden/node/cli/models/response/fileResponse"; import { Main } from "../bw"; +import { Response } from "../models/response"; +import { FileResponse } from "../models/response/file.response"; import { ConfirmCommand } from "./confirm.command"; import { CreateCommand } from "./create.command"; @@ -26,7 +26,7 @@ import { SendDeleteCommand } from "./send/delete.command"; import { SendEditCommand } from "./send/edit.command"; import { SendGetCommand } from "./send/get.command"; import { SendListCommand } from "./send/list.command"; -import { SendRemovePasswordCommand } from "./send/removePassword.command"; +import { SendRemovePasswordCommand } from "./send/remove-password.command"; import { ShareCommand } from "./share.command"; import { StatusCommand } from "./status.command"; import { SyncCommand } from "./sync.command"; diff --git a/apps/cli/src/commands/share.command.ts b/apps/cli/src/commands/share.command.ts index 8766baea8f7..c04760b9d93 100644 --- a/apps/cli/src/commands/share.command.ts +++ b/apps/cli/src/commands/share.command.ts @@ -1,7 +1,7 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { CipherResponse } from "../models/response/cipherResponse"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; import { CliUtils } from "../utils"; export class ShareCommand { diff --git a/apps/cli/src/commands/status.command.ts b/apps/cli/src/commands/status.command.ts index 1b8d5fa3f56..7cd109309c1 100644 --- a/apps/cli/src/commands/status.command.ts +++ b/apps/cli/src/commands/status.command.ts @@ -3,9 +3,9 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { TemplateResponse } from "../models/response/templateResponse"; +import { Response } from "../models/response"; +import { TemplateResponse } from "../models/response/template.response"; export class StatusCommand { constructor( diff --git a/apps/cli/src/commands/sync.command.ts b/apps/cli/src/commands/sync.command.ts index 6ba4166826f..3d72b6b89ea 100644 --- a/apps/cli/src/commands/sync.command.ts +++ b/apps/cli/src/commands/sync.command.ts @@ -1,8 +1,8 @@ import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; +import { StringResponse } from "../models/response/string.response"; import { CliUtils } from "../utils"; export class SyncCommand { diff --git a/apps/cli/src/commands/unlock.command.ts b/apps/cli/src/commands/unlock.command.ts index 83de7a0b1ce..7e2136e6e3e 100644 --- a/apps/cli/src/commands/unlock.command.ts +++ b/apps/cli/src/commands/unlock.command.ts @@ -10,12 +10,12 @@ import { HashPurpose } from "@bitwarden/common/enums/hashPurpose"; import { Utils } from "@bitwarden/common/misc/utils"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secret-verification.request"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; import { CliUtils } from "../utils"; -import { ConvertToKeyConnectorCommand } from "./convertToKeyConnector.command"; +import { ConvertToKeyConnectorCommand } from "./convert-to-key-connector.command"; export class UnlockCommand { constructor( diff --git a/libs/node/src/cli/commands/update.command.ts b/apps/cli/src/commands/update.command.ts similarity index 97% rename from libs/node/src/cli/commands/update.command.ts rename to apps/cli/src/commands/update.command.ts index 534e11de47d..b058feb72b8 100644 --- a/libs/node/src/cli/commands/update.command.ts +++ b/apps/cli/src/commands/update.command.ts @@ -4,7 +4,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { Response } from "../models/response"; -import { MessageResponse } from "../models/response/messageResponse"; +import { MessageResponse } from "../models/response/message.response"; export class UpdateCommand { inPkg = false; diff --git a/apps/cli/src/models/request/organizationCollectionRequest.ts b/apps/cli/src/models/request/organization-collection.request.ts similarity index 89% rename from apps/cli/src/models/request/organizationCollectionRequest.ts rename to apps/cli/src/models/request/organization-collection.request.ts index 7b9f184d855..7546d116092 100644 --- a/apps/cli/src/models/request/organizationCollectionRequest.ts +++ b/apps/cli/src/models/request/organization-collection.request.ts @@ -1,6 +1,6 @@ import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; -import { SelectionReadOnly } from "../selectionReadOnly"; +import { SelectionReadOnly } from "../selection-read-only"; export class OrganizationCollectionRequest extends CollectionExport { static template(): OrganizationCollectionRequest { diff --git a/libs/node/src/cli/models/response.ts b/apps/cli/src/models/response.ts similarity index 95% rename from libs/node/src/cli/models/response.ts rename to apps/cli/src/models/response.ts index d768c51fdeb..ad70c4c8df1 100644 --- a/libs/node/src/cli/models/response.ts +++ b/apps/cli/src/models/response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./response/baseResponse"; +import { BaseResponse } from "./response/base.response"; export class Response { static error(error: any, data?: any): Response { diff --git a/apps/cli/src/models/response/attachmentResponse.ts b/apps/cli/src/models/response/attachment.response.ts similarity index 100% rename from apps/cli/src/models/response/attachmentResponse.ts rename to apps/cli/src/models/response/attachment.response.ts diff --git a/libs/node/src/cli/models/response/baseResponse.ts b/apps/cli/src/models/response/base.response.ts similarity index 100% rename from libs/node/src/cli/models/response/baseResponse.ts rename to apps/cli/src/models/response/base.response.ts diff --git a/apps/cli/src/models/response/cipherResponse.ts b/apps/cli/src/models/response/cipher.response.ts similarity index 80% rename from apps/cli/src/models/response/cipherResponse.ts rename to apps/cli/src/models/response/cipher.response.ts index f1e83ac30e2..182bff6e7f2 100644 --- a/apps/cli/src/models/response/cipherResponse.ts +++ b/apps/cli/src/models/response/cipher.response.ts @@ -1,11 +1,11 @@ import { CipherType } from "@bitwarden/common/enums/cipherType"; import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; -import { AttachmentResponse } from "./attachmentResponse"; -import { LoginResponse } from "./loginResponse"; -import { PasswordHistoryResponse } from "./passwordHistoryResponse"; +import { AttachmentResponse } from "./attachment.response"; +import { BaseResponse } from "./base.response"; +import { LoginResponse } from "./login.response"; +import { PasswordHistoryResponse } from "./password-history.response"; export class CipherResponse extends CipherWithIdExport implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/collectionResponse.ts b/apps/cli/src/models/response/collection.response.ts similarity index 82% rename from apps/cli/src/models/response/collectionResponse.ts rename to apps/cli/src/models/response/collection.response.ts index 1818c946e35..4275415233f 100644 --- a/apps/cli/src/models/response/collectionResponse.ts +++ b/apps/cli/src/models/response/collection.response.ts @@ -1,6 +1,7 @@ import { CollectionWithIdExport } from "@bitwarden/common/models/export/collection-with-id.export"; import { CollectionView } from "@bitwarden/common/models/view/collection.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class CollectionResponse extends CollectionWithIdExport implements BaseResponse { object: string; diff --git a/libs/node/src/cli/models/response/fileResponse.ts b/apps/cli/src/models/response/file.response.ts similarity index 83% rename from libs/node/src/cli/models/response/fileResponse.ts rename to apps/cli/src/models/response/file.response.ts index c16b56e0912..487cb270e2d 100644 --- a/libs/node/src/cli/models/response/fileResponse.ts +++ b/apps/cli/src/models/response/file.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class FileResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/folderResponse.ts b/apps/cli/src/models/response/folder.response.ts similarity index 81% rename from apps/cli/src/models/response/folderResponse.ts rename to apps/cli/src/models/response/folder.response.ts index 196071fbab7..5a73d7feb28 100644 --- a/apps/cli/src/models/response/folderResponse.ts +++ b/apps/cli/src/models/response/folder.response.ts @@ -1,6 +1,7 @@ import { FolderWithIdExport } from "@bitwarden/common/models/export/folder-with-id.export"; import { FolderView } from "@bitwarden/common/models/view/folder.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class FolderResponse extends FolderWithIdExport implements BaseResponse { object: string; diff --git a/libs/node/src/cli/models/response/listResponse.ts b/apps/cli/src/models/response/list.response.ts similarity index 79% rename from libs/node/src/cli/models/response/listResponse.ts rename to apps/cli/src/models/response/list.response.ts index db415bcdb30..da6e7f0999d 100644 --- a/libs/node/src/cli/models/response/listResponse.ts +++ b/apps/cli/src/models/response/list.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class ListResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/loginResponse.ts b/apps/cli/src/models/response/login.response.ts similarity index 100% rename from apps/cli/src/models/response/loginResponse.ts rename to apps/cli/src/models/response/login.response.ts diff --git a/libs/node/src/cli/models/response/messageResponse.ts b/apps/cli/src/models/response/message.response.ts similarity index 85% rename from libs/node/src/cli/models/response/messageResponse.ts rename to apps/cli/src/models/response/message.response.ts index 8612841ac1e..1440f97b646 100644 --- a/libs/node/src/cli/models/response/messageResponse.ts +++ b/apps/cli/src/models/response/message.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class MessageResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/organizationCollectionResponse.ts b/apps/cli/src/models/response/organization-collection.response.ts similarity index 73% rename from apps/cli/src/models/response/organizationCollectionResponse.ts rename to apps/cli/src/models/response/organization-collection.response.ts index 811e4b2a57d..98b5b09c587 100644 --- a/apps/cli/src/models/response/organizationCollectionResponse.ts +++ b/apps/cli/src/models/response/organization-collection.response.ts @@ -1,8 +1,8 @@ import { CollectionView } from "@bitwarden/common/models/view/collection.view"; -import { SelectionReadOnly } from "../selectionReadOnly"; +import { SelectionReadOnly } from "../selection-read-only"; -import { CollectionResponse } from "./collectionResponse"; +import { CollectionResponse } from "./collection.response"; export class OrganizationCollectionResponse extends CollectionResponse { groups: SelectionReadOnly[]; diff --git a/apps/cli/src/models/response/organizationUserResponse.ts b/apps/cli/src/models/response/organization-user.response.ts similarity index 85% rename from apps/cli/src/models/response/organizationUserResponse.ts rename to apps/cli/src/models/response/organization-user.response.ts index 962fdb23bcd..92a8e63821a 100644 --- a/apps/cli/src/models/response/organizationUserResponse.ts +++ b/apps/cli/src/models/response/organization-user.response.ts @@ -1,6 +1,7 @@ import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class OrganizationUserResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/organizationResponse.ts b/apps/cli/src/models/response/organization.response.ts similarity index 89% rename from apps/cli/src/models/response/organizationResponse.ts rename to apps/cli/src/models/response/organization.response.ts index 989623e35f0..1b100687f26 100644 --- a/apps/cli/src/models/response/organizationResponse.ts +++ b/apps/cli/src/models/response/organization.response.ts @@ -1,7 +1,8 @@ import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; import { Organization } from "@bitwarden/common/models/domain/organization"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class OrganizationResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/passwordHistoryResponse.ts b/apps/cli/src/models/response/password-history.response.ts similarity index 100% rename from apps/cli/src/models/response/passwordHistoryResponse.ts rename to apps/cli/src/models/response/password-history.response.ts diff --git a/apps/cli/src/models/response/sendAccessResponse.ts b/apps/cli/src/models/response/send-access.response.ts similarity index 82% rename from apps/cli/src/models/response/sendAccessResponse.ts rename to apps/cli/src/models/response/send-access.response.ts index a37fd479b7e..18644d67492 100644 --- a/apps/cli/src/models/response/sendAccessResponse.ts +++ b/apps/cli/src/models/response/send-access.response.ts @@ -1,9 +1,9 @@ import { SendType } from "@bitwarden/common/enums/sendType"; import { SendAccessView } from "@bitwarden/common/models/view/send-access.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; -import { SendFileResponse } from "./sendFileResponse"; -import { SendTextResponse } from "./sendTextResponse"; +import { BaseResponse } from "./base.response"; +import { SendFileResponse } from "./send-file.response"; +import { SendTextResponse } from "./send-text.response"; export class SendAccessResponse implements BaseResponse { static template(): SendAccessResponse { diff --git a/apps/cli/src/models/response/sendFileResponse.ts b/apps/cli/src/models/response/send-file.response.ts similarity index 100% rename from apps/cli/src/models/response/sendFileResponse.ts rename to apps/cli/src/models/response/send-file.response.ts diff --git a/apps/cli/src/models/response/sendTextResponse.ts b/apps/cli/src/models/response/send-text.response.ts similarity index 100% rename from apps/cli/src/models/response/sendTextResponse.ts rename to apps/cli/src/models/response/send-text.response.ts diff --git a/apps/cli/src/models/response/sendResponse.ts b/apps/cli/src/models/response/send.response.ts similarity index 95% rename from apps/cli/src/models/response/sendResponse.ts rename to apps/cli/src/models/response/send.response.ts index c2e47fa740e..6d9f555312e 100644 --- a/apps/cli/src/models/response/sendResponse.ts +++ b/apps/cli/src/models/response/send.response.ts @@ -1,10 +1,10 @@ import { SendType } from "@bitwarden/common/enums/sendType"; import { Utils } from "@bitwarden/common/misc/utils"; import { SendView } from "@bitwarden/common/models/view/send.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; -import { SendFileResponse } from "./sendFileResponse"; -import { SendTextResponse } from "./sendTextResponse"; +import { BaseResponse } from "./base.response"; +import { SendFileResponse } from "./send-file.response"; +import { SendTextResponse } from "./send-text.response"; const dateProperties: string[] = [ Utils.nameOf("deletionDate"), diff --git a/libs/node/src/cli/models/response/stringResponse.ts b/apps/cli/src/models/response/string.response.ts similarity index 78% rename from libs/node/src/cli/models/response/stringResponse.ts rename to apps/cli/src/models/response/string.response.ts index f4becfe84ca..49bb299b669 100644 --- a/libs/node/src/cli/models/response/stringResponse.ts +++ b/apps/cli/src/models/response/string.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class StringResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/templateResponse.ts b/apps/cli/src/models/response/template.response.ts similarity index 70% rename from apps/cli/src/models/response/templateResponse.ts rename to apps/cli/src/models/response/template.response.ts index 15944b0780e..86dc0b15886 100644 --- a/apps/cli/src/models/response/templateResponse.ts +++ b/apps/cli/src/models/response/template.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; +import { BaseResponse } from "./base.response"; export class TemplateResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/selectionReadOnly.ts b/apps/cli/src/models/selection-read-only.ts similarity index 100% rename from apps/cli/src/models/selectionReadOnly.ts rename to apps/cli/src/models/selection-read-only.ts diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index f80a87068b1..41a99dcd9bc 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -3,11 +3,6 @@ import * as program from "commander"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions"; -import { BaseProgram } from "@bitwarden/node/cli/baseProgram"; -import { LogoutCommand } from "@bitwarden/node/cli/commands/logout.command"; -import { UpdateCommand } from "@bitwarden/node/cli/commands/update.command"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; import { Main } from "./bw"; import { CompletionCommand } from "./commands/completion.command"; @@ -16,19 +11,23 @@ import { EncodeCommand } from "./commands/encode.command"; import { GenerateCommand } from "./commands/generate.command"; import { LockCommand } from "./commands/lock.command"; import { LoginCommand } from "./commands/login.command"; +import { LogoutCommand } from "./commands/logout.command"; import { ServeCommand } from "./commands/serve.command"; import { StatusCommand } from "./commands/status.command"; import { SyncCommand } from "./commands/sync.command"; import { UnlockCommand } from "./commands/unlock.command"; -import { TemplateResponse } from "./models/response/templateResponse"; +import { UpdateCommand } from "./commands/update.command"; +import { Response } from "./models/response"; +import { ListResponse } from "./models/response/list.response"; +import { MessageResponse } from "./models/response/message.response"; +import { StringResponse } from "./models/response/string.response"; +import { TemplateResponse } from "./models/response/template.response"; import { CliUtils } from "./utils"; const writeLn = CliUtils.writeLn; -export class Program extends BaseProgram { - constructor(protected main: Main) { - super(main.stateService, writeLn); - } +export class Program { + constructor(protected main: Main) {} async register() { program @@ -144,7 +143,6 @@ export class Program extends BaseProgram { this.main.authService, this.main.apiService, this.main.cryptoFunctionService, - this.main.i18nService, this.main.environmentService, this.main.passwordGenerationService, this.main.platformUtilsService, @@ -502,12 +500,102 @@ export class Program extends BaseProgram { } protected processResponse(response: Response, exitImmediately = false) { - super.processResponse(response, exitImmediately, () => { - if (response.data.object === "template") { - return this.getJson((response.data as TemplateResponse).template); + if (!response.success) { + if (process.env.BW_QUIET !== "true") { + if (process.env.BW_RESPONSE === "true") { + writeLn(this.getJson(response), true, false); + } else { + writeLn(chalk.redBright(response.message), true, true); + } } - return null; - }); + const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; + if (exitImmediately) { + process.exit(exitCode); + } else { + process.exitCode = exitCode; + } + return; + } + + if (process.env.BW_RESPONSE === "true") { + writeLn(this.getJson(response), true, false); + } else if (response.data != null) { + let out: string = null; + + if (response.data.object === "template") { + out = this.getJson((response.data as TemplateResponse).template); + } + + if (out == null) { + if (response.data.object === "string") { + const data = (response.data as StringResponse).data; + if (data != null) { + out = data; + } + } else if (response.data.object === "list") { + out = this.getJson((response.data as ListResponse).data); + } else if (response.data.object === "message") { + out = this.getMessage(response); + } else { + out = this.getJson(response.data); + } + } + + if (out != null && process.env.BW_QUIET !== "true") { + writeLn(out, true, false); + } + } + if (exitImmediately) { + process.exit(0); + } else { + process.exitCode = 0; + } + } + + private getJson(obj: any): string { + if (process.env.BW_PRETTY === "true") { + return JSON.stringify(obj, null, " "); + } else { + return JSON.stringify(obj); + } + } + + private getMessage(response: Response): string { + const message = response.data as MessageResponse; + if (process.env.BW_RAW === "true") { + return message.raw; + } + + let out = ""; + if (message.title != null) { + if (message.noColor) { + out = message.title; + } else { + out = chalk.greenBright(message.title); + } + } + if (message.message != null) { + if (message.title != null) { + out += "\n"; + } + out += message.message; + } + return out.trim() === "" ? null : out; + } + + private async exitIfAuthed() { + const authed = await this.main.stateService.getIsAuthenticated(); + if (authed) { + const email = await this.main.stateService.getEmail(); + this.processResponse(Response.error("You are already logged in as " + email + "."), true); + } + } + + private async exitIfNotAuthed() { + const authed = await this.main.stateService.getIsAuthenticated(); + if (!authed) { + this.processResponse(Response.error("You are not logged in."), true); + } } protected async exitIfLocked() { diff --git a/apps/cli/src/send.program.ts b/apps/cli/src/send.program.ts index f92e2a305e1..78c607f0419 100644 --- a/apps/cli/src/send.program.ts +++ b/apps/cli/src/send.program.ts @@ -6,7 +6,6 @@ import * as program from "commander"; import { SendType } from "@bitwarden/common/enums/sendType"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; import { Main } from "./bw"; import { GetCommand } from "./commands/get.command"; @@ -16,10 +15,11 @@ import { SendEditCommand } from "./commands/send/edit.command"; import { SendGetCommand } from "./commands/send/get.command"; import { SendListCommand } from "./commands/send/list.command"; import { SendReceiveCommand } from "./commands/send/receive.command"; -import { SendRemovePasswordCommand } from "./commands/send/removePassword.command"; -import { SendFileResponse } from "./models/response/sendFileResponse"; -import { SendResponse } from "./models/response/sendResponse"; -import { SendTextResponse } from "./models/response/sendTextResponse"; +import { SendRemovePasswordCommand } from "./commands/send/remove-password.command"; +import { Response } from "./models/response"; +import { SendFileResponse } from "./models/response/send-file.response"; +import { SendTextResponse } from "./models/response/send-text.response"; +import { SendResponse } from "./models/response/send.response"; import { Program } from "./program"; import { CliUtils } from "./utils"; diff --git a/libs/node/src/cli/services/cliPlatformUtils.service.ts b/apps/cli/src/services/cli-platform-utils.service.ts similarity index 100% rename from libs/node/src/cli/services/cliPlatformUtils.service.ts rename to apps/cli/src/services/cli-platform-utils.service.ts diff --git a/libs/node/spec/cli/consoleLog.service.spec.ts b/apps/cli/src/services/console-log.service.spec.ts similarity index 84% rename from libs/node/spec/cli/consoleLog.service.spec.ts rename to apps/cli/src/services/console-log.service.spec.ts index 656a74ca55d..d629b7c1c06 100644 --- a/libs/node/spec/cli/consoleLog.service.spec.ts +++ b/apps/cli/src/services/console-log.service.spec.ts @@ -1,6 +1,6 @@ -import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.service"; +import { interceptConsole, restoreConsole } from "@bitwarden/common/spec/shared/interceptConsole"; -import { interceptConsole, restoreConsole } from "../../../common/spec/shared/interceptConsole"; +import { ConsoleLogService } from "./console-log.service"; let caughtMessage: any = {}; diff --git a/libs/node/src/cli/services/consoleLog.service.ts b/apps/cli/src/services/console-log.service.ts similarity index 100% rename from libs/node/src/cli/services/consoleLog.service.ts rename to apps/cli/src/services/console-log.service.ts diff --git a/libs/node/src/services/lowdbStorage.service.ts b/apps/cli/src/services/lowdb-storage.service.ts similarity index 87% rename from libs/node/src/services/lowdbStorage.service.ts rename to apps/cli/src/services/lowdb-storage.service.ts index 102168f909b..6e096bd3c37 100644 --- a/libs/node/src/services/lowdbStorage.service.ts +++ b/apps/cli/src/services/lowdb-storage.service.ts @@ -3,6 +3,8 @@ import * as path from "path"; import * as lowdb from "lowdb"; import * as FileSync from "lowdb/adapters/FileSync"; +import * as lock from "proper-lockfile"; +import { OperationOptions } from "retry"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; @@ -10,6 +12,13 @@ import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; import { sequentialize } from "@bitwarden/common/misc/sequentialize"; import { Utils } from "@bitwarden/common/misc/utils"; +const retries: OperationOptions = { + retries: 50, + minTimeout: 100, + maxTimeout: 250, + factor: 2, +}; + export class LowdbStorageService implements AbstractStorageService { protected dataFilePath: string; private db: lowdb.LowdbSync; @@ -20,7 +29,8 @@ export class LowdbStorageService implements AbstractStorageService { protected logService: LogService, defaults?: any, private dir?: string, - private allowCache = false + private allowCache = false, + private requireLock = false ) { this.defaults = defaults; } @@ -130,8 +140,18 @@ export class LowdbStorageService implements AbstractStorageService { } protected async lockDbFile(action: () => T): Promise { - // Lock methods implemented in clients - return Promise.resolve(action()); + if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) { + this.logService.info("acquiring db file lock"); + return await lock.lock(this.dataFilePath, { retries: retries }).then((release) => { + try { + return action(); + } finally { + release(); + } + }); + } else { + return action(); + } } private readForNoCache() { diff --git a/apps/cli/src/services/lowdbStorage.service.ts b/apps/cli/src/services/lowdbStorage.service.ts deleted file mode 100644 index e01a161c518..00000000000 --- a/apps/cli/src/services/lowdbStorage.service.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as lock from "proper-lockfile"; -import { OperationOptions } from "retry"; - -import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { Utils } from "@bitwarden/common/misc/utils"; -import { LowdbStorageService as LowdbStorageServiceBase } from "@bitwarden/node/services/lowdbStorage.service"; - -const retries: OperationOptions = { - retries: 50, - minTimeout: 100, - maxTimeout: 250, - factor: 2, -}; - -export class LowdbStorageService extends LowdbStorageServiceBase { - constructor( - logService: LogService, - defaults?: any, - dir?: string, - allowCache = false, - private requireLock = false - ) { - super(logService, defaults, dir, allowCache); - } - - protected async lockDbFile(action: () => T): Promise { - if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) { - this.logService.info("acquiring db file lock"); - return await lock.lock(this.dataFilePath, { retries: retries }).then((release) => { - try { - return action(); - } finally { - release(); - } - }); - } else { - return action(); - } - } -} diff --git a/libs/node/src/services/nodeApi.service.ts b/apps/cli/src/services/node-api.service.ts similarity index 100% rename from libs/node/src/services/nodeApi.service.ts rename to apps/cli/src/services/node-api.service.ts diff --git a/apps/cli/src/services/nodeEnvSecureStorage.service.ts b/apps/cli/src/services/node-env-secure-storage.service.ts similarity index 100% rename from apps/cli/src/services/nodeEnvSecureStorage.service.ts rename to apps/cli/src/services/node-env-secure-storage.service.ts diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts index 440d02c25d2..e41c1c7896a 100644 --- a/apps/cli/src/utils.ts +++ b/apps/cli/src/utils.ts @@ -10,8 +10,9 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { Organization } from "@bitwarden/common/models/domain/organization"; import { CollectionView } from "@bitwarden/common/models/view/collection.view"; import { FolderView } from "@bitwarden/common/models/view/folder.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; + +import { Response } from "./models/response"; +import { MessageResponse } from "./models/response/message.response"; export class CliUtils { static writeLn(s: string, finalLine = false, error = false) { diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 616568fe248..a3edf1a9132 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -1,7 +1,5 @@ import * as program from "commander"; -import { Response } from "@bitwarden/node/cli/models/response"; - import { Main } from "./bw"; import { ConfirmCommand } from "./commands/confirm.command"; import { CreateCommand } from "./commands/create.command"; @@ -13,6 +11,7 @@ import { ImportCommand } from "./commands/import.command"; import { ListCommand } from "./commands/list.command"; import { RestoreCommand } from "./commands/restore.command"; import { ShareCommand } from "./commands/share.command"; +import { Response } from "./models/response"; import { Program } from "./program"; import { CliUtils } from "./utils"; diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index da47ac52d8b..845c6b5afcf 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -12,9 +12,10 @@ "sourceMap": true, "baseUrl": ".", "paths": { + "@bitwarden/common/spec/*": ["../../libs/common/spec/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/node/*": ["../../libs/node/src/*"] } }, - "include": ["src"] + "include": ["src", "src/**/*.spec.ts"] } diff --git a/libs/node/src/cli/baseProgram.ts b/libs/node/src/cli/baseProgram.ts deleted file mode 100644 index 46e9db3ec98..00000000000 --- a/libs/node/src/cli/baseProgram.ts +++ /dev/null @@ -1,113 +0,0 @@ -import * as chalk from "chalk"; - -import { StateService } from "@bitwarden/common/abstractions/state.service"; - -import { Response } from "./models/response"; -import { ListResponse } from "./models/response/listResponse"; -import { MessageResponse } from "./models/response/messageResponse"; -import { StringResponse } from "./models/response/stringResponse"; - -export abstract class BaseProgram { - constructor( - protected stateService: StateService, - private writeLn: (s: string, finalLine: boolean, error: boolean) => void - ) {} - - protected processResponse( - response: Response, - exitImmediately = false, - dataProcessor: () => string = null - ) { - if (!response.success) { - if (process.env.BW_QUIET !== "true") { - if (process.env.BW_RESPONSE === "true") { - this.writeLn(this.getJson(response), true, false); - } else { - this.writeLn(chalk.redBright(response.message), true, true); - } - } - const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; - if (exitImmediately) { - process.exit(exitCode); - } else { - process.exitCode = exitCode; - } - return; - } - - if (process.env.BW_RESPONSE === "true") { - this.writeLn(this.getJson(response), true, false); - } else if (response.data != null) { - let out: string = dataProcessor != null ? dataProcessor() : null; - if (out == null) { - if (response.data.object === "string") { - const data = (response.data as StringResponse).data; - if (data != null) { - out = data; - } - } else if (response.data.object === "list") { - out = this.getJson((response.data as ListResponse).data); - } else if (response.data.object === "message") { - out = this.getMessage(response); - } else { - out = this.getJson(response.data); - } - } - - if (out != null && process.env.BW_QUIET !== "true") { - this.writeLn(out, true, false); - } - } - if (exitImmediately) { - process.exit(0); - } else { - process.exitCode = 0; - } - } - - protected getJson(obj: any): string { - if (process.env.BW_PRETTY === "true") { - return JSON.stringify(obj, null, " "); - } else { - return JSON.stringify(obj); - } - } - - protected getMessage(response: Response): string { - const message = response.data as MessageResponse; - if (process.env.BW_RAW === "true") { - return message.raw; - } - - let out = ""; - if (message.title != null) { - if (message.noColor) { - out = message.title; - } else { - out = chalk.greenBright(message.title); - } - } - if (message.message != null) { - if (message.title != null) { - out += "\n"; - } - out += message.message; - } - return out.trim() === "" ? null : out; - } - - protected async exitIfAuthed() { - const authed = await this.stateService.getIsAuthenticated(); - if (authed) { - const email = await this.stateService.getEmail(); - this.processResponse(Response.error("You are already logged in as " + email + "."), true); - } - } - - protected async exitIfNotAuthed() { - const authed = await this.stateService.getIsAuthenticated(); - if (!authed) { - this.processResponse(Response.error("You are not logged in."), true); - } - } -} diff --git a/libs/node/src/cli/commands/login.command.ts b/libs/node/src/cli/commands/login.command.ts deleted file mode 100644 index 413b5add5ee..00000000000 --- a/libs/node/src/cli/commands/login.command.ts +++ /dev/null @@ -1,632 +0,0 @@ -import * as http from "http"; - -import * as program from "commander"; -import * as inquirer from "inquirer"; -import Separator from "inquirer/lib/objects/separator"; -import { firstValueFrom } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuthService } from "@bitwarden/common/abstractions/auth.service"; -import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; -import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; -import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; -import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; -import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; -import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; -import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; -import { Utils } from "@bitwarden/common/misc/utils"; -import { AuthResult } from "@bitwarden/common/models/domain/auth-result"; -import { - UserApiLogInCredentials, - PasswordLogInCredentials, - SsoLogInCredentials, -} from "@bitwarden/common/models/domain/log-in-credentials"; -import { TokenTwoFactorRequest } from "@bitwarden/common/models/request/identity-token/token-two-factor.request"; -import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/two-factor-email.request"; -import { UpdateTempPasswordRequest } from "@bitwarden/common/models/request/update-temp-password.request"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; - -import { Response } from "../models/response"; -import { MessageResponse } from "../models/response/messageResponse"; - -export class LoginCommand { - protected validatedParams: () => Promise; - protected success: () => Promise; - protected logout: () => Promise; - protected canInteract: boolean; - protected clientId: string; - protected clientSecret: string; - protected email: string; - - private ssoRedirectUri: string = null; - - constructor( - protected authService: AuthService, - protected apiService: ApiService, - protected i18nService: I18nService, - protected environmentService: EnvironmentService, - protected passwordGenerationService: PasswordGenerationService, - protected cryptoFunctionService: CryptoFunctionService, - protected platformUtilsService: PlatformUtilsService, - protected stateService: StateService, - protected cryptoService: CryptoService, - protected policyService: PolicyService, - protected twoFactorService: TwoFactorService, - clientId: string - ) { - this.clientId = clientId; - } - - async run(email: string, password: string, options: program.OptionValues) { - this.canInteract = process.env.BW_NOINTERACTION !== "true"; - - let ssoCodeVerifier: string = null; - let ssoCode: string = null; - let orgIdentifier: string = null; - - let clientId: string = null; - let clientSecret: string = null; - - let selectedProvider: any = null; - - if (options.apikey != null) { - const apiIdentifiers = await this.apiIdentifiers(); - clientId = apiIdentifiers.clientId; - clientSecret = apiIdentifiers.clientSecret; - } else if (options.sso != null && this.canInteract) { - const passwordOptions: any = { - type: "password", - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; - const state = await this.passwordGenerationService.generatePassword(passwordOptions); - ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - try { - const ssoParams = await this.openSsoPrompt(codeChallenge, state); - ssoCode = ssoParams.ssoCode; - orgIdentifier = ssoParams.orgIdentifier; - } catch { - return Response.badRequest("Something went wrong. Try again."); - } - } else { - if ((email == null || email === "") && this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "email", - message: "Email address:", - }); - email = answer.email; - } - if (email == null || email.trim() === "") { - return Response.badRequest("Email address is required."); - } - if (email.indexOf("@") === -1) { - return Response.badRequest("Email address is invalid."); - } - this.email = email; - - if (password == null || password === "") { - if (options.passwordfile) { - password = await NodeUtils.readFirstLine(options.passwordfile); - } else if (options.passwordenv && process.env[options.passwordenv]) { - password = process.env[options.passwordenv]; - } else if (this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "password", - name: "password", - message: "Master password:", - }); - password = answer.password; - } - } - - if (password == null || password === "") { - return Response.badRequest("Master password is required."); - } - } - - let twoFactorToken: string = options.code; - let twoFactorMethod: TwoFactorProviderType = null; - try { - if (options.method != null) { - twoFactorMethod = parseInt(options.method, null); - } - } catch (e) { - return Response.error("Invalid two-step login method."); - } - - const twoFactor = - twoFactorToken == null - ? null - : new TokenTwoFactorRequest(twoFactorMethod, twoFactorToken, false); - - try { - if (this.validatedParams != null) { - await this.validatedParams(); - } - - let response: AuthResult = null; - if (clientId != null && clientSecret != null) { - if (!clientId.startsWith("user")) { - return Response.error("Invalid API Key; Organization API Key currently not supported"); - } - response = await this.authService.logIn( - new UserApiLogInCredentials(clientId, clientSecret) - ); - } else if (ssoCode != null && ssoCodeVerifier != null) { - response = await this.authService.logIn( - new SsoLogInCredentials( - ssoCode, - ssoCodeVerifier, - this.ssoRedirectUri, - orgIdentifier, - twoFactor - ) - ); - } else { - response = await this.authService.logIn( - new PasswordLogInCredentials(email, password, null, twoFactor) - ); - } - if (response.captchaSiteKey) { - const credentials = new PasswordLogInCredentials(email, password); - const handledResponse = await this.handleCaptchaRequired(twoFactor, credentials); - - // Error Response - if (handledResponse instanceof Response) { - return handledResponse; - } else { - response = handledResponse; - } - } - if (response.requiresTwoFactor) { - const twoFactorProviders = this.twoFactorService.getSupportedProviders(null); - if (twoFactorProviders.length === 0) { - return Response.badRequest("No providers available for this client."); - } - - if (twoFactorMethod != null) { - try { - selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; - } catch (e) { - return Response.error("Invalid two-step login method."); - } - } - - if (selectedProvider == null) { - if (twoFactorProviders.length === 1) { - selectedProvider = twoFactorProviders[0]; - } else if (this.canInteract) { - const twoFactorOptions: (string | Separator)[] = twoFactorProviders.map((p) => p.name); - twoFactorOptions.push(new inquirer.Separator()); - twoFactorOptions.push("Cancel"); - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "list", - name: "method", - message: "Two-step login method:", - choices: twoFactorOptions, - }); - const i = twoFactorOptions.indexOf(answer.method); - if (i === twoFactorOptions.length - 1) { - return Response.error("Login failed."); - } - selectedProvider = twoFactorProviders[i]; - } - if (selectedProvider == null) { - return Response.error("Login failed. No provider selected."); - } - } - - if ( - twoFactorToken == null && - response.twoFactorProviders.size > 1 && - selectedProvider.type === TwoFactorProviderType.Email - ) { - const emailReq = new TwoFactorEmailRequest(); - emailReq.email = this.authService.email; - emailReq.masterPasswordHash = this.authService.masterPasswordHash; - await this.apiService.postTwoFactorEmail(emailReq); - } - - if (twoFactorToken == null) { - if (this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "token", - message: "Two-step login code:", - }); - twoFactorToken = answer.token; - } - if (twoFactorToken == null || twoFactorToken === "") { - return Response.badRequest("Code is required."); - } - } - - response = await this.authService.logInTwoFactor( - new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken), - null - ); - } - - if (response.captchaSiteKey) { - const twoFactorRequest = new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken); - const handledResponse = await this.handleCaptchaRequired(twoFactorRequest); - - // Error Response - if (handledResponse instanceof Response) { - return handledResponse; - } else { - response = handledResponse; - } - } - - if (response.requiresTwoFactor) { - return Response.error("Login failed."); - } - - if (response.resetMasterPassword) { - return Response.error( - "In order to log in with SSO from the CLI, you must first log in" + - " through the web vault to set your master password." - ); - } - - // Handle Updating Temp Password if NOT using an API Key for authentication - if (response.forcePasswordReset && clientId == null && clientSecret == null) { - return await this.updateTempPassword(); - } - - return await this.handleSuccessResponse(); - } catch (e) { - return Response.error(e); - } - } - - private async handleSuccessResponse(): Promise { - if (this.success != null) { - const res = await this.success(); - return Response.success(res); - } else { - const res = new MessageResponse("You are logged in!", null); - return Response.success(res); - } - } - - private async updateTempPassword(error?: string): Promise { - // If no interaction available, alert user to use web vault - if (!this.canInteract) { - await this.logout(); - this.authService.logOut(() => { - /* Do nothing */ - }); - return Response.error( - new MessageResponse( - "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now via the web vault. You have been logged out.", - null - ) - ); - } - - if (this.email == null || this.email === "undefined") { - this.email = await this.stateService.getEmail(); - } - - // Get New Master Password - const baseMessage = - "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.\n" + - "Master password: "; - const firstMessage = error != null ? error + baseMessage : baseMessage; - const mp: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ - type: "password", - name: "password", - message: firstMessage, - }); - const masterPassword = mp.password; - - // Master Password Validation - if (masterPassword == null || masterPassword === "") { - return this.updateTempPassword("Master password is required.\n"); - } - - if (masterPassword.length < 8) { - return this.updateTempPassword("Master password must be at least 8 characters long.\n"); - } - - // Strength & Policy Validation - const strengthResult = this.passwordGenerationService.passwordStrength( - masterPassword, - this.getPasswordStrengthUserInput() - ); - - // Get New Master Password Re-type - const reTypeMessage = "Re-type New Master password (Strength: " + strengthResult.score + ")"; - const retype: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ - type: "password", - name: "password", - message: reTypeMessage, - }); - const masterPasswordRetype = retype.password; - - // Re-type Validation - if (masterPassword !== masterPasswordRetype) { - return this.updateTempPassword("Master password confirmation does not match.\n"); - } - - // Get Hint (optional) - const hint: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ - type: "input", - name: "input", - message: "Master Password Hint (optional):", - }); - const masterPasswordHint = hint.input; - - // Retrieve details for key generation - const enforcedPolicyOptions = await firstValueFrom( - this.policyService.masterPasswordPolicyOptions$() - ); - const kdf = await this.stateService.getKdfType(); - const kdfIterations = await this.stateService.getKdfIterations(); - - if ( - enforcedPolicyOptions != null && - !this.policyService.evaluateMasterPassword( - strengthResult.score, - masterPassword, - enforcedPolicyOptions - ) - ) { - return this.updateTempPassword( - "Your new master password does not meet the policy requirements.\n" - ); - } - - try { - // Create new key and hash new password - const newKey = await this.cryptoService.makeKey( - masterPassword, - this.email.trim().toLowerCase(), - kdf, - kdfIterations - ); - const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey); - - // Grab user's current enc key - const userEncKey = await this.cryptoService.getEncKey(); - - // Create new encKey for the User - const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey); - - // Create request - const request = new UpdateTempPasswordRequest(); - request.key = newEncKey[1].encryptedString; - request.newMasterPasswordHash = newPasswordHash; - request.masterPasswordHint = masterPasswordHint; - - // Update user's password - await this.apiService.putUpdateTempPassword(request); - return this.handleSuccessResponse(); - } catch (e) { - await this.logout(); - this.authService.logOut(() => { - /* Do nothing */ - }); - return Response.error(e); - } - } - - private async handleCaptchaRequired( - twoFactorRequest: TokenTwoFactorRequest, - credentials: PasswordLogInCredentials = null - ): Promise { - const badCaptcha = Response.badRequest( - "Your authentication request has been flagged and will require user interaction to proceed.\n" + - "Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" + - "(https://bitwarden.com/help/cli-auth-challenges)" - ); - - try { - const captchaClientSecret = await this.apiClientSecret(true); - if (Utils.isNullOrWhitespace(captchaClientSecret)) { - return badCaptcha; - } - - let authResultResponse: AuthResult = null; - if (credentials != null) { - credentials.captchaToken = captchaClientSecret; - credentials.twoFactor = twoFactorRequest; - authResultResponse = await this.authService.logIn(credentials); - } else { - authResultResponse = await this.authService.logInTwoFactor( - twoFactorRequest, - captchaClientSecret - ); - } - - return authResultResponse; - } catch (e) { - if ( - e instanceof ErrorResponse || - (e.constructor.name === ErrorResponse.name && - (e as ErrorResponse).message.includes("Captcha is invalid")) - ) { - return badCaptcha; - } else { - return Response.error(e); - } - } - } - - private getPasswordStrengthUserInput() { - let userInput: string[] = []; - const atPosition = this.email.indexOf("@"); - if (atPosition > -1) { - userInput = userInput.concat( - this.email - .substr(0, atPosition) - .trim() - .toLowerCase() - .split(/[^A-Za-z0-9]/) - ); - } - return userInput; - } - - private async apiClientId(): Promise { - let clientId: string = null; - - const storedClientId: string = process.env.BW_CLIENTID; - if (storedClientId == null) { - if (this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "clientId", - message: "client_id:", - }); - clientId = answer.clientId; - } else { - clientId = null; - } - } else { - clientId = storedClientId; - } - - return clientId; - } - - private async apiClientSecret(isAdditionalAuthentication = false): Promise { - const additionalAuthenticationMessage = "Additional authentication required.\nAPI key "; - let clientSecret: string = null; - - const storedClientSecret: string = this.clientSecret || process.env.BW_CLIENTSECRET; - if (this.canInteract && storedClientSecret == null) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "clientSecret", - message: - (isAdditionalAuthentication ? additionalAuthenticationMessage : "") + "client_secret:", - }); - clientSecret = answer.clientSecret; - } else { - clientSecret = storedClientSecret; - } - - return clientSecret; - } - - private async apiIdentifiers(): Promise<{ clientId: string; clientSecret: string }> { - return { - clientId: await this.apiClientId(), - clientSecret: await this.apiClientSecret(), - }; - } - - private async openSsoPrompt( - codeChallenge: string, - state: string - ): Promise<{ ssoCode: string; orgIdentifier: string }> { - return new Promise((resolve, reject) => { - const callbackServer = http.createServer((req, res) => { - const urlString = "http://localhost" + req.url; - const url = new URL(urlString); - const code = url.searchParams.get("code"); - const receivedState = url.searchParams.get("state"); - const orgIdentifier = this.getOrgIdentifierFromState(receivedState); - res.setHeader("Content-Type", "text/html"); - if (code != null && receivedState != null && this.checkState(receivedState, state)) { - res.writeHead(200); - res.end( - "Success | Bitwarden CLI" + - "

Successfully authenticated with the Bitwarden CLI

" + - "

You may now close this tab and return to the terminal.

" + - "" - ); - callbackServer.close(() => - resolve({ - ssoCode: code, - orgIdentifier: orgIdentifier, - }) - ); - } else { - res.writeHead(400); - res.end( - "Failed | Bitwarden CLI" + - "

Something went wrong logging into the Bitwarden CLI

" + - "

You may now close this tab and return to the terminal.

" + - "" - ); - callbackServer.close(() => reject()); - } - }); - let foundPort = false; - const webUrl = this.environmentService.getWebVaultUrl(); - for (let port = 8065; port <= 8070; port++) { - try { - this.ssoRedirectUri = "http://localhost:" + port; - callbackServer.listen(port, () => { - this.platformUtilsService.launchUri( - webUrl + - "/#/sso?clientId=" + - this.clientId + - "&redirectUri=" + - encodeURIComponent(this.ssoRedirectUri) + - "&state=" + - state + - "&codeChallenge=" + - codeChallenge - ); - }); - foundPort = true; - break; - } catch { - // Ignore error since we run the same command up to 5 times. - } - } - if (!foundPort) { - reject(); - } - }); - } - - private getOrgIdentifierFromState(state: string): string { - if (state === null || state === undefined) { - return null; - } - - const stateSplit = state.split("_identifier="); - return stateSplit.length > 1 ? stateSplit[1] : null; - } - - private checkState(state: string, checkState: string): boolean { - if (state === null || state === undefined) { - return false; - } - if (checkState === null || checkState === undefined) { - return false; - } - - const stateSplit = state.split("_identifier="); - const checkStateSplit = checkState.split("_identifier="); - return stateSplit[0] === checkStateSplit[0]; - } -} From 076e605f101646523387ce620bac11c932b66ab7 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Fri, 18 Nov 2022 16:38:28 -0500 Subject: [PATCH 002/205] [PS-1879] Fix Key Connector Migration Flow (#4080) * Move OrganizationService to fullSync * Add Tech Debt Tracking Link * Remove Commented out code * Add InternalOrganizationService * Use InternalOrganization in services that get to update state --- .../browser/src/background/main.background.ts | 8 ++-- .../organization-service.factory.ts | 11 +---- apps/cli/src/bw.ts | 4 +- apps/web/webpack.config.js | 1 + .../src/services/jslib-services.module.ts | 15 ++++-- .../organization/organization.service.spec.ts | 48 ++----------------- .../organization.service.abstraction.ts | 5 ++ .../organization/organization.service.ts | 45 ++++------------- libs/common/src/services/sync/sync.service.ts | 23 ++++++--- 9 files changed, 54 insertions(+), 106 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index cc973a8c826..581da587e45 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -17,7 +17,7 @@ import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarde import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service"; import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; -import { OrganizationService as OrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; +import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction"; @@ -153,7 +153,7 @@ export default class MainBackground { popupUtilsService: PopupUtilsService; sendService: SendServiceAbstraction; fileUploadService: FileUploadServiceAbstraction; - organizationService: OrganizationServiceAbstraction; + organizationService: InternalOrganizationServiceAbstraction; providerService: ProviderServiceAbstraction; keyConnectorService: KeyConnectorServiceAbstraction; userVerificationService: UserVerificationServiceAbstraction; @@ -317,7 +317,7 @@ export default class MainBackground { this.stateService ); this.syncNotifierService = new SyncNotifierService(); - this.organizationService = new OrganizationService(this.stateService, this.syncNotifierService); + this.organizationService = new OrganizationService(this.stateService); this.policyService = new PolicyService(this.stateService, this.organizationService); this.policyApiService = new PolicyApiService( this.policyService, @@ -409,7 +409,7 @@ export default class MainBackground { this.stateService, this.providerService, this.folderApiService, - this.syncNotifierService, + this.organizationService, logoutCallback ); this.eventService = new EventService( diff --git a/apps/browser/src/background/service_factories/organization-service.factory.ts b/apps/browser/src/background/service_factories/organization-service.factory.ts index 2e7c31b596e..ea11d32e26a 100644 --- a/apps/browser/src/background/service_factories/organization-service.factory.ts +++ b/apps/browser/src/background/service_factories/organization-service.factory.ts @@ -3,15 +3,10 @@ import { OrganizationService } from "@bitwarden/common/services/organization/org import { FactoryOptions, CachedServices, factory } from "./factory-options"; import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; -import { - syncNotifierServiceFactory, - SyncNotifierServiceInitOptions, -} from "./sync-notifier-service.factory"; type OrganizationServiceFactoryOptions = FactoryOptions; export type OrganizationServiceInitOptions = OrganizationServiceFactoryOptions & - SyncNotifierServiceInitOptions & StateServiceInitOptions; export function organizationServiceFactory( @@ -22,10 +17,6 @@ export function organizationServiceFactory( cache, "organizationService", opts, - async () => - new OrganizationService( - await stateServiceFactory(cache, opts), - await syncNotifierServiceFactory(cache, opts) - ) + async () => new OrganizationService(await stateServiceFactory(cache, opts)) ); } diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 8f877e7fbf0..1cb03db67bc 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -240,7 +240,7 @@ export class Main { this.providerService = new ProviderService(this.stateService); - this.organizationService = new OrganizationService(this.stateService, this.syncNotifierService); + this.organizationService = new OrganizationService(this.stateService); this.policyService = new PolicyService(this.stateService, this.organizationService); @@ -322,7 +322,7 @@ export class Main { this.stateService, this.providerService, this.folderApiService, - this.syncNotifierService, + this.organizationService, async (expired: boolean) => await this.logout() ); diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index cecfbea3998..2000fe877ab 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -273,6 +273,7 @@ const devServer = https://quack.duckduckgo.com/api/email/addresses https://app.anonaddy.com/api/v1/aliases https://api.fastmail.com + http://localhost:5000 ;object-src 'self' blob: diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 5f49041bd76..5399c90e635 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -35,7 +35,10 @@ import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstr 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"; -import { OrganizationService as OrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; +import { + InternalOrganizationService, + OrganizationService as OrganizationServiceAbstraction, +} from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -356,7 +359,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; StateServiceAbstraction, ProviderServiceAbstraction, FolderApiServiceAbstraction, - SyncNotifierServiceAbstraction, + OrganizationServiceAbstraction, LOGOUT_CALLBACK, ], }, @@ -506,6 +509,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; LogService, OrganizationServiceAbstraction, CryptoFunctionServiceAbstraction, + SyncNotifierServiceAbstraction, + MessagingServiceAbstraction, LOGOUT_CALLBACK, ], }, @@ -522,7 +527,11 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; { provide: OrganizationServiceAbstraction, useClass: OrganizationService, - deps: [StateServiceAbstraction, SyncNotifierServiceAbstraction], + deps: [StateServiceAbstraction], + }, + { + provide: InternalOrganizationService, + useExisting: OrganizationServiceAbstraction, }, { provide: ProviderServiceAbstraction, diff --git a/libs/common/spec/services/organization/organization.service.spec.ts b/libs/common/spec/services/organization/organization.service.spec.ts index 87226735712..42a327c18fd 100644 --- a/libs/common/spec/services/organization/organization.service.spec.ts +++ b/libs/common/spec/services/organization/organization.service.spec.ts @@ -1,12 +1,9 @@ -import { MockProxy, mock, any, mockClear, matches } from "jest-mock-extended"; -import { BehaviorSubject, firstValueFrom, Subject } from "rxjs"; +import { MockProxy, mock, any, mockClear } from "jest-mock-extended"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { SyncNotifierService } from "@bitwarden/common/abstractions/sync/syncNotifier.service.abstraction"; import { OrganizationData } from "@bitwarden/common/models/data/organization.data"; -import { SyncResponse } from "@bitwarden/common/models/response/sync.response"; import { OrganizationService } from "@bitwarden/common/services/organization/organization.service"; -import { SyncEventArgs } from "@bitwarden/common/types/syncEventArgs"; describe("Organization Service", () => { let organizationService: OrganizationService; @@ -14,8 +11,6 @@ describe("Organization Service", () => { let stateService: MockProxy; let activeAccount: BehaviorSubject; let activeAccountUnlocked: BehaviorSubject; - let syncNotifierService: MockProxy; - let sync: Subject; const resetStateService = async ( customizeStateService: (stateService: MockProxy) => void @@ -25,7 +20,7 @@ describe("Organization Service", () => { stateService.activeAccount$ = activeAccount; stateService.activeAccountUnlocked$ = activeAccountUnlocked; customizeStateService(stateService); - organizationService = new OrganizationService(stateService, syncNotifierService); + organizationService = new OrganizationService(stateService); await new Promise((r) => setTimeout(r, 50)); }; @@ -41,12 +36,7 @@ describe("Organization Service", () => { "1": organizationData("1", "Test Org"), }); - sync = new Subject(); - - syncNotifierService = mock(); - syncNotifierService.sync$ = sync; - - organizationService = new OrganizationService(stateService, syncNotifierService); + organizationService = new OrganizationService(stateService); }); afterEach(() => { @@ -169,36 +159,6 @@ describe("Organization Service", () => { }); }); - describe("syncEvent works", () => { - it("Complete event updates data", async () => { - sync.next({ - status: "Completed", - successfully: true, - data: new SyncResponse({ - profile: { - organizations: [ - { - id: "1", - name: "Updated Name", - }, - ], - }, - }), - }); - - await new Promise((r) => setTimeout(r, 500)); - - expect(stateService.setOrganizations).toHaveBeenCalledTimes(1); - - expect(stateService.setOrganizations).toHaveBeenLastCalledWith( - matches((organizationData: { [id: string]: OrganizationData }) => { - const organization = organizationData["1"]; - return organization?.name === "Updated Name"; - }) - ); - }); - }); - function organizationData(id: string, name: string) { const data = new OrganizationData({} as any); data.id = id; diff --git a/libs/common/src/abstractions/organization/organization.service.abstraction.ts b/libs/common/src/abstractions/organization/organization.service.abstraction.ts index 3dfeb90cb03..68c64654d57 100644 --- a/libs/common/src/abstractions/organization/organization.service.abstraction.ts +++ b/libs/common/src/abstractions/organization/organization.service.abstraction.ts @@ -1,6 +1,7 @@ import { map, Observable } from "rxjs"; import { Utils } from "../../misc/utils"; +import { OrganizationData } from "../../models/data/organization.data"; import { Organization } from "../../models/domain/organization"; import { I18nService } from "../i18n.service"; @@ -83,3 +84,7 @@ export abstract class OrganizationService { canManageSponsorships: () => Promise; hasOrganizations: () => boolean; } + +export abstract class InternalOrganizationService extends OrganizationService { + replace: (organizations: { [id: string]: OrganizationData }) => Promise; +} diff --git a/libs/common/src/services/organization/organization.service.ts b/libs/common/src/services/organization/organization.service.ts index 101069adadc..5d936c53ae7 100644 --- a/libs/common/src/services/organization/organization.service.ts +++ b/libs/common/src/services/organization/organization.service.ts @@ -1,21 +1,16 @@ -import { BehaviorSubject, concatMap, filter } from "rxjs"; +import { BehaviorSubject, concatMap } from "rxjs"; -import { OrganizationService as OrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction"; +import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction"; import { StateService } from "../../abstractions/state.service"; -import { SyncNotifierService } from "../../abstractions/sync/syncNotifier.service.abstraction"; import { OrganizationData } from "../../models/data/organization.data"; import { Organization } from "../../models/domain/organization"; -import { isSuccessfullyCompleted } from "../../types/syncEventArgs"; -export class OrganizationService implements OrganizationServiceAbstraction { +export class OrganizationService implements InternalOrganizationServiceAbstraction { private _organizations = new BehaviorSubject([]); organizations$ = this._organizations.asObservable(); - constructor( - private stateService: StateService, - private syncNotifierService: SyncNotifierService - ) { + constructor(private stateService: StateService) { this.stateService.activeAccountUnlocked$ .pipe( concatMap(async (unlocked) => { @@ -29,28 +24,6 @@ export class OrganizationService implements OrganizationServiceAbstraction { }) ) .subscribe(); - - this.syncNotifierService.sync$ - .pipe( - filter(isSuccessfullyCompleted), - concatMap(async ({ data }) => { - const { profile } = data; - const organizations: { [id: string]: OrganizationData } = {}; - profile.organizations.forEach((o) => { - organizations[o.id] = new OrganizationData(o); - }); - - profile.providerOrganizations.forEach((o) => { - if (organizations[o.id] == null) { - organizations[o.id] = new OrganizationData(o); - organizations[o.id].isProviderUser = true; - } - }); - - await this.updateStateAndObservables(organizations); - }) - ) - .subscribe(); } async getAll(userId?: string): Promise { @@ -78,7 +51,7 @@ export class OrganizationService implements OrganizationServiceAbstraction { organizations[organization.id] = organization; - await this.updateStateAndObservables(organizations); + await this.replace(organizations); } async delete(id: string): Promise { @@ -92,7 +65,7 @@ export class OrganizationService implements OrganizationServiceAbstraction { } delete organizations[id]; - await this.updateStateAndObservables(organizations); + await this.replace(organizations); } get(id: string): Organization { @@ -121,9 +94,9 @@ export class OrganizationService implements OrganizationServiceAbstraction { return organizations.find((organization) => organization.identifier === identifier); } - private async updateStateAndObservables(organizationsMap: { [id: string]: OrganizationData }) { - await this.stateService.setOrganizations(organizationsMap); - this.updateObservables(organizationsMap); + async replace(organizations: { [id: string]: OrganizationData }) { + await this.stateService.setOrganizations(organizations); + this.updateObservables(organizations); } private updateObservables(organizationsMap: { [id: string]: OrganizationData }) { diff --git a/libs/common/src/services/sync/sync.service.ts b/libs/common/src/services/sync/sync.service.ts index 2513871636d..cd3f8e40ff6 100644 --- a/libs/common/src/services/sync/sync.service.ts +++ b/libs/common/src/services/sync/sync.service.ts @@ -7,17 +7,18 @@ import { InternalFolderService } from "../../abstractions/folder/folder.service. import { KeyConnectorService } from "../../abstractions/keyConnector.service"; import { LogService } from "../../abstractions/log.service"; import { MessagingService } from "../../abstractions/messaging.service"; +import { InternalOrganizationService } from "../../abstractions/organization/organization.service.abstraction"; import { InternalPolicyService } from "../../abstractions/policy/policy.service.abstraction"; import { ProviderService } from "../../abstractions/provider.service"; import { SendService } from "../../abstractions/send.service"; import { SettingsService } from "../../abstractions/settings.service"; import { StateService } from "../../abstractions/state.service"; import { SyncService as SyncServiceAbstraction } from "../../abstractions/sync/sync.service.abstraction"; -import { SyncNotifierService } from "../../abstractions/sync/syncNotifier.service.abstraction"; import { sequentialize } from "../../misc/sequentialize"; import { CipherData } from "../../models/data/cipher.data"; import { CollectionData } from "../../models/data/collection.data"; import { FolderData } from "../../models/data/folder.data"; +import { OrganizationData } from "../../models/data/organization.data"; import { PolicyData } from "../../models/data/policy.data"; import { ProviderData } from "../../models/data/provider.data"; import { SendData } from "../../models/data/send.data"; @@ -52,7 +53,7 @@ export class SyncService implements SyncServiceAbstraction { private stateService: StateService, private providerService: ProviderService, private folderApiService: FolderApiServiceAbstraction, - private syncNotifierService: SyncNotifierService, + private organizationService: InternalOrganizationService, private logoutCallback: (expired: boolean) => Promise ) {} @@ -76,10 +77,8 @@ export class SyncService implements SyncServiceAbstraction { @sequentialize(() => "fullSync") async fullSync(forceSync: boolean, allowThrowOnError = false): Promise { this.syncStarted(); - this.syncNotifierService.next({ status: "Started" }); const isAuthenticated = await this.stateService.getIsAuthenticated(); if (!isAuthenticated) { - this.syncNotifierService.next({ status: "Completed", successfully: false }); return this.syncCompleted(false); } @@ -95,7 +94,6 @@ export class SyncService implements SyncServiceAbstraction { if (!needsSync) { await this.setLastSync(now); - this.syncNotifierService.next({ status: "Completed", successfully: false }); return this.syncCompleted(false); } @@ -112,13 +110,11 @@ export class SyncService implements SyncServiceAbstraction { await this.syncPolicies(response.policies); await this.setLastSync(now); - this.syncNotifierService.next({ status: "Completed", successfully: true, data: response }); return this.syncCompleted(true); } catch (e) { if (allowThrowOnError) { throw e; } else { - this.syncNotifierService.next({ status: "Completed", successfully: false }); return this.syncCompleted(false); } } @@ -315,11 +311,24 @@ export class SyncService implements SyncServiceAbstraction { await this.stateService.setForcePasswordReset(response.forcePasswordReset); await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector); + const organizations: { [id: string]: OrganizationData } = {}; + response.organizations.forEach((o) => { + organizations[o.id] = new OrganizationData(o); + }); + const providers: { [id: string]: ProviderData } = {}; response.providers.forEach((p) => { providers[p.id] = new ProviderData(p); }); + response.providerOrganizations.forEach((o) => { + if (organizations[o.id] == null) { + organizations[o.id] = new OrganizationData(o); + organizations[o.id].isProviderUser = true; + } + }); + + await this.organizationService.replace(organizations); await this.providerService.save(providers); if (await this.keyConnectorService.userNeedsMigration()) { From 156eabe77456a739dabf82e0dbff397f5905cbd2 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 21 Nov 2022 13:13:31 +0100 Subject: [PATCH 003/205] [CL-59] [Bug] Link buttons have different height depending on html tag used (#3954) * [CL-59] feat: add explicit relative line-height to button * [EC-59] feat: fix using pseudo element workaround * [EC-59] fix: inconsistent templates * [CL-59] feat: add inline example * [CL-59] fix: tweak horizontal padding --- libs/components/src/link/link.directive.ts | 92 +++++++++++++++------- libs/components/src/link/link.module.ts | 6 +- libs/components/src/link/link.stories.ts | 57 ++++++++++---- 3 files changed, 109 insertions(+), 46 deletions(-) diff --git a/libs/components/src/link/link.directive.ts b/libs/components/src/link/link.directive.ts index d4ae14058ec..0d80fb27f18 100644 --- a/libs/components/src/link/link.directive.ts +++ b/libs/components/src/link/link.directive.ts @@ -6,49 +6,85 @@ const linkStyles: Record = { primary: [ "!tw-text-primary-500", "hover:!tw-text-primary-500", - "focus-visible:tw-ring-primary-700", + "focus-visible:before:tw-ring-primary-700", "disabled:!tw-text-primary-500/60", ], secondary: [ "!tw-text-main", "hover:!tw-text-main", - "focus-visible:tw-ring-primary-700", + "focus-visible:before:tw-ring-primary-700", "disabled:!tw-text-muted/60", ], contrast: [ "!tw-text-contrast", "hover:!tw-text-contrast", - "focus-visible:tw-ring-text-contrast", + "focus-visible:before:tw-ring-text-contrast", "disabled:!tw-text-contrast/60", ], }; -@Directive({ - selector: "button[bitLink], a[bitLink]", -}) -export class LinkDirective { - @HostBinding("class") get classList() { - return [ - "tw-font-semibold", - "tw-py-0.5", - "tw-px-0", - "tw-bg-transparent", - "tw-border-0", - "tw-border-none", - "tw-rounded", - "tw-transition", - "hover:tw-underline", - "hover:tw-decoration-1", - "focus-visible:tw-outline-none", - "focus-visible:tw-underline", - "focus-visible:tw-decoration-1", - "focus-visible:tw-ring-2", - "focus-visible:tw-z-10", - "disabled:tw-no-underline", - "disabled:tw-cursor-not-allowed", - ].concat(linkStyles[this.linkType] ?? []); - } +const commonStyles = [ + "tw-leading-none", + "tw-p-0", + "tw-font-semibold", + "tw-bg-transparent", + "tw-border-0", + "tw-border-none", + "tw-rounded", + "tw-transition", + "hover:tw-underline", + "hover:tw-decoration-1", + "disabled:tw-no-underline", + "disabled:tw-cursor-not-allowed", + "focus-visible:tw-outline-none", + "focus-visible:tw-underline", + "focus-visible:tw-decoration-1", + // Workaround for html button tag not being able to be set to `display: inline` + // and at the same time not being able to use `tw-ring-offset` because of box-shadow issue. + // https://github.com/w3c/csswg-drafts/issues/3226 + // Add `tw-inline`, add `tw-py-0.5` and use regular `tw-ring` if issue is fixed. + // + // https://github.com/tailwindlabs/tailwindcss/issues/3595 + // Remove `before:` and use regular `tw-ring` when browser no longer has bug, or better: + // switch to `outline` with `outline-offset` when Safari supports border radius on outline. + // Using `box-shadow` to create outlines is a hack and as such `outline` should be preferred. + "tw-relative", + "before:tw-content-['']", + "before:tw-block", + "before:tw-absolute", + "before:-tw-inset-x-[0.1em]", + "before:tw-rounded-md", + "before:tw-transition", + "before:tw-ring-2", + "focus-visible:before:tw-ring-text-contrast", + "focus-visible:tw-z-10", +]; + +@Directive() +abstract class LinkDirective { @Input() linkType: LinkType = "primary"; } + +@Directive({ + selector: "a[bitLink]", +}) +export class AnchorLinkDirective extends LinkDirective { + @HostBinding("class") get classList() { + return ["before:-tw-inset-y-[0.125rem]"] + .concat(commonStyles) + .concat(linkStyles[this.linkType] ?? []); + } +} + +@Directive({ + selector: "button[bitLink]", +}) +export class ButtonLinkDirective extends LinkDirective { + @HostBinding("class") get classList() { + return ["before:-tw-inset-y-[0.25rem]"] + .concat(commonStyles) + .concat(linkStyles[this.linkType] ?? []); + } +} diff --git a/libs/components/src/link/link.module.ts b/libs/components/src/link/link.module.ts index 73e78edf785..b8b54d57c00 100644 --- a/libs/components/src/link/link.module.ts +++ b/libs/components/src/link/link.module.ts @@ -1,11 +1,11 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { LinkDirective } from "./link.directive"; +import { AnchorLinkDirective, ButtonLinkDirective } from "./link.directive"; @NgModule({ imports: [CommonModule], - exports: [LinkDirective], - declarations: [LinkDirective], + exports: [AnchorLinkDirective, ButtonLinkDirective], + declarations: [AnchorLinkDirective, ButtonLinkDirective], }) export class LinkModule {} diff --git a/libs/components/src/link/link.stories.ts b/libs/components/src/link/link.stories.ts index 5c7b11fe352..68567e2f755 100644 --- a/libs/components/src/link/link.stories.ts +++ b/libs/components/src/link/link.stories.ts @@ -1,10 +1,15 @@ -import { Meta, Story } from "@storybook/angular"; +import { Meta, moduleMetadata, Story } from "@storybook/angular"; -import { LinkDirective } from "./link.directive"; +import { AnchorLinkDirective, ButtonLinkDirective } from "./link.directive"; +import { LinkModule } from "./link.module"; export default { title: "Component Library/Link", - component: LinkDirective, + decorators: [ + moduleMetadata({ + imports: [LinkModule], + }), + ], argTypes: { linkType: { options: ["primary", "secondary", "contrast"], @@ -19,25 +24,33 @@ export default { }, } as Meta; -const ButtonTemplate: Story = (args: LinkDirective) => ({ +const ButtonTemplate: Story = (args: ButtonLinkDirective) => ({ props: args, template: `
- - - - +
+ +
+
+ +
+
+ +
+
+ +
`, }); -const AnchorTemplate: Story = (args: LinkDirective) => ({ +const AnchorTemplate: Story = (args: AnchorLinkDirective) => ({ props: args, template: `
@@ -73,6 +86,20 @@ Anchors.args = { linkType: "primary", }; +const InlineTemplate: Story = (args) => ({ + props: args, + template: ` + + On the internet pargraphs often contain inline links, but few know that can be used for similar purposes. + + `, +}); + +export const Inline = InlineTemplate.bind({}); +Inline.args = { + linkType: "primary", +}; + const DisabledTemplate: Story = (args) => ({ props: args, template: ` From a6226c7c90ec19579d73d310f2b050d35621c61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Gon=C3=A7alves?= Date: Mon, 21 Nov 2022 17:08:47 +0000 Subject: [PATCH 004/205] [SG-632] - Change forwarded providers radio buttons list to dropdown (#4045) * SG-632 - Changed forwarded providers list of radio buttons to dropdown * SG-632 - Added role attributes to improve accessibility. * SG-632 - Added sorting to array and empty option * SG-632 - Fix styling to match standards. --- .../popup/generator/generator.component.html | 26 ++++++++----------- .../src/app/vault/generator.component.html | 26 ++++++++----------- .../src/app/tools/generator.component.html | 26 ++++++++----------- .../src/components/generator.component.ts | 25 +++++++++++++----- 4 files changed, 51 insertions(+), 52 deletions(-) diff --git a/apps/browser/src/popup/generator/generator.component.html b/apps/browser/src/popup/generator/generator.component.html index c3ae6c6ab53..1f0a0a328af 100644 --- a/apps/browser/src/popup/generator/generator.component.html +++ b/apps/browser/src/popup/generator/generator.component.html @@ -320,22 +320,18 @@
-
- -
- -
- +
- diff --git a/apps/browser/src/popup/accounts/set-password.component.html b/apps/browser/src/popup/accounts/set-password.component.html index 9eb551bf269..656664facbf 100644 --- a/apps/browser/src/popup/accounts/set-password.component.html +++ b/apps/browser/src/popup/accounts/set-password.component.html @@ -49,6 +49,7 @@ id="masterPassword" type="{{ showPassword ? 'text' : 'password' }}" name="MasterPassword" + aria-describedby="masterPasswordHelp" class="monospaced" [(ngModel)]="masterPassword" required @@ -82,7 +83,7 @@
- @@ -127,10 +128,16 @@
- +
- diff --git a/apps/browser/src/popup/accounts/update-temp-password.component.html b/apps/browser/src/popup/accounts/update-temp-password.component.html index 6fcbb76c3c2..3dea8039cff 100644 --- a/apps/browser/src/popup/accounts/update-temp-password.component.html +++ b/apps/browser/src/popup/accounts/update-temp-password.component.html @@ -109,10 +109,10 @@
- +
- diff --git a/apps/browser/src/popup/components/password-reprompt.component.html b/apps/browser/src/popup/components/password-reprompt.component.html index ed698097356..730e96fab96 100644 --- a/apps/browser/src/popup/components/password-reprompt.component.html +++ b/apps/browser/src/popup/components/password-reprompt.component.html @@ -12,6 +12,7 @@ id="masterPassword" type="{{ showPassword ? 'text' : 'password' }}" name="MasterPassword" + aria-describedby="masterPasswordHelp" class="monospaced" [(ngModel)]="masterPassword" required @@ -36,7 +37,7 @@ - diff --git a/apps/browser/src/popup/components/user-verification.component.html b/apps/browser/src/popup/components/user-verification.component.html index 2fd78bb907d..8d7f1ed8706 100644 --- a/apps/browser/src/popup/components/user-verification.component.html +++ b/apps/browser/src/popup/components/user-verification.component.html @@ -5,6 +5,7 @@ id="masterPassword" type="password" name="MasterPasswordHash" + aria-describedby="confirmIdentityHelp" class="form-control" [formControl]="secret" required diff --git a/apps/browser/src/popup/generator/generator.component.html b/apps/browser/src/popup/generator/generator.component.html index 8178e355363..c6dc49772cf 100644 --- a/apps/browser/src/popup/generator/generator.component.html +++ b/apps/browser/src/popup/generator/generator.component.html @@ -312,7 +312,9 @@ /> diff --git a/apps/browser/src/popup/send/efflux-dates.component.html b/apps/browser/src/popup/send/efflux-dates.component.html index 81fcf8f838b..737fdae4aab 100644 --- a/apps/browser/src/popup/send/efflux-dates.component.html +++ b/apps/browser/src/popup/send/efflux-dates.component.html @@ -7,6 +7,7 @@ + - @@ -104,13 +112,14 @@ - - @@ -215,6 +225,7 @@ id="password" type="{{ showPassword ? 'text' : 'password' }}" name="Password" + aria-describedby="passwordHelp" class="monospaced" [(ngModel)]="password" appInputVerbatim @@ -239,7 +250,7 @@ - @@ -251,13 +262,14 @@ - diff --git a/apps/browser/src/popup/settings/export.component.html b/apps/browser/src/popup/settings/export.component.html index ebd0e03d6f6..db072d6b504 100644 --- a/apps/browser/src/popup/settings/export.component.html +++ b/apps/browser/src/popup/settings/export.component.html @@ -32,7 +32,7 @@ - diff --git a/apps/browser/src/popup/settings/options.component.html b/apps/browser/src/popup/settings/options.component.html index afa77407f5e..cdf8d93c0c4 100644 --- a/apps/browser/src/popup/settings/options.component.html +++ b/apps/browser/src/popup/settings/options.component.html @@ -33,6 +33,7 @@ - +
@@ -49,6 +52,7 @@
- +
@@ -67,12 +71,13 @@
- +
@@ -81,12 +86,15 @@
- +
@@ -97,12 +105,15 @@
- +
@@ -111,12 +122,13 @@
- +
@@ -141,12 +153,15 @@
- +
@@ -155,12 +170,15 @@
- +
@@ -169,12 +187,13 @@
- +
@@ -183,23 +202,30 @@
- +
-
- +
@@ -224,12 +250,13 @@
- - diff --git a/apps/browser/src/popup/vault/share.component.html b/apps/browser/src/popup/vault/share.component.html index dcec42415c0..46aaecd06b8 100644 --- a/apps/browser/src/popup/vault/share.component.html +++ b/apps/browser/src/popup/vault/share.component.html @@ -35,6 +35,7 @@ - diff --git a/apps/desktop/src/app/accounts/accessibility-cookie.component.html b/apps/desktop/src/app/accounts/accessibility-cookie.component.html index fd52508eac3..b5de1e766ff 100644 --- a/apps/desktop/src/app/accounts/accessibility-cookie.component.html +++ b/apps/desktop/src/app/accounts/accessibility-cookie.component.html @@ -14,6 +14,7 @@ id="link" type="text" name="Link" + aria-describedby="linkHelp" formControlName="link" placeholder="{{ 'ex' | i18n }} https://accounts.hcaptcha.com/verify_email" appAutofocus @@ -21,7 +22,7 @@ /> - +
+ - @@ -34,7 +35,13 @@ {{ "customEnvironment" | i18n }} -
+
- diff --git a/apps/desktop/src/app/accounts/hint.component.html b/apps/desktop/src/app/accounts/hint.component.html index 3ab6088c947..a46479efee0 100644 --- a/apps/desktop/src/app/accounts/hint.component.html +++ b/apps/desktop/src/app/accounts/hint.component.html @@ -9,6 +9,7 @@ id="email" type="text" name="Email" + aria-describedby="emailHelp" [(ngModel)]="email" required appAutofocus @@ -16,7 +17,7 @@ />
- diff --git a/apps/desktop/src/app/accounts/lock.component.html b/apps/desktop/src/app/accounts/lock.component.html index f5d8b369355..23adbff18cd 100644 --- a/apps/desktop/src/app/accounts/lock.component.html +++ b/apps/desktop/src/app/accounts/lock.component.html @@ -23,6 +23,7 @@ id="masterPassword" type="{{ showPassword ? 'text' : 'password' }}" name="MasterPassword" + aria-describedby="masterPasswordHelp" class="monospaced" [(ngModel)]="masterPassword" required @@ -47,7 +48,7 @@ - diff --git a/apps/desktop/src/app/accounts/register.component.html b/apps/desktop/src/app/accounts/register.component.html index f270ceec1ff..388c8751312 100644 --- a/apps/desktop/src/app/accounts/register.component.html +++ b/apps/desktop/src/app/accounts/register.component.html @@ -26,6 +26,7 @@ id="masterPassword" type="{{ showPassword ? 'text' : 'password' }}" class="monospaced" + aria-describedby="masterPasswordHelp" formControlName="masterPassword" appInputVerbatim /> @@ -57,7 +58,7 @@ - @@ -93,7 +94,7 @@
- +
@@ -107,7 +108,7 @@
- diff --git a/apps/desktop/src/app/accounts/set-password.component.html b/apps/desktop/src/app/accounts/set-password.component.html index 4730e39e384..0a1522c45d1 100644 --- a/apps/desktop/src/app/accounts/set-password.component.html +++ b/apps/desktop/src/app/accounts/set-password.component.html @@ -46,6 +46,7 @@ type="{{ showPassword ? 'text' : 'password' }}" name="MasterPassword" class="monospaced" + aria-describedby="masterPasswordHelp" [(ngModel)]="masterPassword" required appInputVerbatim @@ -77,7 +78,7 @@ - @@ -122,10 +123,16 @@
- +
- diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index 2e409f6fcab..0a1ed59d3ba 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -44,13 +44,16 @@ name="VaultTimeoutAction" id="vaultTimeoutActionLock" value="lock" + aria-describedby="vaultTimeoutActionLockHelp" [(ngModel)]="vaultTimeoutAction" (change)="saveVaultTimeoutOptions()" /> {{ "lock" | i18n }} - {{ "vaultTimeoutActionLockDesc" | i18n }} + {{ + "vaultTimeoutActionLockDesc" | i18n + }}
- {{ "vaultTimeoutActionLogOutDesc" | i18n }} + {{ + "vaultTimeoutActionLogOutDesc" | i18n + }}
@@ -139,6 +145,7 @@ - {{ "clearClipboardDesc" | i18n }} + {{ + "clearClipboardDesc" | i18n + }}
@@ -155,13 +164,16 @@ id="minimizeOnCopyToClipboard" type="checkbox" name="MinimizeOnCopyToClipboard" + aria-describedby="minimizeOnCopyToClipboardHelp" [(ngModel)]="minimizeOnCopyToClipboard" (change)="saveMinOnCopyToClipboard()" /> {{ "minimizeOnCopyToClipboard" | i18n }}
- {{ "minimizeOnCopyToClipboardDesc" | i18n }} + {{ + "minimizeOnCopyToClipboardDesc" | i18n + }}
@@ -170,13 +182,14 @@ id="enableFavicons" type="checkbox" name="enableFavicons" + aria-describedby="enableFaviconsHelp" [(ngModel)]="enableFavicons" (change)="saveFavicons()" /> {{ "enableFavicon" | i18n }}
- {{ "faviconDesc" | i18n }} + {{ "faviconDesc" | i18n }}
@@ -211,13 +224,14 @@ id="enableTray" type="checkbox" name="EnableTray" + aria-describedby="enableTrayHelp" [(ngModel)]="enableTray" (change)="saveTray()" /> {{ enableTrayText }} - {{ enableTrayDescText }} + {{ enableTrayDescText }}
@@ -226,13 +240,16 @@ id="enableMinToTray" type="checkbox" name="EnableMinToTray" + aria-describedby="enableMinToTrayHelp" [(ngModel)]="enableMinToTray" (change)="saveMinToTray()" /> {{ enableMinToTrayText }}
- {{ enableMinToTrayDescText }} + {{ + enableMinToTrayDescText + }}
@@ -241,13 +258,16 @@ id="enableCloseToTray" type="checkbox" name="EnableCloseToTray" + aria-describedby="enableCloseToTrayHelp" [(ngModel)]="enableCloseToTray" (change)="saveCloseToTray()" /> {{ enableCloseToTrayText }}
- {{ enableCloseToTrayDescText }} + {{ + enableCloseToTrayDescText + }}
@@ -256,13 +276,14 @@ id="startToTray" type="checkbox" name="StartToTray" + aria-describedby="startToTrayHelp" [(ngModel)]="startToTray" (change)="saveStartToTray()" /> {{ startToTrayText }}
- {{ startToTrayDescText }} + {{ startToTrayDescText }}
@@ -271,13 +292,16 @@ id="openAtLogin" type="checkbox" name="OpenAtLogin" + aria-describedby="openAtLoginHelp" [(ngModel)]="openAtLogin" (change)="saveOpenAtLogin()" /> {{ "openAtLogin" | i18n }}
- {{ "openAtLoginDesc" | i18n }} + {{ + "openAtLoginDesc" | i18n + }}
@@ -286,13 +310,16 @@ id="alwaysShowDock" type="checkbox" name="AlwaysShowDock" + aria-describedby="alwaysShowDockHelp" [(ngModel)]="alwaysShowDock" (change)="saveAlwaysShowDock()" /> {{ "alwaysShowDock" | i18n }}
- {{ "alwaysShowDockDesc" | i18n }} + {{ + "alwaysShowDockDesc" | i18n + }}
@@ -301,13 +328,16 @@ id="enableBrowserIntegration" type="checkbox" name="EnableBrowserIntegration" + aria-describedby="enableBrowserIntegrationHelp" [(ngModel)]="enableBrowserIntegration" (change)="saveBrowserIntegration()" /> {{ "enableBrowserIntegration" | i18n }}
- {{ "enableBrowserIntegrationDesc" | i18n }} + {{ + "enableBrowserIntegrationDesc" | i18n + }}
@@ -316,6 +346,7 @@ id="enableBrowserIntegrationFingerprint" type="checkbox" name="EnableBrowserIntegrationFingerprint" + aria-describedby="enableBrowserIntegrationFingerprintHelp" [(ngModel)]="enableBrowserIntegrationFingerprint" (change)="saveBrowserIntegrationFingerprint()" [disabled]="!enableBrowserIntegration" @@ -323,7 +354,7 @@ {{ "enableBrowserIntegrationFingerprint" | i18n }}
- {{ + {{ "enableBrowserIntegrationFingerprintDesc" | i18n }}
@@ -346,17 +377,29 @@
- - {{ "themeDesc" | i18n }} + {{ "themeDesc" | i18n }}
- - {{ "languageDesc" | i18n }} + {{ "languageDesc" | i18n }}
diff --git a/apps/desktop/src/app/accounts/update-temp-password.component.html b/apps/desktop/src/app/accounts/update-temp-password.component.html index 1bba7fcdc4f..893257077a8 100644 --- a/apps/desktop/src/app/accounts/update-temp-password.component.html +++ b/apps/desktop/src/app/accounts/update-temp-password.component.html @@ -96,10 +96,10 @@
- +
- diff --git a/apps/desktop/src/app/accounts/vault-timeout-input.component.html b/apps/desktop/src/app/accounts/vault-timeout-input.component.html index 62e5a104c19..21b10d4f736 100644 --- a/apps/desktop/src/app/accounts/vault-timeout-input.component.html +++ b/apps/desktop/src/app/accounts/vault-timeout-input.component.html @@ -8,12 +8,13 @@ - {{ "vaultTimeoutDesc" | i18n }} + {{ "vaultTimeoutDesc" | i18n }}
diff --git a/apps/desktop/src/app/components/password-reprompt.component.html b/apps/desktop/src/app/components/password-reprompt.component.html index 1ec4ef702c2..1ff853c278c 100644 --- a/apps/desktop/src/app/components/password-reprompt.component.html +++ b/apps/desktop/src/app/components/password-reprompt.component.html @@ -14,6 +14,7 @@ id="masterPassword" type="{{ showPassword ? 'text' : 'password' }}" name="MasterPassword" + aria-describedby="masterPasswordHelp" class="monospaced" [(ngModel)]="masterPassword" required @@ -38,7 +39,7 @@
- diff --git a/apps/desktop/src/app/components/user-verification.component.html b/apps/desktop/src/app/components/user-verification.component.html index 2fd78bb907d..10b16a04087 100644 --- a/apps/desktop/src/app/components/user-verification.component.html +++ b/apps/desktop/src/app/components/user-verification.component.html @@ -5,6 +5,7 @@ id="masterPassword" type="password" name="MasterPasswordHash" + aria-describedby="confirmIdentityHelp" class="form-control" [formControl]="secret" required @@ -36,6 +37,7 @@ id="verificationCode" type="input" name="verificationCode" + aria-describedby="confirmIdentityHelp" class="form-control" [formControl]="secret" required diff --git a/apps/desktop/src/app/send/add-edit.component.html b/apps/desktop/src/app/send/add-edit.component.html index bae0e073dac..e9e1b319adb 100644 --- a/apps/desktop/src/app/send/add-edit.component.html +++ b/apps/desktop/src/app/send/add-edit.component.html @@ -51,6 +51,7 @@ id="file" class="form-control-file" name="file" + aria-describedby="fileHelp" required [disabled]="disableSend" /> @@ -64,16 +65,17 @@ - - -

- {{ "notes" | i18n }} +

- diff --git a/apps/desktop/src/app/send/efflux-dates.component.html b/apps/desktop/src/app/send/efflux-dates.component.html index 37eacad9dc4..156dfae9ddd 100644 --- a/apps/desktop/src/app/send/efflux-dates.component.html +++ b/apps/desktop/src/app/send/efflux-dates.component.html @@ -6,12 +6,13 @@ - {{ "deletionDateDesc" | i18n }} + {{ "deletionDateDesc" | i18n }}
@@ -19,23 +20,27 @@ id="deletionDateCustom" type="datetime-local" name="deletionDate" + aria-describedby="deletionDateCustomHelp" formControlName="defaultDeletionDateTime" required placeholder="MM/DD/YYYY HH:MM AM/PM" /> - {{ "deletionDateDesc" | i18n }} + {{ + "deletionDateDesc" | i18n + }}
- {{ "expirationDateDesc" | i18n }} + {{ "expirationDateDesc" | i18n }}
@@ -43,11 +48,14 @@ id="expirationDateCustom" type="datetime-local" name="expirationDate" + aria-describedby="expirationDateCustomHelp" formControlName="defaultExpirationDateTime" required placeholder="MM/DD/YYYY HH:MM AM/PM" /> - {{ "expirationDateDesc" | i18n }} + {{ + "expirationDateDesc" | i18n + }}
diff --git a/apps/desktop/src/app/vault/attachments.component.html b/apps/desktop/src/app/vault/attachments.component.html index 0248d78b022..4931467b34a 100644 --- a/apps/desktop/src/app/vault/attachments.component.html +++ b/apps/desktop/src/app/vault/attachments.component.html @@ -45,10 +45,10 @@
- +
- diff --git a/apps/desktop/src/app/vault/export.component.html b/apps/desktop/src/app/vault/export.component.html index 229156c1f6a..8b3af4f1da3 100644 --- a/apps/desktop/src/app/vault/export.component.html +++ b/apps/desktop/src/app/vault/export.component.html @@ -24,7 +24,7 @@ - diff --git a/apps/desktop/src/app/vault/generator.component.html b/apps/desktop/src/app/vault/generator.component.html index 28d7b37afe7..4b8f16a91d8 100644 --- a/apps/desktop/src/app/vault/generator.component.html +++ b/apps/desktop/src/app/vault/generator.component.html @@ -341,7 +341,7 @@ /> diff --git a/apps/desktop/src/app/vault/share.component.html b/apps/desktop/src/app/vault/share.component.html index 9232d08df3b..a83cbb66298 100644 --- a/apps/desktop/src/app/vault/share.component.html +++ b/apps/desktop/src/app/vault/share.component.html @@ -18,6 +18,7 @@ - diff --git a/apps/desktop/src/scss/misc.scss b/apps/desktop/src/scss/misc.scss index 90988424b6f..c5aabb5b072 100644 --- a/apps/desktop/src/scss/misc.scss +++ b/apps/desktop/src/scss/misc.scss @@ -361,31 +361,31 @@ form, margin-left: -18px; } } +} - .help-block { - margin-top: 3px; - display: block; +.help-block { + margin-top: 3px; + display: block; + + @include themify($themes) { + color: themed("mutedColor"); + } + + a { + @extend .btn; + @extend .link; + + padding: 0; + font-size: inherit; + font-weight: bold; @include themify($themes) { color: themed("mutedColor"); } - a { - @extend .btn; - @extend .link; - - padding: 0; - font-size: inherit; - font-weight: bold; - + &:hover { @include themify($themes) { - color: themed("mutedColor"); - } - - &:hover { - @include themify($themes) { - color: darken(themed("mutedColor"), 6%); - } + color: darken(themed("mutedColor"), 6%); } } } From eeb407b8a438e9d01333d7d93de689e55812e6d9 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Wed, 21 Dec 2022 16:50:41 -0500 Subject: [PATCH 106/205] [SM-43] create product-switcher (#4189) * rebase to master * use bit-menu in product switcher; add focusStrategy to bit-menu * recommit locales after rebase * add light style to iconButton, use in product-switcher * move out of component library * add buttonType input * gate behind sm flag * update aria-label * add role input to bit-menu * style changes * simplify partition logic * split into two components for Storybook * update focus styles; update grid sizing to relative * fix underline on hover * update attribute binding * move to layouts dir * add bitLink; update grid gap * reorder loose components * move orgs mock * move a11y module * fix aria role bug; add aria label to menu * update colors * update ring color * simplify colors * remove duplicate link module --- .storybook/main.js | 6 + .../web/src/app/layouts/navbar.component.html | 1 + .../src/app/layouts/product-switcher/index.ts | 1 + .../product-switcher-content.component.html | 39 +++++ .../product-switcher-content.component.ts | 93 ++++++++++++ .../product-switcher.component.html | 9 ++ .../product-switcher.component.ts | 19 +++ .../product-switcher.module.ts | 18 +++ .../product-switcher.stories.ts | 134 ++++++++++++++++++ .../src/app/shared/loose-components.module.ts | 9 +- apps/web/src/app/shared/shared.module.ts | 4 +- apps/web/src/locales/en/messages.json | 6 + .../layout/filter.component.html | 1 - .../layout/filter.component.ts | 7 - .../layout/header.component.html | 2 +- .../shared/sm-shared.module.ts | 6 +- .../src/icon-button/icon-button.component.ts | 13 +- .../src/icon-button/icon-button.stories.ts | 11 +- .../src/menu/menu-trigger-for.directive.ts | 21 ++- libs/components/src/menu/menu.component.html | 5 +- libs/components/src/menu/menu.component.ts | 11 +- libs/components/src/menu/menu.module.ts | 3 +- 22 files changed, 387 insertions(+), 32 deletions(-) create mode 100644 apps/web/src/app/layouts/product-switcher/index.ts create mode 100644 apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html create mode 100644 apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts create mode 100644 apps/web/src/app/layouts/product-switcher/product-switcher.component.html create mode 100644 apps/web/src/app/layouts/product-switcher/product-switcher.component.ts create mode 100644 apps/web/src/app/layouts/product-switcher/product-switcher.module.ts create mode 100644 apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts delete mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/filter.component.html delete mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/layout/filter.component.ts diff --git a/.storybook/main.js b/.storybook/main.js index 83fe7fef3dd..3db3964022e 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -20,6 +20,12 @@ module.exports = { builder: "webpack5", disableTelemetry: true, }, + env: (config) => ({ + ...config, + FLAGS: JSON.stringify({ + secretsManager: true, + }), + }), webpackFinal: async (config, { configType }) => { config.resolve.plugins = [new TsconfigPathsPlugin()]; return config; diff --git a/apps/web/src/app/layouts/navbar.component.html b/apps/web/src/app/layouts/navbar.component.html index b7e5a69fb6b..0146a83af32 100644 --- a/apps/web/src/app/layouts/navbar.component.html +++ b/apps/web/src/app/layouts/navbar.component.html @@ -38,6 +38,7 @@ +

- {{ "premiumPrice" | i18n: (premiumPrice | currency: "$") }} + {{ "premiumPriceWithFamilyPlan" | i18n: (premiumPrice | currency: "$"):familyPlanMaxUserCount }} + {{ + "bitwardenFamiliesPlan" | i18n + }}

Date: Fri, 6 Jan 2023 10:55:48 -0800 Subject: [PATCH 139/205] Bumped web version to 2023.1.0 (#4407) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/package.json | 2 +- package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 21e19565c2e..6b03c068620 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2022.12.0", + "version": "2023.1.0", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index d76a2aa8585..63c42dd0777 100644 --- a/package-lock.json +++ b/package-lock.json @@ -230,7 +230,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2022.12.0" + "version": "2023.1.0" }, "libs/angular": { "name": "@bitwarden/angular", From 574c18ba3faf26165e568f55aa89d0a2d4480b74 Mon Sep 17 00:00:00 2001 From: Patrick Demers Date: Fri, 6 Jan 2023 15:11:34 -0600 Subject: [PATCH 140/205] browser - modify styling to remove scrolling glitch in virtual list (#4316) --- .../components/cipher-row.component.html | 86 ++++++++++--------- apps/browser/src/popup/scss/box.scss | 9 +- .../popup/vault/vault-filter.component.html | 2 +- .../popup/vault/vault-items.component.html | 2 +- 4 files changed, 52 insertions(+), 47 deletions(-) diff --git a/apps/browser/src/popup/components/cipher-row.component.html b/apps/browser/src/popup/components/cipher-row.component.html index ebb18bebe83..8ac9147cb92 100644 --- a/apps/browser/src/popup/components/cipher-row.component.html +++ b/apps/browser/src/popup/components/cipher-row.component.html @@ -1,49 +1,51 @@
- - - + {{ cipher.subTitle }} +
+ + + + diff --git a/apps/browser/src/popup/scss/box.scss b/apps/browser/src/popup/scss/box.scss index 7236adc035e..a14427357fc 100644 --- a/apps/browser/src/popup/scss/box.scss +++ b/apps/browser/src/popup/scss/box.scss @@ -115,11 +115,14 @@ &.list { margin: 10px 0 20px 0; .box-content { + .virtual-scroll-item { + display: inline-block; + width: 100%; + } + .box-content-row { - padding: 5px 10px; text-decoration: none; border-radius: $border-radius; - margin: 5px; // background-color: $background-color; @include themify($themes) { @@ -230,7 +233,7 @@ position: relative; z-index: 1; border-radius: $border-radius; - margin: 8px 5px; + margin: 3px 5px; @include themify($themes) { background-color: themed("boxBackgroundColor"); diff --git a/apps/browser/src/popup/vault/vault-filter.component.html b/apps/browser/src/popup/vault/vault-filter.component.html index 26689a5055d..8e459a26006 100644 --- a/apps/browser/src/popup/vault/vault-filter.component.html +++ b/apps/browser/src/popup/vault/vault-filter.component.html @@ -200,7 +200,7 @@

{{ "noItemsInList" | i18n }}

Date: Fri, 6 Jan 2023 19:31:32 -0500 Subject: [PATCH 141/205] [PS-1306] Context Menu for MV3 (#3910) * Add combine helper * Helper for running multiple actions with single service cache * Remove unneeded any * Send identifier through callback * Extend Tab Message * Split out ContextMenu logic * Add tests for ContextMenu actions * Context Menu Fixes * Await call to menu handler * set onUpdatedRan to false when it's ran * Switch to using new cache per run * Fix Generate Password Test * Remove old file from whitelist * Remove Useless never from Generic * Update apps/browser/src/background/main.background.ts Co-authored-by: Matt Gibson * Address PR Feedback * Specify a Document Url for Context Menu Items * Update Test * Use Generate Password Callback * Remove DocumentUrlPatterns Co-authored-by: Matt Gibson --- .github/whitelist-capital-letters.txt | 1 - apps/browser/src/background.ts | 32 +- .../src/background/contextMenus.background.ts | 136 +------- .../browser/src/background/main.background.ts | 318 +++--------------- .../cipher-service.factory.ts | 2 +- .../encrypt-service.factory.ts | 25 +- .../service_factories/factory-options.ts | 2 +- apps/browser/src/browser/browserApi.ts | 15 +- .../cipher-context-menu-handler.spec.ts | 109 ++++++ .../browser/cipher-context-menu-handler.ts | 185 ++++++++++ .../context-menu-clicked-handler.spec.ts | 193 +++++++++++ .../browser/context-menu-clicked-handler.ts | 239 +++++++++++++ .../browser/main-context-menu-handler.spec.ts | 137 ++++++++ .../src/browser/main-context-menu-handler.ts | 241 +++++++++++++ ...eTabCommand.ts => autofill-tab-command.ts} | 30 +- .../browser/src/content/contextMenuHandler.ts | 5 +- apps/browser/src/listeners/combine.spec.ts | 25 ++ apps/browser/src/listeners/combine.ts | 15 + apps/browser/src/listeners/index.ts | 40 +++ .../src/listeners/onCommandListener.ts | 6 +- .../src/listeners/onInstallListener.ts | 9 +- apps/browser/src/listeners/update-badge.ts | 34 +- apps/browser/src/types/tab-messages.ts | 11 +- apps/browser/test.setup.ts | 6 + .../common/src/abstractions/cipher.service.ts | 4 +- 25 files changed, 1360 insertions(+), 460 deletions(-) create mode 100644 apps/browser/src/browser/cipher-context-menu-handler.spec.ts create mode 100644 apps/browser/src/browser/cipher-context-menu-handler.ts create mode 100644 apps/browser/src/browser/context-menu-clicked-handler.spec.ts create mode 100644 apps/browser/src/browser/context-menu-clicked-handler.ts create mode 100644 apps/browser/src/browser/main-context-menu-handler.spec.ts create mode 100644 apps/browser/src/browser/main-context-menu-handler.ts rename apps/browser/src/commands/{autoFillActiveTabCommand.ts => autofill-tab-command.ts} (57%) create mode 100644 apps/browser/src/listeners/combine.spec.ts create mode 100644 apps/browser/src/listeners/combine.ts create mode 100644 apps/browser/src/listeners/index.ts diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index af82f6b811a..a6d2f96079f 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -207,7 +207,6 @@ ./apps/browser/src/safari/safari/SafariWebExtensionHandler.swift ./apps/browser/src/safari/safari/Info.plist ./apps/browser/src/safari/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist -./apps/browser/src/commands/autoFillActiveTabCommand.ts ./apps/browser/src/listeners/onCommandListener.ts ./apps/browser/src/listeners/onInstallListener.ts ./apps/browser/src/services/browserFileDownloadService.ts diff --git a/apps/browser/src/background.ts b/apps/browser/src/background.ts index fc020b19aa3..a6baca89c47 100644 --- a/apps/browser/src/background.ts +++ b/apps/browser/src/background.ts @@ -2,30 +2,26 @@ import { onAlarmListener } from "./alarms/on-alarm-listener"; import { registerAlarms } from "./alarms/register-alarms"; import MainBackground from "./background/main.background"; import { BrowserApi } from "./browser/browserApi"; -import { onCommandListener } from "./listeners/onCommandListener"; -import { onInstallListener } from "./listeners/onInstallListener"; -import { UpdateBadge } from "./listeners/update-badge"; - -const manifestV3MessageListeners: (( - serviceCache: Record, - message: { command: string } -) => void | Promise)[] = [UpdateBadge.messageListener]; +import { + contextMenusClickedListener, + onCommandListener, + onInstallListener, + runtimeMessageListener, + tabsOnActivatedListener, + tabsOnReplacedListener, + tabsOnUpdatedListener, +} from "./listeners"; if (BrowserApi.manifestVersion === 3) { chrome.commands.onCommand.addListener(onCommandListener); chrome.runtime.onInstalled.addListener(onInstallListener); chrome.alarms.onAlarm.addListener(onAlarmListener); registerAlarms(); - chrome.tabs.onActivated.addListener(UpdateBadge.tabsOnActivatedListener); - chrome.tabs.onReplaced.addListener(UpdateBadge.tabsOnReplacedListener); - chrome.tabs.onUpdated.addListener(UpdateBadge.tabsOnUpdatedListener); - BrowserApi.messageListener("runtime.background", (message) => { - const serviceCache = {}; - - manifestV3MessageListeners.forEach((listener) => { - listener(serviceCache, message); - }); - }); + chrome.tabs.onActivated.addListener(tabsOnActivatedListener); + chrome.tabs.onReplaced.addListener(tabsOnReplacedListener); + chrome.tabs.onUpdated.addListener(tabsOnUpdatedListener); + chrome.contextMenus.onClicked.addListener(contextMenusClickedListener); + BrowserApi.messageListener("runtime.background", runtimeMessageListener); } else { const bitwardenMain = ((window as any).bitwardenMain = new MainBackground()); bitwardenMain.bootstrap().then(() => { diff --git a/apps/browser/src/background/contextMenus.background.ts b/apps/browser/src/background/contextMenus.background.ts index 98223280a23..9a9ba0f5140 100644 --- a/apps/browser/src/background/contextMenus.background.ts +++ b/apps/browser/src/background/contextMenus.background.ts @@ -1,146 +1,38 @@ -import { AuthService } from "@bitwarden/common/abstractions/auth.service"; -import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; -import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { TotpService } from "@bitwarden/common/abstractions/totp.service"; -import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; -import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType"; -import { EventType } from "@bitwarden/common/enums/eventType"; -import { CipherView } from "@bitwarden/common/models/view/cipher.view"; - import { BrowserApi } from "../browser/browserApi"; +import { ContextMenuClickedHandler } from "../browser/context-menu-clicked-handler"; -import MainBackground from "./main.background"; import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem"; export default class ContextMenusBackground { - private readonly noopCommandSuffix = "noop"; - private contextMenus: any; + private contextMenus: typeof chrome.contextMenus; - constructor( - private main: MainBackground, - private cipherService: CipherService, - private passwordGenerationService: PasswordGenerationService, - private platformUtilsService: PlatformUtilsService, - private authService: AuthService, - private eventCollectionService: EventCollectionService, - private totpService: TotpService - ) { + constructor(private contextMenuClickedHandler: ContextMenuClickedHandler) { this.contextMenus = chrome.contextMenus; } - async init() { + init() { if (!this.contextMenus) { return; } - this.contextMenus.onClicked.addListener( - async (info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => { - if (info.menuItemId === "generate-password") { - await this.generatePasswordToClipboard(); - } else if (info.menuItemId === "copy-identifier") { - await this.getClickedElement(tab, info.frameId); - } else if ( - info.parentMenuItemId === "autofill" || - info.parentMenuItemId === "copy-username" || - info.parentMenuItemId === "copy-password" || - info.parentMenuItemId === "copy-totp" - ) { - await this.cipherAction(tab, info); - } - } + this.contextMenus.onClicked.addListener((info, tab) => + this.contextMenuClickedHandler.run(info, tab) ); BrowserApi.messageListener( "contextmenus.background", - async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => { + async ( + msg: { command: string; data: LockedVaultPendingNotificationsItem }, + sender: chrome.runtime.MessageSender, + sendResponse: any + ) => { if (msg.command === "unlockCompleted" && msg.data.target === "contextmenus.background") { - await this.cipherAction( - msg.data.commandToRetry.sender.tab, - msg.data.commandToRetry.msg.data + await this.contextMenuClickedHandler.cipherAction( + msg.data.commandToRetry.msg.data, + msg.data.commandToRetry.sender.tab ); } } ); } - - private async generatePasswordToClipboard() { - const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {}; - const password = await this.passwordGenerationService.generatePassword(options); - this.platformUtilsService.copyToClipboard(password, { window: window }); - this.passwordGenerationService.addHistory(password); - } - - private async getClickedElement(tab: chrome.tabs.Tab, frameId: number) { - if (tab == null) { - return; - } - - BrowserApi.tabSendMessage(tab, { command: "getClickedElement" }, { frameId: frameId }); - } - - private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) { - if (typeof info.menuItemId !== "string") { - return; - } - - const id = info.menuItemId.split("_")[1]; - - if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) { - const retryMessage: LockedVaultPendingNotificationsItem = { - commandToRetry: { - msg: { command: this.noopCommandSuffix, data: info }, - sender: { tab: tab }, - }, - target: "contextmenus.background", - }; - await BrowserApi.tabSendMessageData( - tab, - "addToLockedVaultPendingNotifications", - retryMessage - ); - - BrowserApi.tabSendMessageData(tab, "promptForLogin"); - return; - } - - let cipher: CipherView; - if (id === this.noopCommandSuffix) { - const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url); - cipher = ciphers.find((c) => c.reprompt === CipherRepromptType.None); - } else { - const ciphers = await this.cipherService.getAllDecrypted(); - cipher = ciphers.find((c) => c.id === id); - } - - if (cipher == null) { - return; - } - - if (info.parentMenuItemId === "autofill") { - await this.startAutofillPage(tab, cipher); - } else if (info.parentMenuItemId === "copy-username") { - this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window }); - } else if (info.parentMenuItemId === "copy-password") { - this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window }); - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); - } else if (info.parentMenuItemId === "copy-totp") { - const totpValue = await this.totpService.getCode(cipher.login.totp); - this.platformUtilsService.copyToClipboard(totpValue, { window: window }); - } - } - - private async startAutofillPage(tab: chrome.tabs.Tab, cipher: CipherView) { - this.main.loginToAutoFill = cipher; - if (tab == null) { - return; - } - - BrowserApi.tabSendMessage(tab, { - command: "collectPageDetails", - tab: tab, - sender: "contextMenu", - }); - } } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 4ef078bde5b..aea7f9600d9 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -40,9 +40,6 @@ import { UserVerificationService as UserVerificationServiceAbstraction } from "@ import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@bitwarden/common/abstractions/usernameGeneration.service"; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service"; -import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; -import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType"; -import { CipherType } from "@bitwarden/common/enums/cipherType"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { GlobalState } from "@bitwarden/common/models/domain/global-state"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; @@ -84,7 +81,11 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTim import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service"; import { BrowserApi } from "../browser/browserApi"; +import { CipherContextMenuHandler } from "../browser/cipher-context-menu-handler"; +import { ContextMenuClickedHandler } from "../browser/context-menu-clicked-handler"; +import { MainContextMenuHandler } from "../browser/main-context-menu-handler"; import { SafariApp } from "../browser/safariApp"; +import { AutofillTabCommand } from "../commands/autofill-tab-command"; import { flagEnabled } from "../flags"; import { UpdateBadge } from "../listeners/update-badge"; import { Account } from "../models/account"; @@ -112,7 +113,6 @@ import VaultTimeoutService from "../services/vaultTimeout/vaultTimeout.service"; import CommandsBackground from "./commands.background"; import ContextMenusBackground from "./contextMenus.background"; import IdleBackground from "./idle.background"; -import IconDetails from "./models/iconDetails"; import { NativeMessagingBackground } from "./nativeMessaging.background"; import NotificationBackground from "./notification.background"; import RuntimeBackground from "./runtime.background"; @@ -171,6 +171,8 @@ export default class MainBackground { userVerificationApiService: UserVerificationApiServiceAbstraction; syncNotifierService: SyncNotifierServiceAbstraction; avatarUpdateService: AvatarUpdateServiceAbstraction; + mainContextMenuHandler: MainContextMenuHandler; + cipherContextMenuHandler: CipherContextMenuHandler; // Passed to the popup for Safari to workaround issues with theming, downloading, etc. backgroundWindow = window; @@ -188,8 +190,6 @@ export default class MainBackground { private webRequestBackground: WebRequestBackground; private sidebarAction: any; - private buildingContextMenu: boolean; - private menuOptionsLoaded: any[] = []; private syncTimeout: any; private isSafari: boolean; private nativeMessagingBackground: NativeMessagingBackground; @@ -536,15 +536,25 @@ export default class MainBackground { ); this.tabsBackground = new TabsBackground(this, this.notificationBackground); - this.contextMenusBackground = new ContextMenusBackground( - this, - this.cipherService, - this.passwordGenerationService, - this.platformUtilsService, - this.authService, - this.eventCollectionService, - this.totpService - ); + if (!this.popupOnlyContext) { + const contextMenuClickedHandler = new ContextMenuClickedHandler( + (options) => this.platformUtilsService.copyToClipboard(options.text, { window: self }), + async (_tab) => { + const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {}; + const password = await this.passwordGenerationService.generatePassword(options); + this.platformUtilsService.copyToClipboard(password, { window: window }); + this.passwordGenerationService.addHistory(password); + }, + this.authService, + this.cipherService, + new AutofillTabCommand(this.autofillService), + this.totpService, + this.eventCollectionService + ); + + this.contextMenusBackground = new ContextMenusBackground(contextMenuClickedHandler); + } + this.idleBackground = new IdleBackground( this.vaultTimeoutService, this.stateService, @@ -563,6 +573,16 @@ export default class MainBackground { ); this.avatarUpdateService = new AvatarUpdateService(this.apiService, this.stateService); + + if (!this.popupOnlyContext) { + this.mainContextMenuHandler = new MainContextMenuHandler(this.stateService, this.i18nService); + + this.cipherContextMenuHandler = new CipherContextMenuHandler( + this.mainContextMenuHandler, + this.authService, + this.cipherService + ); + } } async bootstrap() { @@ -580,7 +600,9 @@ export default class MainBackground { this.twoFactorService.init(); await this.tabsBackground.init(); - await this.contextMenusBackground.init(); + if (!this.popupOnlyContext) { + this.contextMenusBackground?.init(); + } await this.idleBackground.init(); await this.webRequestBackground.init(); @@ -620,22 +642,20 @@ export default class MainBackground { return; } - const menuDisabled = await this.stateService.getDisableContextMenuItem(); - if (!menuDisabled) { - await this.buildContextMenu(); - } else { - await this.contextMenusRemoveAll(); - } + await MainContextMenuHandler.removeAll(); if (forLocked) { - await this.loadMenuForNoAccessState(!menuDisabled); + await this.mainContextMenuHandler?.noAccess(); this.onUpdatedRan = this.onReplacedRan = false; return; } + await this.mainContextMenuHandler?.init(); + const tab = await BrowserApi.getTabFromCurrentWindow(); if (tab) { - await this.contextMenuReady(tab, !menuDisabled); + await this.cipherContextMenuHandler?.update(tab.url); + this.onUpdatedRan = this.onReplacedRan = false; } } @@ -667,7 +687,7 @@ export default class MainBackground { BrowserApi.sendMessage("updateBadge"); } await this.refreshBadge(); - await this.refreshMenu(true); + await this.mainContextMenuHandler.noAccess(); await this.reseedStorage(); this.notificationsService.updateConnection(false); await this.systemService.clearPendingClipboard(); @@ -741,204 +761,6 @@ export default class MainBackground { } } - private async buildContextMenu() { - if (!chrome.contextMenus || this.buildingContextMenu) { - return; - } - - this.buildingContextMenu = true; - await this.contextMenusRemoveAll(); - - await this.contextMenusCreate({ - type: "normal", - id: "root", - contexts: ["all"], - title: "Bitwarden", - }); - - await this.contextMenusCreate({ - type: "normal", - id: "autofill", - parentId: "root", - contexts: ["all"], - title: this.i18nService.t("autoFill"), - }); - - await this.contextMenusCreate({ - type: "normal", - id: "copy-username", - parentId: "root", - contexts: ["all"], - title: this.i18nService.t("copyUsername"), - }); - - await this.contextMenusCreate({ - type: "normal", - id: "copy-password", - parentId: "root", - contexts: ["all"], - title: this.i18nService.t("copyPassword"), - }); - - if (await this.stateService.getCanAccessPremium()) { - await this.contextMenusCreate({ - type: "normal", - id: "copy-totp", - parentId: "root", - contexts: ["all"], - title: this.i18nService.t("copyVerificationCode"), - }); - } - - await this.contextMenusCreate({ - type: "separator", - parentId: "root", - }); - - await this.contextMenusCreate({ - type: "normal", - id: "generate-password", - parentId: "root", - contexts: ["all"], - title: this.i18nService.t("generatePasswordCopied"), - }); - - await this.contextMenusCreate({ - type: "normal", - id: "copy-identifier", - parentId: "root", - contexts: ["all"], - title: this.i18nService.t("copyElementIdentifier"), - }); - - this.buildingContextMenu = false; - } - - private async contextMenuReady(tab: any, contextMenuEnabled: boolean) { - await this.loadMenu(tab.url, tab.id, contextMenuEnabled); - this.onUpdatedRan = this.onReplacedRan = false; - } - - private async loadMenu(url: string, tabId: number, contextMenuEnabled: boolean) { - if (!url || (!chrome.browserAction && !this.sidebarAction)) { - return; - } - - this.menuOptionsLoaded = []; - const authStatus = await this.authService.getAuthStatus(); - if (authStatus === AuthenticationStatus.Unlocked) { - try { - const ciphers = await this.cipherService.getAllDecryptedForUrl(url); - ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); - - if (contextMenuEnabled) { - ciphers.forEach((cipher) => { - this.loadLoginContextMenuOptions(cipher); - }); - } - - if (contextMenuEnabled && ciphers.length === 0) { - await this.loadNoLoginsContextMenuOptions(this.i18nService.t("noMatchingLogins")); - } - - return; - } catch (e) { - this.logService.error(e); - } - } - - await this.loadMenuForNoAccessState(contextMenuEnabled); - } - - private async loadMenuForNoAccessState(contextMenuEnabled: boolean) { - if (contextMenuEnabled) { - const authed = await this.stateService.getIsAuthenticated(); - await this.loadNoLoginsContextMenuOptions( - this.i18nService.t(authed ? "unlockVaultMenu" : "loginToVaultMenu") - ); - } - } - - private async loadLoginContextMenuOptions(cipher: any) { - if ( - cipher == null || - cipher.type !== CipherType.Login || - cipher.reprompt !== CipherRepromptType.None - ) { - return; - } - - let title = cipher.name; - if (cipher.login.username && cipher.login.username !== "") { - title += " (" + cipher.login.username + ")"; - } - await this.loadContextMenuOptions(title, cipher.id, cipher); - } - - private async loadNoLoginsContextMenuOptions(noLoginsMessage: string) { - await this.loadContextMenuOptions(noLoginsMessage, "noop", null); - } - - private async loadContextMenuOptions(title: string, idSuffix: string, cipher: any) { - if ( - !chrome.contextMenus || - this.menuOptionsLoaded.indexOf(idSuffix) > -1 || - (cipher != null && cipher.type !== CipherType.Login) - ) { - return; - } - - this.menuOptionsLoaded.push(idSuffix); - - if (cipher == null || (cipher.login.password && cipher.login.password !== "")) { - await this.contextMenusCreate({ - type: "normal", - id: "autofill_" + idSuffix, - parentId: "autofill", - contexts: ["all"], - title: this.sanitizeContextMenuTitle(title), - }); - } - - if (cipher == null || (cipher.login.username && cipher.login.username !== "")) { - await this.contextMenusCreate({ - type: "normal", - id: "copy-username_" + idSuffix, - parentId: "copy-username", - contexts: ["all"], - title: this.sanitizeContextMenuTitle(title), - }); - } - - if ( - cipher == null || - (cipher.login.password && cipher.login.password !== "" && cipher.viewPassword) - ) { - await this.contextMenusCreate({ - type: "normal", - id: "copy-password_" + idSuffix, - parentId: "copy-password", - contexts: ["all"], - title: this.sanitizeContextMenuTitle(title), - }); - } - - const canAccessPremium = await this.stateService.getCanAccessPremium(); - if (canAccessPremium && (cipher == null || (cipher.login.totp && cipher.login.totp !== ""))) { - await this.contextMenusCreate({ - type: "normal", - id: "copy-totp_" + idSuffix, - parentId: "copy-totp", - contexts: ["all"], - title: this.sanitizeContextMenuTitle(title), - }); - } - } - - private sanitizeContextMenuTitle(title: string): string { - return title.replace(/&/g, "&&"); - } - private async fullSync(override = false) { const syncInternal = 6 * 60 * 60 * 1000; // 6 hours const lastSync = await this.syncService.getLastSync(); @@ -963,54 +785,4 @@ export default class MainBackground { this.syncTimeout = setTimeout(async () => await this.fullSync(), 5 * 60 * 1000); // check every 5 minutes } - - // Browser API Helpers - - private contextMenusRemoveAll() { - return new Promise((resolve) => { - chrome.contextMenus.removeAll(() => { - resolve(); - if (chrome.runtime.lastError) { - return; - } - }); - }); - } - - private contextMenusCreate(options: any) { - return new Promise((resolve) => { - chrome.contextMenus.create(options, () => { - resolve(); - if (chrome.runtime.lastError) { - return; - } - }); - }); - } - - private async actionSetIcon(theAction: any, suffix: string, windowId?: number): Promise { - if (!theAction || !theAction.setIcon) { - return; - } - - const options: IconDetails = { - path: { - 19: "images/icon19" + suffix + ".png", - 38: "images/icon38" + suffix + ".png", - }, - }; - - if (this.platformUtilsService.isFirefox()) { - options.windowId = windowId; - await theAction.setIcon(options); - } else if (this.platformUtilsService.isSafari()) { - // Workaround since Safari 14.0.3 returns a pending promise - // which doesn't resolve within a reasonable time. - theAction.setIcon(options); - } else { - return new Promise((resolve) => { - theAction.setIcon(options, () => resolve()); - }); - } - } } 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 6d31ebd76f7..020c19983c2 100644 --- a/apps/browser/src/background/service_factories/cipher-service.factory.ts +++ b/apps/browser/src/background/service_factories/cipher-service.factory.ts @@ -47,7 +47,7 @@ export function cipherServiceFactory( await fileUploadServiceFactory(cache, opts), await i18nServiceFactory(cache, opts), opts.cipherServiceOptions?.searchServiceFactory === undefined - ? () => cache.searchService + ? () => cache.searchService as SearchService : opts.cipherServiceOptions.searchServiceFactory, await logServiceFactory(cache, opts), await stateServiceFactory(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 6e4c104b05b..5b2a3766a3f 100644 --- a/apps/browser/src/background/service_factories/encrypt-service.factory.ts +++ b/apps/browser/src/background/service_factories/encrypt-service.factory.ts @@ -1,7 +1,4 @@ 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, @@ -24,17 +21,15 @@ export function encryptServiceFactory( cache: { encryptService?: EncryptServiceImplementation } & CachedServices, opts: EncryptServiceInitOptions ): 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 - ) + return factory( + cache, + "encryptService", + opts, + async () => + new EncryptServiceImplementation( + await cryptoFunctionServiceFactory(cache, opts), + await logServiceFactory(cache, opts), + opts.encryptServiceOptions.logMacFailures + ) ); } diff --git a/apps/browser/src/background/service_factories/factory-options.ts b/apps/browser/src/background/service_factories/factory-options.ts index 12129e4e673..7dde09204ef 100644 --- a/apps/browser/src/background/service_factories/factory-options.ts +++ b/apps/browser/src/background/service_factories/factory-options.ts @@ -1,4 +1,4 @@ -export type CachedServices = Record; +export type CachedServices = Record; export type FactoryOptions = { alwaysInitializeNewService?: boolean; diff --git a/apps/browser/src/browser/browserApi.ts b/apps/browser/src/browser/browserApi.ts index d64231833df..60666d23fea 100644 --- a/apps/browser/src/browser/browserApi.ts +++ b/apps/browser/src/browser/browserApi.ts @@ -44,7 +44,7 @@ export class BrowserApi { static async tabsQuery(options: chrome.tabs.QueryInfo): Promise { return new Promise((resolve) => { - chrome.tabs.query(options, (tabs: any[]) => { + chrome.tabs.query(options, (tabs) => { resolve(tabs); }); }); @@ -63,7 +63,7 @@ export class BrowserApi { tab: chrome.tabs.Tab, command: string, data: any = null - ): Promise { + ): Promise { const obj: any = { command: command, }; @@ -75,11 +75,11 @@ export class BrowserApi { return BrowserApi.tabSendMessage(tab, obj); } - static async tabSendMessage( + static async tabSendMessage( tab: chrome.tabs.Tab, - obj: any, + obj: T, options: chrome.tabs.MessageSendOptions = null - ): Promise { + ): Promise { if (!tab || !tab.id) { return; } @@ -94,12 +94,13 @@ export class BrowserApi { }); } - static sendTabsMessage( + static sendTabsMessage( tabId: number, message: TabMessage, + options?: chrome.tabs.MessageSendOptions, responseCallback?: (response: T) => void ) { - chrome.tabs.sendMessage(tabId, message, responseCallback); + chrome.tabs.sendMessage(tabId, message, options, responseCallback); } static async getPrivateModeWindows(): Promise { diff --git a/apps/browser/src/browser/cipher-context-menu-handler.spec.ts b/apps/browser/src/browser/cipher-context-menu-handler.spec.ts new file mode 100644 index 00000000000..39b7f8e3619 --- /dev/null +++ b/apps/browser/src/browser/cipher-context-menu-handler.spec.ts @@ -0,0 +1,109 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { AuthService } from "@bitwarden/common/abstractions/auth.service"; +import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; +import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; +import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType"; +import { CipherType } from "@bitwarden/common/enums/cipherType"; + +import { CipherContextMenuHandler } from "./cipher-context-menu-handler"; +import { MainContextMenuHandler } from "./main-context-menu-handler"; + +describe("CipherContextMenuHandler", () => { + let mainContextMenuHandler: MockProxy; + let authService: MockProxy; + let cipherService: MockProxy; + + let sut: CipherContextMenuHandler; + + beforeEach(() => { + mainContextMenuHandler = mock(); + authService = mock(); + cipherService = mock(); + + jest.spyOn(MainContextMenuHandler, "removeAll").mockResolvedValue(); + + sut = new CipherContextMenuHandler(mainContextMenuHandler, authService, cipherService); + }); + + afterEach(() => jest.resetAllMocks()); + + describe("update", () => { + it("locked, updates for no access", async () => { + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Locked); + + await sut.update("https://test.com"); + + expect(mainContextMenuHandler.noAccess).toHaveBeenCalledTimes(1); + }); + + it("logged out, updates for no access", async () => { + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.LoggedOut); + + await sut.update("https://test.com"); + + expect(mainContextMenuHandler.noAccess).toHaveBeenCalledTimes(1); + }); + + it("has menu disabled, does not load anything", async () => { + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Unlocked); + + await sut.update("https://test.com"); + + expect(mainContextMenuHandler.loadOptions).not.toHaveBeenCalled(); + + expect(mainContextMenuHandler.noAccess).not.toHaveBeenCalled(); + + expect(mainContextMenuHandler.noLogins).not.toHaveBeenCalled(); + }); + + it("has no ciphers, add no ciphers item", async () => { + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Unlocked); + + mainContextMenuHandler.init.mockResolvedValue(true); + + cipherService.getAllDecryptedForUrl.mockResolvedValue([]); + + await sut.update("https://test.com"); + + expect(mainContextMenuHandler.noLogins).toHaveBeenCalledTimes(1); + }); + + it("only adds valid ciphers", async () => { + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Unlocked); + + mainContextMenuHandler.init.mockResolvedValue(true); + + const realCipher = { + id: "5", + type: CipherType.Login, + reprompt: CipherRepromptType.None, + name: "Test Cipher", + login: { username: "Test Username" }, + }; + + cipherService.getAllDecryptedForUrl.mockResolvedValue([ + null, + undefined, + { type: CipherType.Card }, + { type: CipherType.Login, reprompt: CipherRepromptType.Password }, + realCipher, + ] as any[]); + + await sut.update("https://test.com"); + + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1); + + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com"); + + expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(1); + + expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledWith( + "Test Cipher (Test Username)", + "5", + "https://test.com", + realCipher + ); + }); + }); +}); diff --git a/apps/browser/src/browser/cipher-context-menu-handler.ts b/apps/browser/src/browser/cipher-context-menu-handler.ts new file mode 100644 index 00000000000..ea87f8da93e --- /dev/null +++ b/apps/browser/src/browser/cipher-context-menu-handler.ts @@ -0,0 +1,185 @@ +import { AuthService } from "@bitwarden/common/abstractions/auth.service"; +import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; +import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; +import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType"; +import { CipherType } from "@bitwarden/common/enums/cipherType"; +import { StateFactory } from "@bitwarden/common/factories/stateFactory"; +import { Utils } from "@bitwarden/common/misc/utils"; +import { GlobalState } from "@bitwarden/common/models/domain/global-state"; +import { CipherView } from "@bitwarden/common/models/view/cipher.view"; + +import { + authServiceFactory, + AuthServiceInitOptions, +} from "../background/service_factories/auth-service.factory"; +import { + cipherServiceFactory, + CipherServiceInitOptions, +} from "../background/service_factories/cipher-service.factory"; +import { CachedServices } from "../background/service_factories/factory-options"; +import { searchServiceFactory } from "../background/service_factories/search-service.factory"; +import { Account } from "../models/account"; + +import { BrowserApi } from "./browserApi"; +import { MainContextMenuHandler } from "./main-context-menu-handler"; + +const NOT_IMPLEMENTED = (..._args: unknown[]) => Promise.resolve(); + +const LISTENED_TO_COMMANDS = [ + "loggedIn", + "unlocked", + "syncCompleted", + "bgUpdateContextMenu", + "editedCipher", + "addedCipher", + "deletedCipher", +]; + +export class CipherContextMenuHandler { + constructor( + private mainContextMenuHandler: MainContextMenuHandler, + private authService: AuthService, + private cipherService: CipherService + ) {} + + static async create(cachedServices: CachedServices) { + const stateFactory = new StateFactory(GlobalState, Account); + let searchService: SearchService | null = null; + const serviceOptions: AuthServiceInitOptions & CipherServiceInitOptions = { + apiServiceOptions: { + logoutCallback: NOT_IMPLEMENTED, + }, + cipherServiceOptions: { + searchServiceFactory: () => searchService, + }, + cryptoFunctionServiceOptions: { + win: self, + }, + encryptServiceOptions: { + logMacFailures: false, + }, + i18nServiceOptions: { + systemLanguage: chrome.i18n.getUILanguage(), + }, + keyConnectorServiceOptions: { + logoutCallback: NOT_IMPLEMENTED, + }, + logServiceOptions: { + isDev: false, + }, + platformUtilsServiceOptions: { + biometricCallback: () => Promise.resolve(false), + clipboardWriteCallback: NOT_IMPLEMENTED, + win: self, + }, + stateMigrationServiceOptions: { + stateFactory: stateFactory, + }, + stateServiceOptions: { + stateFactory: stateFactory, + }, + }; + searchService = await searchServiceFactory(cachedServices, serviceOptions); + return new CipherContextMenuHandler( + await MainContextMenuHandler.mv3Create(cachedServices), + await authServiceFactory(cachedServices, serviceOptions), + await cipherServiceFactory(cachedServices, serviceOptions) + ); + } + + static async tabsOnActivatedListener( + activeInfo: chrome.tabs.TabActiveInfo, + serviceCache: CachedServices + ) { + const cipherContextMenuHandler = await CipherContextMenuHandler.create(serviceCache); + const tab = await BrowserApi.getTab(activeInfo.tabId); + await cipherContextMenuHandler.update(tab.url); + } + + static async tabsOnReplacedListener( + addedTabId: number, + removedTabId: number, + serviceCache: CachedServices + ) { + const cipherContextMenuHandler = await CipherContextMenuHandler.create(serviceCache); + const tab = await BrowserApi.getTab(addedTabId); + await cipherContextMenuHandler.update(tab.url); + } + + static async tabsOnUpdatedListener( + tabId: number, + changeInfo: chrome.tabs.TabChangeInfo, + tab: chrome.tabs.Tab, + serviceCache: CachedServices + ) { + if (changeInfo.status !== "complete") { + return; + } + const cipherContextMenuHandler = await CipherContextMenuHandler.create(serviceCache); + await cipherContextMenuHandler.update(tab.url); + } + + static async messageListener(message: { command: string }, cachedServices: CachedServices) { + const cipherContextMenuHandler = await CipherContextMenuHandler.create(cachedServices); + await cipherContextMenuHandler.messageListener(message); + } + + async messageListener(message: { command: string }) { + if (!LISTENED_TO_COMMANDS.includes(message.command)) { + return; + } + + const activeTabs = await BrowserApi.getActiveTabs(); + if (!activeTabs || activeTabs.length === 0) { + return; + } + + await this.update(activeTabs[0].url); + } + + async update(url: string) { + const authStatus = await this.authService.getAuthStatus(); + await MainContextMenuHandler.removeAll(); + if (authStatus !== AuthenticationStatus.Unlocked) { + // Should I pass in the auth status or even have two seperate methods for this + // on MainContextMenuHandler + await this.mainContextMenuHandler.noAccess(); + return; + } + + const menuEnabled = await this.mainContextMenuHandler.init(); + if (!menuEnabled) { + return; + } + + const ciphers = await this.cipherService.getAllDecryptedForUrl(url); + ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); + + if (ciphers.length === 0) { + await this.mainContextMenuHandler.noLogins(url); + return; + } + + for (const cipher of ciphers) { + await this.updateForCipher(url, cipher); + } + } + + private async updateForCipher(url: string, cipher: CipherView) { + if ( + cipher == null || + cipher.type !== CipherType.Login || + cipher.reprompt !== CipherRepromptType.None + ) { + return; + } + + let title = cipher.name; + if (!Utils.isNullOrEmpty(title)) { + title += ` (${cipher.login.username})`; + } + + await this.mainContextMenuHandler.loadOptions(title, cipher.id, url, cipher); + } +} diff --git a/apps/browser/src/browser/context-menu-clicked-handler.spec.ts b/apps/browser/src/browser/context-menu-clicked-handler.spec.ts new file mode 100644 index 00000000000..a4d84aad126 --- /dev/null +++ b/apps/browser/src/browser/context-menu-clicked-handler.spec.ts @@ -0,0 +1,193 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { AuthService } from "@bitwarden/common/abstractions/auth.service"; +import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { TotpService } from "@bitwarden/common/abstractions/totp.service"; +import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType"; +import { CipherType } from "@bitwarden/common/enums/cipherType"; +import { Cipher } from "@bitwarden/common/models/domain/cipher"; +import { CipherView } from "@bitwarden/common/models/view/cipher.view"; + +import { AutofillTabCommand } from "../commands/autofill-tab-command"; + +import { + CopyToClipboardAction, + ContextMenuClickedHandler, + CopyToClipboardOptions, + GeneratePasswordToClipboardAction, +} from "./context-menu-clicked-handler"; +import { + AUTOFILL_ID, + COPY_PASSWORD_ID, + COPY_USERNAME_ID, + COPY_VERIFICATIONCODE_ID, + GENERATE_PASSWORD_ID, +} from "./main-context-menu-handler"; + +describe("ContextMenuClickedHandler", () => { + const createData = ( + menuItemId: chrome.contextMenus.OnClickData["menuItemId"], + parentMenuItemId?: chrome.contextMenus.OnClickData["parentMenuItemId"] + ): chrome.contextMenus.OnClickData => { + return { + menuItemId: menuItemId, + parentMenuItemId: parentMenuItemId, + editable: false, + pageUrl: "something", + }; + }; + + const createCipher = (data?: { + id?: CipherView["id"]; + username?: CipherView["login"]["username"]; + password?: CipherView["login"]["password"]; + totp?: CipherView["login"]["totp"]; + }): CipherView => { + const { id, username, password, totp } = data || {}; + const cipherView = new CipherView( + new Cipher({ + id: id ?? "1", + type: CipherType.Login, + } as any) + ); + cipherView.login.username = username ?? "USERNAME"; + cipherView.login.password = password ?? "PASSWORD"; + cipherView.login.totp = totp ?? "TOTP"; + return cipherView; + }; + + let copyToClipboard: CopyToClipboardAction; + let generatePasswordToClipboard: GeneratePasswordToClipboardAction; + let authService: MockProxy; + let cipherService: MockProxy; + let autofillTabCommand: MockProxy; + let totpService: MockProxy; + let eventCollectionService: MockProxy; + + let sut: ContextMenuClickedHandler; + + beforeEach(() => { + copyToClipboard = jest.fn(); + generatePasswordToClipboard = jest.fn, [tab: chrome.tabs.Tab]>(); + authService = mock(); + cipherService = mock(); + autofillTabCommand = mock(); + totpService = mock(); + eventCollectionService = mock(); + + sut = new ContextMenuClickedHandler( + copyToClipboard, + generatePasswordToClipboard, + authService, + cipherService, + autofillTabCommand, + totpService, + eventCollectionService + ); + }); + + afterEach(() => jest.resetAllMocks()); + + describe("run", () => { + it("can generate password", async () => { + await sut.run(createData(GENERATE_PASSWORD_ID), { id: 5 } as any); + + expect(generatePasswordToClipboard).toBeCalledTimes(1); + + expect(generatePasswordToClipboard).toBeCalledWith({ + id: 5, + }); + }); + + it("attempts to autofill the correct cipher", async () => { + const cipher = createCipher(); + cipherService.getAllDecrypted.mockResolvedValue([cipher]); + + await sut.run(createData("T_1", AUTOFILL_ID), { id: 5 } as any); + + expect(autofillTabCommand.doAutofillTabWithCipherCommand).toBeCalledTimes(1); + + expect(autofillTabCommand.doAutofillTabWithCipherCommand).toBeCalledWith({ id: 5 }, cipher); + }); + + it("copies username to clipboard", async () => { + cipherService.getAllDecrypted.mockResolvedValue([ + createCipher({ username: "TEST_USERNAME" }), + ]); + + await sut.run(createData("T_1", COPY_USERNAME_ID)); + + expect(copyToClipboard).toBeCalledTimes(1); + + expect(copyToClipboard).toHaveBeenCalledWith({ text: "TEST_USERNAME", options: undefined }); + }); + + it("copies password to clipboard", async () => { + cipherService.getAllDecrypted.mockResolvedValue([ + createCipher({ password: "TEST_PASSWORD" }), + ]); + + await sut.run(createData("T_1", COPY_PASSWORD_ID)); + + expect(copyToClipboard).toBeCalledTimes(1); + + expect(copyToClipboard).toHaveBeenCalledWith({ text: "TEST_PASSWORD", options: undefined }); + }); + + it("copies totp code to clipboard", async () => { + cipherService.getAllDecrypted.mockResolvedValue([createCipher({ totp: "TEST_TOTP_SEED" })]); + + totpService.getCode.mockImplementation((seed) => { + if (seed === "TEST_TOTP_SEED") { + return Promise.resolve("123456"); + } + + return Promise.resolve("654321"); + }); + + await sut.run(createData("T_1", COPY_VERIFICATIONCODE_ID)); + + expect(totpService.getCode).toHaveBeenCalledTimes(1); + + expect(copyToClipboard).toHaveBeenCalledWith({ text: "123456" }); + }); + + it("attempts to find a cipher when noop but unlocked", async () => { + cipherService.getAllDecryptedForUrl.mockResolvedValue([ + { + ...createCipher({ username: "NOOP_USERNAME" }), + reprompt: CipherRepromptType.None, + } as any, + ]); + + await sut.run(createData("T_noop", COPY_USERNAME_ID), { url: "https://test.com" } as any); + + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1); + + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com"); + + expect(copyToClipboard).toHaveBeenCalledTimes(1); + + expect(copyToClipboard).toHaveBeenCalledWith({ + text: "NOOP_USERNAME", + tab: { url: "https://test.com" }, + }); + }); + + it("attempts to find a cipher when noop but unlocked", async () => { + cipherService.getAllDecryptedForUrl.mockResolvedValue([ + { + ...createCipher({ username: "NOOP_USERNAME" }), + reprompt: CipherRepromptType.Password, + } as any, + ]); + + await sut.run(createData("T_noop", COPY_USERNAME_ID), { url: "https://test.com" } as any); + + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1); + + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com"); + }); + }); +}); diff --git a/apps/browser/src/browser/context-menu-clicked-handler.ts b/apps/browser/src/browser/context-menu-clicked-handler.ts new file mode 100644 index 00000000000..c11c6dbb94f --- /dev/null +++ b/apps/browser/src/browser/context-menu-clicked-handler.ts @@ -0,0 +1,239 @@ +import { AuthService } from "@bitwarden/common/abstractions/auth.service"; +import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { TotpService } from "@bitwarden/common/abstractions/totp.service"; +import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; +import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType"; +import { EventType } from "@bitwarden/common/enums/eventType"; +import { StateFactory } from "@bitwarden/common/factories/stateFactory"; +import { GlobalState } from "@bitwarden/common/models/domain/global-state"; +import { CipherView } from "@bitwarden/common/models/view/cipher.view"; + +import LockedVaultPendingNotificationsItem from "../background/models/lockedVaultPendingNotificationsItem"; +import { + authServiceFactory, + AuthServiceInitOptions, +} from "../background/service_factories/auth-service.factory"; +import { autofillServiceFactory } from "../background/service_factories/autofill-service.factory"; +import { + cipherServiceFactory, + CipherServiceInitOptions, +} from "../background/service_factories/cipher-service.factory"; +import { eventCollectionServiceFactory } from "../background/service_factories/event-collection-service.factory"; +import { CachedServices } from "../background/service_factories/factory-options"; +import { passwordGenerationServiceFactory } from "../background/service_factories/password-generation-service.factory"; +import { searchServiceFactory } from "../background/service_factories/search-service.factory"; +import { stateServiceFactory } from "../background/service_factories/state-service.factory"; +import { totpServiceFactory } from "../background/service_factories/totp-service.factory"; +import { BrowserApi } from "../browser/browserApi"; +import { copyToClipboard, GeneratePasswordToClipboardCommand } from "../clipboard"; +import { AutofillTabCommand } from "../commands/autofill-tab-command"; +import { Account } from "../models/account"; + +import { + AUTOFILL_ID, + COPY_IDENTIFIER_ID, + COPY_PASSWORD_ID, + COPY_USERNAME_ID, + COPY_VERIFICATIONCODE_ID, + GENERATE_PASSWORD_ID, + NOOP_COMMAND_SUFFIX, +} from "./main-context-menu-handler"; + +export type CopyToClipboardOptions = { text: string; tab: chrome.tabs.Tab }; +export type CopyToClipboardAction = (options: CopyToClipboardOptions) => void; + +export type GeneratePasswordToClipboardAction = (tab: chrome.tabs.Tab) => Promise; + +const NOT_IMPLEMENTED = (..._args: unknown[]) => + Promise.reject("This action is not implemented inside of a service worker context."); + +export class ContextMenuClickedHandler { + constructor( + private copyToClipboard: CopyToClipboardAction, + private generatePasswordToClipboard: GeneratePasswordToClipboardAction, + private authService: AuthService, + private cipherService: CipherService, + private autofillTabCommand: AutofillTabCommand, + private totpService: TotpService, + private eventCollectionService: EventCollectionService + ) {} + + static async mv3Create(cachedServices: CachedServices) { + const stateFactory = new StateFactory(GlobalState, Account); + let searchService: SearchService | null = null; + const serviceOptions: AuthServiceInitOptions & CipherServiceInitOptions = { + apiServiceOptions: { + logoutCallback: NOT_IMPLEMENTED, + }, + cipherServiceOptions: { + searchServiceFactory: () => searchService, + }, + cryptoFunctionServiceOptions: { + win: self, + }, + encryptServiceOptions: { + logMacFailures: false, + }, + i18nServiceOptions: { + systemLanguage: chrome.i18n.getUILanguage(), + }, + keyConnectorServiceOptions: { + logoutCallback: NOT_IMPLEMENTED, + }, + logServiceOptions: { + isDev: false, + }, + platformUtilsServiceOptions: { + biometricCallback: NOT_IMPLEMENTED, + clipboardWriteCallback: NOT_IMPLEMENTED, + win: self, + }, + stateMigrationServiceOptions: { + stateFactory: stateFactory, + }, + stateServiceOptions: { + stateFactory: stateFactory, + }, + }; + searchService = await searchServiceFactory(cachedServices, serviceOptions); + + const generatePasswordToClipboardCommand = new GeneratePasswordToClipboardCommand( + await passwordGenerationServiceFactory(cachedServices, serviceOptions), + await stateServiceFactory(cachedServices, serviceOptions) + ); + + return new ContextMenuClickedHandler( + (options) => copyToClipboard(options.tab, options.text), + (tab) => generatePasswordToClipboardCommand.generatePasswordToClipboard(tab), + await authServiceFactory(cachedServices, serviceOptions), + await cipherServiceFactory(cachedServices, serviceOptions), + new AutofillTabCommand(await autofillServiceFactory(cachedServices, serviceOptions)), + await totpServiceFactory(cachedServices, serviceOptions), + await eventCollectionServiceFactory(cachedServices, serviceOptions) + ); + } + + static async onClickedListener( + info: chrome.contextMenus.OnClickData, + tab?: chrome.tabs.Tab, + cachedServices: CachedServices = {} + ) { + const contextMenuClickedHandler = await ContextMenuClickedHandler.mv3Create(cachedServices); + await contextMenuClickedHandler.run(info, tab); + } + + static async messageListener( + message: { command: string; data: LockedVaultPendingNotificationsItem }, + cachedServices: CachedServices + ) { + if ( + message.command !== "unlockCompleted" || + message.data.target !== "contextmenus.background" + ) { + return; + } + + const contextMenuClickedHandler = await ContextMenuClickedHandler.mv3Create(cachedServices); + await contextMenuClickedHandler.run( + message.data.commandToRetry.msg.data, + message.data.commandToRetry.sender.tab + ); + } + + async run(info: chrome.contextMenus.OnClickData, tab?: chrome.tabs.Tab) { + switch (info.menuItemId) { + case GENERATE_PASSWORD_ID: + if (!tab) { + return; + } + await this.generatePasswordToClipboard(tab); + break; + case COPY_IDENTIFIER_ID: + if (!tab) { + return; + } + this.copyToClipboard({ text: await this.getIdentifier(tab, info), tab: tab }); + break; + default: + await this.cipherAction(info, tab); + } + } + + async cipherAction(info: chrome.contextMenus.OnClickData, tab?: chrome.tabs.Tab) { + if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) { + const retryMessage: LockedVaultPendingNotificationsItem = { + commandToRetry: { + msg: { command: NOOP_COMMAND_SUFFIX, data: info }, + sender: { tab: tab }, + }, + target: "contextmenus.background", + }; + await BrowserApi.tabSendMessageData( + tab, + "addToLockedVaultPendingNotifications", + retryMessage + ); + + await BrowserApi.tabSendMessageData(tab, "promptForLogin"); + return; + } + + // NOTE: We don't actually use the first part of this ID, we further switch based on the parentMenuItemId + // I would really love to not add it but that is a departure from how it currently works. + const id = (info.menuItemId as string).split("_")[1]; // We create all the ids, we can guarantee they are strings + let cipher: CipherView | undefined; + if (id === NOOP_COMMAND_SUFFIX) { + // This NOOP item has come through which is generally only for no access state but since we got here + // we are actually unlocked we will do our best to find a good match of an item to autofill this is useful + // in scenarios like unlock on autofill + const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url); + cipher = ciphers.find((c) => c.reprompt === CipherRepromptType.None); + } else { + const ciphers = await this.cipherService.getAllDecrypted(); + cipher = ciphers.find((c) => c.id === id); + } + + if (cipher == null) { + return; + } + + switch (info.parentMenuItemId) { + case AUTOFILL_ID: + if (tab == null) { + return; + } + await this.autofillTabCommand.doAutofillTabWithCipherCommand(tab, cipher); + break; + case COPY_USERNAME_ID: + this.copyToClipboard({ text: cipher.login.username, tab: tab }); + break; + case COPY_PASSWORD_ID: + this.copyToClipboard({ text: cipher.login.password, tab: tab }); + this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); + break; + case COPY_VERIFICATIONCODE_ID: + this.copyToClipboard({ text: await this.totpService.getCode(cipher.login.totp), tab: tab }); + break; + } + } + + private async getIdentifier(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) { + return new Promise((resolve, reject) => { + BrowserApi.sendTabsMessage( + tab.id, + { command: "getClickedElement" }, + { frameId: info.frameId }, + (identifier: string) => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + return; + } + + resolve(identifier); + } + ); + }); + } +} diff --git a/apps/browser/src/browser/main-context-menu-handler.spec.ts b/apps/browser/src/browser/main-context-menu-handler.spec.ts new file mode 100644 index 00000000000..2dd41faf043 --- /dev/null +++ b/apps/browser/src/browser/main-context-menu-handler.spec.ts @@ -0,0 +1,137 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { CipherType } from "@bitwarden/common/enums/cipherType"; +import { Cipher } from "@bitwarden/common/models/domain/cipher"; +import { CipherView } from "@bitwarden/common/models/view/cipher.view"; + +import { BrowserStateService } from "../services/abstractions/browser-state.service"; + +import { MainContextMenuHandler } from "./main-context-menu-handler"; + +describe("context-menu", () => { + let stateService: MockProxy; + let i18nService: MockProxy; + + let removeAllSpy: jest.SpyInstance void]>; + let createSpy: jest.SpyInstance< + string | number, + [createProperties: chrome.contextMenus.CreateProperties, callback?: () => void] + >; + + let sut: MainContextMenuHandler; + + beforeEach(() => { + stateService = mock(); + i18nService = mock(); + + removeAllSpy = jest + .spyOn(chrome.contextMenus, "removeAll") + .mockImplementation((callback) => callback()); + + createSpy = jest.spyOn(chrome.contextMenus, "create").mockImplementation((props, callback) => { + if (callback) { + callback(); + } + return props.id; + }); + + sut = new MainContextMenuHandler(stateService, i18nService); + }); + + afterEach(() => jest.resetAllMocks()); + + describe("init", () => { + it("has menu disabled", async () => { + stateService.getDisableContextMenuItem.mockResolvedValue(true); + + const createdMenu = await sut.init(); + expect(createdMenu).toBeFalsy(); + expect(removeAllSpy).toHaveBeenCalledTimes(1); + }); + + it("has menu enabled, but does not have premium", async () => { + stateService.getDisableContextMenuItem.mockResolvedValue(false); + + stateService.getCanAccessPremium.mockResolvedValue(false); + + const createdMenu = await sut.init(); + expect(createdMenu).toBeTruthy(); + expect(createSpy).toHaveBeenCalledTimes(7); + }); + + it("has menu enabled and has premium", async () => { + stateService.getDisableContextMenuItem.mockResolvedValue(false); + + stateService.getCanAccessPremium.mockResolvedValue(true); + + const createdMenu = await sut.init(); + expect(createdMenu).toBeTruthy(); + expect(createSpy).toHaveBeenCalledTimes(8); + }); + }); + + describe("loadOptions", () => { + const createCipher = (data?: { + id?: CipherView["id"]; + username?: CipherView["login"]["username"]; + password?: CipherView["login"]["password"]; + totp?: CipherView["login"]["totp"]; + viewPassword?: CipherView["viewPassword"]; + }): CipherView => { + const { id, username, password, totp, viewPassword } = data || {}; + const cipherView = new CipherView( + new Cipher({ + id: id ?? "1", + type: CipherType.Login, + viewPassword: viewPassword ?? true, + } as any) + ); + cipherView.login.username = username ?? "USERNAME"; + cipherView.login.password = password ?? "PASSWORD"; + cipherView.login.totp = totp ?? "TOTP"; + return cipherView; + }; + + it("is not a login cipher", async () => { + await sut.loadOptions("TEST_TITLE", "1", "", { + ...createCipher(), + type: CipherType.SecureNote, + } as any); + + expect(createSpy).not.toHaveBeenCalled(); + }); + + it("creates item for autofill", async () => { + await sut.loadOptions( + "TEST_TITLE", + "1", + "", + createCipher({ + username: "", + totp: "", + viewPassword: false, + }) + ); + + expect(createSpy).toHaveBeenCalledTimes(1); + }); + + it("create entry for each cipher piece", async () => { + stateService.getCanAccessPremium.mockResolvedValue(true); + + await sut.loadOptions("TEST_TITLE", "1", "", createCipher()); + + // One for autofill, copy username, copy password, and copy totp code + expect(createSpy).toHaveBeenCalledTimes(4); + }); + + it("creates noop item for no cipher", async () => { + stateService.getCanAccessPremium.mockResolvedValue(true); + + await sut.loadOptions("TEST_TITLE", "NOOP", ""); + + expect(createSpy).toHaveBeenCalledTimes(4); + }); + }); +}); diff --git a/apps/browser/src/browser/main-context-menu-handler.ts b/apps/browser/src/browser/main-context-menu-handler.ts new file mode 100644 index 00000000000..e78fb89023d --- /dev/null +++ b/apps/browser/src/browser/main-context-menu-handler.ts @@ -0,0 +1,241 @@ +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { CipherType } from "@bitwarden/common/enums/cipherType"; +import { StateFactory } from "@bitwarden/common/factories/stateFactory"; +import { Utils } from "@bitwarden/common/misc/utils"; +import { GlobalState } from "@bitwarden/common/models/domain/global-state"; +import { CipherView } from "@bitwarden/common/models/view/cipher.view"; + +import { CachedServices } from "../background/service_factories/factory-options"; +import { + i18nServiceFactory, + I18nServiceInitOptions, +} from "../background/service_factories/i18n-service.factory"; +import { + stateServiceFactory, + StateServiceInitOptions, +} from "../background/service_factories/state-service.factory"; +import { Account } from "../models/account"; +import { BrowserStateService } from "../services/abstractions/browser-state.service"; + +export const ROOT_ID = "root"; + +export const AUTOFILL_ID = "autofill"; +export const COPY_USERNAME_ID = "copy-username"; +export const COPY_PASSWORD_ID = "copy-password"; +export const COPY_VERIFICATIONCODE_ID = "copy-totp"; +export const COPY_IDENTIFIER_ID = "copy-identifier"; + +const SEPARATOR_ID = "separator"; +export const GENERATE_PASSWORD_ID = "generate-password"; + +export const NOOP_COMMAND_SUFFIX = "noop"; + +export class MainContextMenuHandler { + // + private initRunning = false; + + create: (options: chrome.contextMenus.CreateProperties) => Promise; + + constructor(private stateService: BrowserStateService, private i18nService: I18nService) { + if (chrome.contextMenus) { + this.create = (options) => { + return new Promise((resolve, reject) => { + chrome.contextMenus.create(options, () => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + return; + } + resolve(); + }); + }); + }; + } else { + this.create = (_options) => Promise.resolve(); + } + } + + static async mv3Create(cachedServices: CachedServices) { + const stateFactory = new StateFactory(GlobalState, Account); + const serviceOptions: StateServiceInitOptions & I18nServiceInitOptions = { + cryptoFunctionServiceOptions: { + win: self, + }, + encryptServiceOptions: { + logMacFailures: false, + }, + i18nServiceOptions: { + systemLanguage: chrome.i18n.getUILanguage(), + }, + logServiceOptions: { + isDev: false, + }, + stateMigrationServiceOptions: { + stateFactory: stateFactory, + }, + stateServiceOptions: { + stateFactory: stateFactory, + }, + }; + + return new MainContextMenuHandler( + await stateServiceFactory(cachedServices, serviceOptions), + await i18nServiceFactory(cachedServices, serviceOptions) + ); + } + + /** + * + * @returns a boolean showing whether or not items were created + */ + async init(): Promise { + const menuDisabled = await this.stateService.getDisableContextMenuItem(); + + if (this.initRunning) { + return menuDisabled; + } + + try { + if (menuDisabled) { + await MainContextMenuHandler.removeAll(); + return false; + } + + const create = async (options: Omit) => { + await this.create({ ...options, contexts: ["all"] }); + }; + + await create({ + id: ROOT_ID, + title: "Bitwarden", + }); + + await create({ + id: AUTOFILL_ID, + parentId: ROOT_ID, + title: this.i18nService.t("autoFill"), + }); + + await create({ + id: COPY_USERNAME_ID, + parentId: ROOT_ID, + title: this.i18nService.t("copyUsername"), + }); + + await create({ + id: COPY_PASSWORD_ID, + parentId: ROOT_ID, + title: this.i18nService.t("copyPassword"), + }); + + if (await this.stateService.getCanAccessPremium()) { + await create({ + id: COPY_VERIFICATIONCODE_ID, + parentId: ROOT_ID, + title: this.i18nService.t("copyVerificationCode"), + }); + } + + await create({ + id: SEPARATOR_ID, + type: "separator", + parentId: ROOT_ID, + }); + + await create({ + id: GENERATE_PASSWORD_ID, + parentId: ROOT_ID, + title: this.i18nService.t("generatePasswordCopied"), + }); + + await create({ + id: COPY_IDENTIFIER_ID, + parentId: ROOT_ID, + title: this.i18nService.t("copyElementIdentifier"), + }); + + return true; + } finally { + this.initRunning = false; + } + } + + static async removeAll() { + return new Promise((resolve, reject) => { + chrome.contextMenus.removeAll(() => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + return; + } + + resolve(); + }); + }); + } + + static remove(menuItemId: string) { + return new Promise((resolve, reject) => { + chrome.contextMenus.remove(menuItemId, () => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + return; + } + + resolve(); + }); + }); + } + + async loadOptions(title: string, id: string, url: string, cipher?: CipherView | undefined) { + if (cipher != null && cipher.type !== CipherType.Login) { + return; + } + + const sanitizedTitle = MainContextMenuHandler.sanitizeContextMenuTitle(title); + + const createChildItem = async (parent: string) => { + const menuItemId = `${parent}_${id}`; + return await this.create({ + type: "normal", + id: menuItemId, + parentId: parent, + title: sanitizedTitle, + contexts: ["all"], + }); + }; + + if (cipher == null || !Utils.isNullOrEmpty(cipher.login.password)) { + await createChildItem(AUTOFILL_ID); + if (cipher?.viewPassword ?? true) { + await createChildItem(COPY_PASSWORD_ID); + } + } + + if (cipher == null || !Utils.isNullOrEmpty(cipher.login.username)) { + await createChildItem(COPY_USERNAME_ID); + } + + const canAccessPremium = await this.stateService.getCanAccessPremium(); + if (canAccessPremium && (cipher == null || !Utils.isNullOrEmpty(cipher.login.totp))) { + await createChildItem(COPY_VERIFICATIONCODE_ID); + } + } + + static sanitizeContextMenuTitle(title: string): string { + return title.replace(/&/g, "&&"); + } + + async noAccess() { + if (await this.init()) { + const authed = await this.stateService.getIsAuthenticated(); + await this.loadOptions( + this.i18nService.t(authed ? "unlockVaultMenu" : "loginToVaultMenu"), + NOOP_COMMAND_SUFFIX, + "" + ); + } + } + + async noLogins(url: string) { + await this.loadOptions(this.i18nService.t("noMatchingLogins"), NOOP_COMMAND_SUFFIX, url); + } +} diff --git a/apps/browser/src/commands/autoFillActiveTabCommand.ts b/apps/browser/src/commands/autofill-tab-command.ts similarity index 57% rename from apps/browser/src/commands/autoFillActiveTabCommand.ts rename to apps/browser/src/commands/autofill-tab-command.ts index 74cdad55d73..cf94dc93ae3 100644 --- a/apps/browser/src/commands/autoFillActiveTabCommand.ts +++ b/apps/browser/src/commands/autofill-tab-command.ts @@ -1,10 +1,12 @@ +import { CipherView } from "@bitwarden/common/models/view/cipher.view"; + import AutofillPageDetails from "../models/autofillPageDetails"; import { AutofillService } from "../services/abstractions/autofill.service"; -export class AutoFillActiveTabCommand { +export class AutofillTabCommand { constructor(private autofillService: AutofillService) {} - async doAutoFillActiveTabCommand(tab: chrome.tabs.Tab) { + async doAutofillTabCommand(tab: chrome.tabs.Tab) { if (!tab.id) { throw new Error("Tab does not have an id, cannot complete autofill."); } @@ -23,6 +25,30 @@ export class AutoFillActiveTabCommand { ); } + async doAutofillTabWithCipherCommand(tab: chrome.tabs.Tab, cipher: CipherView) { + if (!tab.id) { + throw new Error("Tab does not have an id, cannot complete autofill."); + } + + const details = await this.collectPageDetails(tab.id); + await this.autofillService.doAutoFill({ + tab: tab, + cipher: cipher, + pageDetails: [ + { + frameId: 0, + tab: tab, + details: details, + }, + ], + skipLastUsed: false, + skipUsernameOnlyFill: false, + onlyEmptyFields: false, + onlyVisibleFields: false, + fillNewPassword: true, + }); + } + private async collectPageDetails(tabId: number): Promise { return new Promise((resolve, reject) => { chrome.tabs.sendMessage( diff --git a/apps/browser/src/content/contextMenuHandler.ts b/apps/browser/src/content/contextMenuHandler.ts index b6d0159df0f..30aa40be4ac 100644 --- a/apps/browser/src/content/contextMenuHandler.ts +++ b/apps/browser/src/content/contextMenuHandler.ts @@ -54,9 +54,12 @@ document.addEventListener("contextmenu", (event) => { }); // Runs when the 'Copy Custom Field Name' context menu item is actually clicked. -chrome.runtime.onMessage.addListener((event) => { +chrome.runtime.onMessage.addListener((event, _sender, sendResponse) => { if (event.command === "getClickedElement") { const identifier = getClickedElementIdentifier(); + if (sendResponse) { + sendResponse(identifier); + } chrome.runtime.sendMessage({ command: "getClickedElementResponse", sender: "contextMenuHandler", diff --git a/apps/browser/src/listeners/combine.spec.ts b/apps/browser/src/listeners/combine.spec.ts new file mode 100644 index 00000000000..3975d27017d --- /dev/null +++ b/apps/browser/src/listeners/combine.spec.ts @@ -0,0 +1,25 @@ +import { combine } from "./combine"; + +describe("combine", () => { + it("runs", () => { + const combined = combine([ + (arg: Record, serviceCache: Record) => { + arg["one"] = true; + serviceCache["one"] = true; + }, + (arg: Record, serviceCache: Record) => { + if (serviceCache["one"] !== true) { + throw new Error("One should have ran."); + } + arg["two"] = true; + }, + ]); + + const arg: Record = {}; + combined(arg); + + expect(arg["one"]).toBeTruthy(); + + expect(arg["two"]).toBeTruthy(); + }); +}); diff --git a/apps/browser/src/listeners/combine.ts b/apps/browser/src/listeners/combine.ts new file mode 100644 index 00000000000..278772ba214 --- /dev/null +++ b/apps/browser/src/listeners/combine.ts @@ -0,0 +1,15 @@ +import { CachedServices } from "../background/service_factories/factory-options"; + +type Listener = (...args: [...T, CachedServices]) => void; + +export const combine = ( + listeners: Listener[], + startingServices: CachedServices = {} +) => { + return (...args: T) => { + const cachedServices = { ...startingServices }; + for (const listener of listeners) { + listener(...[...args, cachedServices]); + } + }; +}; diff --git a/apps/browser/src/listeners/index.ts b/apps/browser/src/listeners/index.ts new file mode 100644 index 00000000000..b55ce884a10 --- /dev/null +++ b/apps/browser/src/listeners/index.ts @@ -0,0 +1,40 @@ +import { CipherContextMenuHandler } from "../browser/cipher-context-menu-handler"; +import { ContextMenuClickedHandler } from "../browser/context-menu-clicked-handler"; + +import { combine } from "./combine"; +import { onCommandListener } from "./onCommandListener"; +import { onInstallListener } from "./onInstallListener"; +import { UpdateBadge } from "./update-badge"; + +const tabsOnActivatedListener = combine([ + UpdateBadge.tabsOnActivatedListener, + CipherContextMenuHandler.tabsOnActivatedListener, +]); + +const tabsOnReplacedListener = combine([ + UpdateBadge.tabsOnReplacedListener, + CipherContextMenuHandler.tabsOnReplacedListener, +]); + +const tabsOnUpdatedListener = combine([ + UpdateBadge.tabsOnUpdatedListener, + CipherContextMenuHandler.tabsOnUpdatedListener, +]); + +const contextMenusClickedListener = ContextMenuClickedHandler.onClickedListener; + +const runtimeMessageListener = combine([ + UpdateBadge.messageListener, + CipherContextMenuHandler.messageListener, + ContextMenuClickedHandler.messageListener, +]); + +export { + tabsOnActivatedListener, + tabsOnReplacedListener, + tabsOnUpdatedListener, + contextMenusClickedListener, + runtimeMessageListener, + onCommandListener, + onInstallListener, +}; diff --git a/apps/browser/src/listeners/onCommandListener.ts b/apps/browser/src/listeners/onCommandListener.ts index c52e5cb61ac..395285d40e6 100644 --- a/apps/browser/src/listeners/onCommandListener.ts +++ b/apps/browser/src/listeners/onCommandListener.ts @@ -14,7 +14,7 @@ import { import { stateServiceFactory } from "../background/service_factories/state-service.factory"; import { BrowserApi } from "../browser/browserApi"; import { GeneratePasswordToClipboardCommand } from "../clipboard"; -import { AutoFillActiveTabCommand } from "../commands/autoFillActiveTabCommand"; +import { AutofillTabCommand } from "../commands/autofill-tab-command"; import { Account } from "../models/account"; export const onCommandListener = async (command: string, tab: chrome.tabs.Tab) => { @@ -75,8 +75,8 @@ const doAutoFillLogin = async (tab: chrome.tabs.Tab): Promise => { return; } - const command = new AutoFillActiveTabCommand(autofillService); - await command.doAutoFillActiveTabCommand(tab); + const command = new AutofillTabCommand(autofillService); + await command.doAutofillTabCommand(tab); }; const doGeneratePasswordToClipboard = async (tab: chrome.tabs.Tab): Promise => { diff --git a/apps/browser/src/listeners/onInstallListener.ts b/apps/browser/src/listeners/onInstallListener.ts index cc2d60594c9..92cd2fd6b78 100644 --- a/apps/browser/src/listeners/onInstallListener.ts +++ b/apps/browser/src/listeners/onInstallListener.ts @@ -1,13 +1,16 @@ import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { GlobalState } from "@bitwarden/common/models/domain/global-state"; -import { environmentServiceFactory } from "../background/service_factories/environment-service.factory"; +import { + environmentServiceFactory, + EnvironmentServiceInitOptions, +} from "../background/service_factories/environment-service.factory"; import { BrowserApi } from "../browser/browserApi"; import { Account } from "../models/account"; export async function onInstallListener(details: chrome.runtime.InstalledDetails) { const cache = {}; - const opts = { + const opts: EnvironmentServiceInitOptions = { encryptServiceOptions: { logMacFailures: false, }, @@ -27,7 +30,7 @@ export async function onInstallListener(details: chrome.runtime.InstalledDetails const environmentService = await environmentServiceFactory(cache, opts); setTimeout(async () => { - if (details.reason != null && details.reason === "install") { + if (details.reason != null && details.reason === chrome.runtime.OnInstalledReason.INSTALL) { BrowserApi.createNewTab("https://bitwarden.com/browser-start/"); if (await environmentService.hasManagedEnvironment()) { diff --git a/apps/browser/src/listeners/update-badge.ts b/apps/browser/src/listeners/update-badge.ts index c7a3d32e59a..ac1ce880389 100644 --- a/apps/browser/src/listeners/update-badge.ts +++ b/apps/browser/src/listeners/update-badge.ts @@ -43,31 +43,47 @@ export class UpdateBadge { "deletedCipher", ]; - static async tabsOnActivatedListener(activeInfo: chrome.tabs.TabActiveInfo) { - await new UpdateBadge(self).run({ tabId: activeInfo.tabId, windowId: activeInfo.windowId }); + static async tabsOnActivatedListener( + activeInfo: chrome.tabs.TabActiveInfo, + serviceCache: Record + ) { + await new UpdateBadge(self).run({ + tabId: activeInfo.tabId, + existingServices: serviceCache, + windowId: activeInfo.windowId, + }); } - static async tabsOnReplacedListener(addedTabId: number, removedTabId: number) { - await new UpdateBadge(self).run({ tabId: addedTabId }); + static async tabsOnReplacedListener( + addedTabId: number, + removedTabId: number, + serviceCache: Record + ) { + await new UpdateBadge(self).run({ tabId: addedTabId, existingServices: serviceCache }); } static async tabsOnUpdatedListener( tabId: number, changeInfo: chrome.tabs.TabChangeInfo, - tab: chrome.tabs.Tab + tab: chrome.tabs.Tab, + serviceCache: Record ) { - await new UpdateBadge(self).run({ tabId, windowId: tab.windowId }); + await new UpdateBadge(self).run({ + tabId, + existingServices: serviceCache, + windowId: tab.windowId, + }); } static async messageListener( - serviceCache: Record, - message: { command: string; tabId: number } + message: { command: string; tabId: number }, + serviceCache: Record ) { if (!UpdateBadge.listenedToCommands.includes(message.command)) { return; } - await new UpdateBadge(self).run(); + await new UpdateBadge(self).run({ existingServices: serviceCache }); } constructor(win: Window & typeof globalThis) { diff --git a/apps/browser/src/types/tab-messages.ts b/apps/browser/src/types/tab-messages.ts index 12496f5aa3d..dbedb3c4a55 100644 --- a/apps/browser/src/types/tab-messages.ts +++ b/apps/browser/src/types/tab-messages.ts @@ -1,9 +1,16 @@ -export type TabMessage = CopyTextTabMessage | TabMessageBase<"clearClipboard">; +export type TabMessage = + | CopyTextTabMessage + | ClearClipboardTabMessage + | GetClickedElementTabMessage; export type TabMessageBase = { command: T; }; -export type CopyTextTabMessage = TabMessageBase<"copyText"> & { +type CopyTextTabMessage = TabMessageBase<"copyText"> & { text: string; }; + +type ClearClipboardTabMessage = TabMessageBase<"clearClipboard">; + +type GetClickedElementTabMessage = TabMessageBase<"getClickedElement">; diff --git a/apps/browser/test.setup.ts b/apps/browser/test.setup.ts index a231353f64b..f87fa9c2c12 100644 --- a/apps/browser/test.setup.ts +++ b/apps/browser/test.setup.ts @@ -25,8 +25,14 @@ const runtime = { getManifest: jest.fn(), }; +const contextMenus = { + create: jest.fn(), + removeAll: jest.fn(), +}; + // set chrome global.chrome = { storage, runtime, + contextMenus, } as any; diff --git a/libs/common/src/abstractions/cipher.service.ts b/libs/common/src/abstractions/cipher.service.ts index b551f209910..457bbcd962b 100644 --- a/libs/common/src/abstractions/cipher.service.ts +++ b/libs/common/src/abstractions/cipher.service.ts @@ -66,8 +66,8 @@ export abstract class CipherService { deleteManyWithServer: (ids: string[]) => Promise; deleteAttachment: (id: string, attachmentId: string) => Promise; deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise; - sortCiphersByLastUsed: (a: any, b: any) => number; - sortCiphersByLastUsedThenName: (a: any, b: any) => number; + sortCiphersByLastUsed: (a: CipherView, b: CipherView) => number; + sortCiphersByLastUsedThenName: (a: CipherView, b: CipherView) => number; getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number; softDelete: (id: string | string[]) => Promise; softDeleteWithServer: (id: string) => Promise; From 6b1a72851a37cb4e66c5d6194b617779b567572b Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Mon, 9 Jan 2023 12:59:08 +0100 Subject: [PATCH 142/205] Fixing Successful toast message does not appear (#4421) --- .../src/components/send/add-edit.component.ts | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/libs/angular/src/components/send/add-edit.component.ts b/libs/angular/src/components/send/add-edit.component.ts index 51c8f120488..55b8a03b835 100644 --- a/libs/angular/src/components/send/add-edit.component.ts +++ b/libs/angular/src/components/send/add-edit.component.ts @@ -201,25 +201,14 @@ export class AddEditComponent implements OnInit, OnDestroy { } this.onSavedSend.emit(this.send); if (this.copyLink && this.link != null) { - const copySuccess = await this.copyLinkToClipboard(this.link); - if (copySuccess ?? true) { - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.editMode ? "editedSend" : "createdSend") - ); - } else { - await this.platformUtilsService.showDialog( - this.i18nService.t(this.editMode ? "editedSend" : "createdSend"), - null, - this.i18nService.t("ok"), - null, - "success", - null - ); - await this.copyLinkToClipboard(this.link); - } + await this.handleCopyLinkToClipboard(); + return; } + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t(this.editMode ? "editedSend" : "createdSend") + ); }); try { await this.formPromise; @@ -308,4 +297,24 @@ export class AddEditComponent implements OnInit, OnDestroy { this.showPassword = !this.showPassword; document.getElementById("password").focus(); } + private async handleCopyLinkToClipboard() { + const copySuccess = await this.copyLinkToClipboard(this.link); + if (copySuccess ?? true) { + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t(this.editMode ? "editedSend" : "createdSend") + ); + } else { + await this.platformUtilsService.showDialog( + this.i18nService.t(this.editMode ? "editedSend" : "createdSend"), + null, + this.i18nService.t("ok"), + null, + "success", + null + ); + await this.copyLinkToClipboard(this.link); + } + } } From f4219bada9c31ca1a8b41593ef0f66f2414a3b8f Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 9 Jan 2023 18:59:19 +0100 Subject: [PATCH 143/205] [EC-937] Pre-merge breadcrumbs into master (#4420) original implementation from ef20ee1882648fd36c8b775106652dd16c5fb55e (https://github.com/bitwarden/clients/pull/3762) --- .../src/breadcrumbs/breadcrumb.component.html | 4 + .../src/breadcrumbs/breadcrumb.component.ts | 25 +++++ .../breadcrumbs/breadcrumbs.component.html | 81 +++++++++++++++++ .../src/breadcrumbs/breadcrumbs.component.ts | 39 ++++++++ .../src/breadcrumbs/breadcrumbs.module.ts | 17 ++++ .../src/breadcrumbs/breadcrumbs.stories.ts | 91 +++++++++++++++++++ libs/components/src/breadcrumbs/index.ts | 3 + libs/components/src/index.ts | 1 + 8 files changed, 261 insertions(+) create mode 100644 libs/components/src/breadcrumbs/breadcrumb.component.html create mode 100644 libs/components/src/breadcrumbs/breadcrumb.component.ts create mode 100644 libs/components/src/breadcrumbs/breadcrumbs.component.html create mode 100644 libs/components/src/breadcrumbs/breadcrumbs.component.ts create mode 100644 libs/components/src/breadcrumbs/breadcrumbs.module.ts create mode 100644 libs/components/src/breadcrumbs/breadcrumbs.stories.ts create mode 100644 libs/components/src/breadcrumbs/index.ts diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.html b/libs/components/src/breadcrumbs/breadcrumb.component.html new file mode 100644 index 00000000000..5291f0cab4c --- /dev/null +++ b/libs/components/src/breadcrumbs/breadcrumb.component.html @@ -0,0 +1,4 @@ + + + + diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.ts b/libs/components/src/breadcrumbs/breadcrumb.component.ts new file mode 100644 index 00000000000..060154b4f69 --- /dev/null +++ b/libs/components/src/breadcrumbs/breadcrumb.component.ts @@ -0,0 +1,25 @@ +import { Component, EventEmitter, Input, Output, TemplateRef, ViewChild } from "@angular/core"; + +@Component({ + selector: "bit-breadcrumb", + templateUrl: "./breadcrumb.component.html", +}) +export class BreadcrumbComponent { + @Input() + icon?: string; + + @Input() + route?: string | any[] = undefined; + + @Input() + queryParams?: Record = {}; + + @Output() + click = new EventEmitter(); + + @ViewChild(TemplateRef, { static: true }) content: TemplateRef; + + onClick(args: unknown) { + this.click.next(args); + } +} diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.html b/libs/components/src/breadcrumbs/breadcrumbs.component.html new file mode 100644 index 00000000000..e291c4a9b97 --- /dev/null +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.html @@ -0,0 +1,81 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.ts b/libs/components/src/breadcrumbs/breadcrumbs.component.ts new file mode 100644 index 00000000000..64ca8146c80 --- /dev/null +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.ts @@ -0,0 +1,39 @@ +import { Component, ContentChildren, Input, QueryList } from "@angular/core"; + +import { BreadcrumbComponent } from "./breadcrumb.component"; + +@Component({ + selector: "bit-breadcrumbs", + templateUrl: "./breadcrumbs.component.html", +}) +export class BreadcrumbsComponent { + @Input() + show = 3; + + private breadcrumbs: BreadcrumbComponent[] = []; + + @ContentChildren(BreadcrumbComponent) + protected set breadcrumbList(value: QueryList) { + this.breadcrumbs = value.toArray(); + } + + protected get beforeOverflow() { + if (this.hasOverflow) { + return this.breadcrumbs.slice(0, this.show - 1); + } + + return this.breadcrumbs; + } + + protected get overflow() { + return this.breadcrumbs.slice(this.show - 1, -1); + } + + protected get afterOverflow() { + return this.breadcrumbs.slice(-1); + } + + protected get hasOverflow() { + return this.breadcrumbs.length > this.show; + } +} diff --git a/libs/components/src/breadcrumbs/breadcrumbs.module.ts b/libs/components/src/breadcrumbs/breadcrumbs.module.ts new file mode 100644 index 00000000000..0812b552f9a --- /dev/null +++ b/libs/components/src/breadcrumbs/breadcrumbs.module.ts @@ -0,0 +1,17 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { IconButtonModule } from "../icon-button"; +import { LinkModule } from "../link"; +import { MenuModule } from "../menu"; + +import { BreadcrumbComponent } from "./breadcrumb.component"; +import { BreadcrumbsComponent } from "./breadcrumbs.component"; + +@NgModule({ + imports: [CommonModule, LinkModule, IconButtonModule, MenuModule, RouterModule], + declarations: [BreadcrumbsComponent, BreadcrumbComponent], + exports: [BreadcrumbsComponent, BreadcrumbComponent], +}) +export class BreadcrumbsModule {} diff --git a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts new file mode 100644 index 00000000000..782bb39e75d --- /dev/null +++ b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts @@ -0,0 +1,91 @@ +import { Component } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { Meta, Story, moduleMetadata } from "@storybook/angular"; + +import { IconButtonModule } from "../icon-button"; +import { LinkModule } from "../link"; +import { MenuModule } from "../menu"; + +import { BreadcrumbComponent } from "./breadcrumb.component"; +import { BreadcrumbsComponent } from "./breadcrumbs.component"; + +interface Breadcrumb { + icon?: string; + name: string; + route: string; +} + +@Component({ + template: "", +}) +class EmptyComponent {} + +export default { + title: "Component Library/Breadcrumbs", + component: BreadcrumbsComponent, + decorators: [ + moduleMetadata({ + declarations: [BreadcrumbComponent], + imports: [ + LinkModule, + MenuModule, + IconButtonModule, + RouterModule.forRoot([{ path: "**", component: EmptyComponent }], { useHash: true }), + ], + }), + ], + args: { + items: [], + }, + argTypes: { + breadcrumbs: { + table: { disable: true }, + }, + click: { action: "clicked" }, + }, +} as Meta; + +const Template: Story = (args: BreadcrumbsComponent) => ({ + props: args, + template: ` +

Router links

+

+ + {{item.name}} + +

+ +

Click emit

+

+ + {{item.name}} + +

+ `, +}); + +export const TopLevel = Template.bind({}); +TopLevel.args = { + items: [{ icon: "bwi-star", name: "Top Level" }] as Breadcrumb[], +}; + +export const SecondLevel = Template.bind({}); +SecondLevel.args = { + items: [ + { name: "Acme Vault", route: "/" }, + { icon: "bwi-collection", name: "Collection", route: "collection" }, + ] as Breadcrumb[], +}; + +export const Overflow = Template.bind({}); +Overflow.args = { + items: [ + { name: "Acme Vault", route: "" }, + { icon: "bwi-collection", name: "Collection", route: "collection" }, + { icon: "bwi-collection", name: "Middle-Collection 1", route: "middle-collection-1" }, + { icon: "bwi-collection", name: "Middle-Collection 2", route: "middle-collection-2" }, + { icon: "bwi-collection", name: "Middle-Collection 3", route: "middle-collection-3" }, + { icon: "bwi-collection", name: "Middle-Collection 4", route: "middle-collection-4" }, + { icon: "bwi-collection", name: "End Collection", route: "end-collection" }, + ] as Breadcrumb[], +}; diff --git a/libs/components/src/breadcrumbs/index.ts b/libs/components/src/breadcrumbs/index.ts new file mode 100644 index 00000000000..02f4311e6d0 --- /dev/null +++ b/libs/components/src/breadcrumbs/index.ts @@ -0,0 +1,3 @@ +export * from "./breadcrumbs.module"; +export * from "./breadcrumbs.component"; +export * from "./breadcrumb.component"; diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index 86af709cda1..0ede4028744 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -3,6 +3,7 @@ export * from "./avatar"; export * from "./badge"; export * from "./banner"; export * from "./button"; +export * from "./breadcrumbs"; export * from "./callout"; export * from "./checkbox"; export * from "./color-password"; From 4eab97272f0f21fdad096d1a4f9814b0134115ab Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Tue, 10 Jan 2023 08:59:13 -0600 Subject: [PATCH 144/205] [EC-694] Verify Email - Replace Bootstrap with Tailwind (#4211) * [EC-694] Replace Boostrap with Tailwind * [EC-694] Simplify tailwind classes * [EC-694] Update bitAction handler method to remove Promise wrapper * [EC-694] Coerce bitButton block boolean * [EC-694] Remove unnecessary try/catch and logging * [EC-694] Coersce block boolean * [EC-694] Update boolean coercion * [EC-694] Apply default value for block boolean and simplify attr class conditional * [EC-694] Fix block class application / test --- .../app/settings/verify-email.component.html | 20 +++++-------------- .../app/settings/verify-email.component.ts | 16 +++------------ .../components/src/button/button.component.ts | 17 ++++++++++++---- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/apps/web/src/app/settings/verify-email.component.html b/apps/web/src/app/settings/verify-email.component.html index d1ec7aed2f1..6fd2128651b 100644 --- a/apps/web/src/app/settings/verify-email.component.html +++ b/apps/web/src/app/settings/verify-email.component.html @@ -1,21 +1,11 @@ -
-
+
+
{{ "verifyEmail" | i18n }}
-
+

{{ "verifyEmailDesc" | i18n }}

-
diff --git a/apps/web/src/app/settings/verify-email.component.ts b/apps/web/src/app/settings/verify-email.component.ts index c49b377968b..9580a71e22a 100644 --- a/apps/web/src/app/settings/verify-email.component.ts +++ b/apps/web/src/app/settings/verify-email.component.ts @@ -39,17 +39,7 @@ export class VerifyEmailComponent { ); } - async send() { - if (this.actionPromise != null) { - return; - } - - try { - this.actionPromise = this.verifyEmail(); - await this.actionPromise; - } catch (e) { - this.logService.error(e); - } - this.actionPromise = null; - } + send = async () => { + await this.verifyEmail(); + }; } diff --git a/libs/components/src/button/button.component.ts b/libs/components/src/button/button.component.ts index 0f3589ebf74..aa26143dc07 100644 --- a/libs/components/src/button/button.component.ts +++ b/libs/components/src/button/button.component.ts @@ -1,3 +1,4 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { Input, HostBinding, Component } from "@angular/core"; import { ButtonLikeAbstraction, ButtonType } from "../shared/button-like.abstraction"; @@ -68,9 +69,7 @@ export class ButtonComponent implements ButtonLikeAbstraction { "hover:tw-no-underline", "focus:tw-outline-none", ] - .concat( - this.block == null || this.block === false ? ["tw-inline-block"] : ["tw-w-full", "tw-block"] - ) + .concat(this.block ? ["tw-w-full", "tw-block"] : ["tw-inline-block"]) .concat(buttonStyles[this.buttonType ?? "secondary"]); } @@ -81,7 +80,17 @@ export class ButtonComponent implements ButtonLikeAbstraction { } @Input() buttonType: ButtonType; - @Input() block?: boolean; + + private _block = false; + + @Input() + get block(): boolean { + return this._block; + } + + set block(value: boolean | "") { + this._block = coerceBooleanProperty(value); + } @Input() loading = false; From d08f93774bddff0b681a2f00982c99fbd0239b97 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Tue, 10 Jan 2023 12:52:06 -0800 Subject: [PATCH 145/205] update padding and icon styles (#4438) --- libs/components/src/banner/banner.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/components/src/banner/banner.component.html b/libs/components/src/banner/banner.component.html index 8ce7da97ed7..0a8e5c6d395 100644 --- a/libs/components/src/banner/banner.component.html +++ b/libs/components/src/banner/banner.component.html @@ -1,10 +1,10 @@
- + From 74140e99d911d32daf4f22a88ffe3d2ac6c6ab68 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Wed, 11 Jan 2023 07:35:52 +1000 Subject: [PATCH 146/205] Let a single radio button option be disabled (#4419) --- .../src/radio-button/radio-button.component.html | 2 +- .../src/radio-button/radio-button.component.ts | 3 ++- .../src/radio-button/radio-button.stories.ts | 11 +++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/libs/components/src/radio-button/radio-button.component.html b/libs/components/src/radio-button/radio-button.component.html index 6ecbbff862d..2bcd4c0d5f1 100644 --- a/libs/components/src/radio-button/radio-button.component.html +++ b/libs/components/src/radio-button/radio-button.component.html @@ -4,7 +4,7 @@ bitRadio [id]="inputId" [name]="name" - [disabled]="disabled" + [disabled]="groupDisabled || disabled" [value]="value" [checked]="selected" (change)="onInputChange()" diff --git a/libs/components/src/radio-button/radio-button.component.ts b/libs/components/src/radio-button/radio-button.component.ts index 32aa6e5577a..8da02488d47 100644 --- a/libs/components/src/radio-button/radio-button.component.ts +++ b/libs/components/src/radio-button/radio-button.component.ts @@ -11,6 +11,7 @@ let nextId = 0; export class RadioButtonComponent { @HostBinding("attr.id") @Input() id = `bit-radio-button-${nextId++}`; @Input() value: unknown; + @Input() disabled = false; constructor(private groupComponent: RadioGroupComponent) {} @@ -26,7 +27,7 @@ export class RadioButtonComponent { return this.groupComponent.selected === this.value; } - get disabled() { + get groupDisabled() { return this.groupComponent.disabled; } diff --git a/libs/components/src/radio-button/radio-button.stories.ts b/libs/components/src/radio-button/radio-button.stories.ts index d2689e84d2c..4990c6b6ce0 100644 --- a/libs/components/src/radio-button/radio-button.stories.ts +++ b/libs/components/src/radio-button/radio-button.stories.ts @@ -14,7 +14,7 @@ const template = ` Group of radio buttons First Second - Third + Third `; @@ -41,7 +41,7 @@ class ExampleComponent { this.formObj.patchValue({ radio: value }); } - @Input() set disabled(disable: boolean) { + @Input() set groupDisabled(disable: boolean) { if (disable) { this.formObj.disable(); } else { @@ -49,6 +49,8 @@ class ExampleComponent { } } + @Input() optionDisabled = false; + constructor(private formBuilder: FormBuilder) {} } @@ -81,7 +83,8 @@ export default { }, args: { selected: TestValue.First, - disabled: false, + groupDisabled: false, + optionDisabled: false, label: true, }, argTypes: { @@ -101,7 +104,7 @@ export default { const DefaultTemplate: Story = (args: ExampleComponent) => ({ props: args, - template: ``, + template: ``, }); export const Default = DefaultTemplate.bind({}); From cbd54c549f4b301bfb7cfc3f0b8a76b5cabe6a79 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Jan 2023 09:56:56 +0100 Subject: [PATCH 147/205] Bumped browser version to 2023.1.0 (#4443) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/package.json | 2 +- apps/browser/src/manifest.json | 2 +- apps/browser/src/manifest.v3.json | 2 +- package-lock.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index b986cbd010b..d3f64632bc8 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2022.12.1", + "version": "2023.1.0", "scripts": { "build": "webpack", "build:mv3": "cross-env MANIFEST_VERSION=3 webpack", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index d1d2f16b36f..ae3d2fb9ec3 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2022.12.1", + "version": "2023.1.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 915096694c9..ca415e90fab 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2022.12.1", + "version": "2023.1.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/package-lock.json b/package-lock.json index 63c42dd0777..43829843a69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -177,7 +177,7 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2022.12.1" + "version": "2023.1.0" }, "apps/cli": { "name": "@bitwarden/cli", From 25b40ac78de8a03c1b30c1c5e324c64d25248562 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Jan 2023 09:57:16 +0100 Subject: [PATCH 148/205] Bumped desktop version to 2023.1.0 (#4444) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 52584f57f94..f3b75f31c97 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2022.12.1", + "version": "2023.1.0", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 5d4d7f9c7a3..0c31746a148 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2022.12.1", + "version": "2023.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2022.12.1", + "version": "2023.1.0", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index e893f4ce025..ffc08270cf6 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2022.12.1", + "version": "2023.1.0", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 43829843a69..41f5174fcbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -216,7 +216,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2022.12.1", + "version": "2023.1.0", "hasInstallScript": true, "license": "GPL-3.0" }, From 2b675638683e4d51bf28d00e374abd4286e68d8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Jan 2023 09:57:52 +0100 Subject: [PATCH 149/205] Bumped cli version to 2023.1.0 (#4445) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/cli/package-lock.json | 4 ++-- apps/cli/package.json | 2 +- package-lock.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/cli/package-lock.json b/apps/cli/package-lock.json index 4d3d97301d8..952755e2c3b 100644 --- a/apps/cli/package-lock.json +++ b/apps/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/cli", - "version": "2022.11.0", + "version": "2023.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@bitwarden/cli", - "version": "2022.11.0", + "version": "2023.1.0", "license": "GPL-3.0-only", "dependencies": { "@koa/multer": "^3.0.0", diff --git a/apps/cli/package.json b/apps/cli/package.json index 9294dae2ca3..1a2816d72c0 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2022.11.0", + "version": "2023.1.0", "keywords": [ "bitwarden", "password", diff --git a/package-lock.json b/package-lock.json index 41f5174fcbe..f85d8341bea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -181,7 +181,7 @@ }, "apps/cli": { "name": "@bitwarden/cli", - "version": "2022.11.0", + "version": "2023.1.0", "license": "GPL-3.0-only", "dependencies": { "@koa/multer": "^3.0.0", From 4be2989fec2f191a00f5b2397e6fe624c8866ea0 Mon Sep 17 00:00:00 2001 From: Matt Bishop Date: Wed, 11 Jan 2023 09:01:02 -0500 Subject: [PATCH 150/205] Upload and process test results as an artifact and report (#4435) --- .github/workflows/test.yml | 16 ++++++++++----- .gitignore | 1 + jest.config.js | 2 ++ package-lock.json | 40 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b0ca237c0cc..44f5325ce02 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,8 +53,16 @@ jobs: done - name: Run tests - run: | - npm run test + run: npm run test + + - name: Report test results + uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 + if: always() + with: + name: Test Results + path: "junit.xml" + reporter: jest-junit + fail-on-error: true rust: name: rust - ${{ matrix.os }} @@ -102,6 +110,4 @@ jobs: - name: Test Windows / macOS if: ${{ matrix.os!='ubuntu-latest' }} working-directory: ./apps/desktop/desktop_native - run: | - cargo test -- --test-threads=1 - + run: cargo test -- --test-threads=1 diff --git a/.gitignore b/.gitignore index d7f237aa071..11a4d4c80ff 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ build # Testing coverage +junit.xml # Misc *.crx diff --git a/jest.config.js b/jest.config.js index 13eefb7b7df..ae9de057fb6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,6 +3,8 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("./tsconfig"); module.exports = { + reporters: ["default", "jest-junit"], + collectCoverage: true, coverageReporters: ["html", "lcov"], coverageDirectory: "coverage", diff --git a/package-lock.json b/package-lock.json index f85d8341bea..988b7991ba8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -138,6 +138,7 @@ "html-webpack-injector": "^1.1.4", "html-webpack-plugin": "^5.5.0", "husky": "^8.0.1", + "jest-junit": "^15.0.0", "jest-mock-extended": "2.0.6", "jest-preset-angular": "^12.1.0", "lint-staged": "^13.0.3", @@ -28370,6 +28371,21 @@ "node": ">= 10.13.0" } }, + "node_modules/jest-junit": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-15.0.0.tgz", + "integrity": "sha512-Z5sVX0Ag3HZdMUnD5DFlG+1gciIFSy7yIVPhOdGUi8YJaI9iLvvBb530gtQL2CHmv0JJeiwRZenr0VrSR7frvg==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/jest-leak-detector": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", @@ -43046,6 +43062,12 @@ "node": ">=8" } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, "node_modules/xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", @@ -64858,6 +64880,18 @@ } } }, + "jest-junit": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-15.0.0.tgz", + "integrity": "sha512-Z5sVX0Ag3HZdMUnD5DFlG+1gciIFSy7yIVPhOdGUi8YJaI9iLvvBb530gtQL2CHmv0JJeiwRZenr0VrSR7frvg==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + } + }, "jest-leak-detector": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", @@ -76245,6 +76279,12 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "dev": true }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/package.json b/package.json index 9a8d1b8953e..e2ecaef0e44 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "html-webpack-injector": "^1.1.4", "html-webpack-plugin": "^5.5.0", "husky": "^8.0.1", + "jest-junit": "^15.0.0", "jest-mock-extended": "2.0.6", "jest-preset-angular": "^12.1.0", "lint-staged": "^13.0.3", From 794f1193dbaedce2ec197c3320aa341bc69e4780 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Wed, 11 Jan 2023 10:47:43 -0500 Subject: [PATCH 151/205] [SM-303] feat: add fadeIn animation to bit-dialog (#4309) --- libs/components/src/dialog/animations.ts | 11 +++++++++++ .../src/dialog/dialog/dialog.component.html | 1 + libs/components/src/dialog/dialog/dialog.component.ts | 3 +++ .../dialog/simple-dialog/simple-dialog.component.html | 1 + .../dialog/simple-dialog/simple-dialog.component.ts | 3 +++ 5 files changed, 19 insertions(+) create mode 100644 libs/components/src/dialog/animations.ts diff --git a/libs/components/src/dialog/animations.ts b/libs/components/src/dialog/animations.ts new file mode 100644 index 00000000000..56f0c971277 --- /dev/null +++ b/libs/components/src/dialog/animations.ts @@ -0,0 +1,11 @@ +import { style, animate, trigger, transition, group } from "@angular/animations"; + +export const fadeIn = trigger("fadeIn", [ + transition(":enter", [ + style({ opacity: 0, transform: "translateY(-50px)" }), + group([ + animate("0.15s linear", style({ opacity: 1 })), + animate("0.3s ease-out", style({ transform: "none" })), + ]), + ]), +]); diff --git a/libs/components/src/dialog/dialog/dialog.component.html b/libs/components/src/dialog/dialog/dialog.component.html index 271d923079e..cce5ad3e267 100644 --- a/libs/components/src/dialog/dialog/dialog.component.html +++ b/libs/components/src/dialog/dialog/dialog.component.html @@ -1,6 +1,7 @@
diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts index 48a4d62c833..fa4e359bce0 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts @@ -1,11 +1,14 @@ import { Component, ContentChild, Directive } from "@angular/core"; +import { fadeIn } from "../animations"; + @Directive({ selector: "[bit-dialog-icon]" }) export class IconDirective {} @Component({ selector: "bit-simple-dialog", templateUrl: "./simple-dialog.component.html", + animations: [fadeIn], }) export class SimpleDialogComponent { @ContentChild(IconDirective) icon!: IconDirective; From fa13cab220de8c88c1ebdba0e00353b660a61f9a Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Wed, 11 Jan 2023 17:12:56 +0100 Subject: [PATCH 152/205] Policy-Api: Remove dependency on OrgService (#4431) --- apps/browser/src/background/main.background.ts | 3 +-- libs/angular/src/services/jslib-services.module.ts | 7 +------ libs/common/src/services/policy/policy-api.service.ts | 4 +--- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index aea7f9600d9..2fc72ede72f 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -328,8 +328,7 @@ export default class MainBackground { this.policyApiService = new PolicyApiService( this.policyService, this.apiService, - this.stateService, - this.organizationService + this.stateService ); this.keyConnectorService = new KeyConnectorService( this.stateService, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 245a1804604..d1d01c3bc9b 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -494,12 +494,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; { provide: PolicyApiServiceAbstraction, useClass: PolicyApiService, - deps: [ - PolicyServiceAbstraction, - ApiServiceAbstraction, - StateServiceAbstraction, - OrganizationServiceAbstraction, - ], + deps: [PolicyServiceAbstraction, ApiServiceAbstraction, StateServiceAbstraction], }, { provide: SendServiceAbstraction, diff --git a/libs/common/src/services/policy/policy-api.service.ts b/libs/common/src/services/policy/policy-api.service.ts index f74042a36f8..98848692dda 100644 --- a/libs/common/src/services/policy/policy-api.service.ts +++ b/libs/common/src/services/policy/policy-api.service.ts @@ -1,7 +1,6 @@ import { firstValueFrom } from "rxjs"; import { ApiService } from "../../abstractions/api.service"; -import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "../../abstractions/policy/policy-api.service.abstraction"; import { InternalPolicyService } from "../../abstractions/policy/policy.service.abstraction"; import { StateService } from "../../abstractions/state.service"; @@ -16,8 +15,7 @@ export class PolicyApiService implements PolicyApiServiceAbstraction { constructor( private policyService: InternalPolicyService, private apiService: ApiService, - private stateService: StateService, - private organizationService: OrganizationService + private stateService: StateService ) {} async getPolicy(organizationId: string, type: PolicyType): Promise { From d138cb90ab91af7d7ca49b1efd55fd7cd2207330 Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Wed, 11 Jan 2023 17:33:00 +0100 Subject: [PATCH 153/205] Reports remove unneeded dependency on stateService (#4426) --- .../tools/exposed-passwords-report.component.ts | 11 +---------- .../tools/inactive-two-factor-report.component.ts | 11 +---------- .../tools/unsecured-websites-report.component.ts | 4 +--- .../tools/weak-passwords-report.component.ts | 3 --- .../src/app/reports/pages/cipher-report.component.ts | 2 -- .../pages/exposed-passwords-report.component.ts | 4 +--- .../pages/inactive-two-factor-report.component.ts | 4 +--- .../pages/reused-passwords-report.component.ts | 2 +- .../pages/unsecured-websites-report.component.ts | 4 +--- .../reports/pages/weak-passwords-report.component.ts | 4 +--- 10 files changed, 8 insertions(+), 41 deletions(-) diff --git a/apps/web/src/app/organizations/tools/exposed-passwords-report.component.ts b/apps/web/src/app/organizations/tools/exposed-passwords-report.component.ts index af4cc8b4303..703ac038d72 100644 --- a/apps/web/src/app/organizations/tools/exposed-passwords-report.component.ts +++ b/apps/web/src/app/organizations/tools/exposed-passwords-report.component.ts @@ -7,7 +7,6 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; import { Cipher } from "@bitwarden/common/models/domain/cipher"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; @@ -27,19 +26,11 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC auditService: AuditService, modalService: ModalService, messagingService: MessagingService, - stateService: StateService, private organizationService: OrganizationService, private route: ActivatedRoute, passwordRepromptService: PasswordRepromptService ) { - super( - cipherService, - auditService, - modalService, - messagingService, - stateService, - passwordRepromptService - ); + super(cipherService, auditService, modalService, messagingService, passwordRepromptService); } ngOnInit() { diff --git a/apps/web/src/app/organizations/tools/inactive-two-factor-report.component.ts b/apps/web/src/app/organizations/tools/inactive-two-factor-report.component.ts index 4675c88dde9..5b6c3be08d2 100644 --- a/apps/web/src/app/organizations/tools/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/organizations/tools/inactive-two-factor-report.component.ts @@ -7,7 +7,6 @@ import { LogService } from "@bitwarden/common/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; // eslint-disable-next-line no-restricted-imports @@ -23,20 +22,12 @@ export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorRepor cipherService: CipherService, modalService: ModalService, messagingService: MessagingService, - stateService: StateService, private route: ActivatedRoute, logService: LogService, passwordRepromptService: PasswordRepromptService, private organizationService: OrganizationService ) { - super( - cipherService, - modalService, - messagingService, - stateService, - logService, - passwordRepromptService - ); + super(cipherService, modalService, messagingService, logService, passwordRepromptService); } async ngOnInit() { diff --git a/apps/web/src/app/organizations/tools/unsecured-websites-report.component.ts b/apps/web/src/app/organizations/tools/unsecured-websites-report.component.ts index 4323f82cb00..a2d46e6ae2a 100644 --- a/apps/web/src/app/organizations/tools/unsecured-websites-report.component.ts +++ b/apps/web/src/app/organizations/tools/unsecured-websites-report.component.ts @@ -6,7 +6,6 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; // eslint-disable-next-line no-restricted-imports @@ -22,12 +21,11 @@ export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesRepor cipherService: CipherService, modalService: ModalService, messagingService: MessagingService, - stateService: StateService, private route: ActivatedRoute, private organizationService: OrganizationService, passwordRepromptService: PasswordRepromptService ) { - super(cipherService, modalService, messagingService, stateService, passwordRepromptService); + super(cipherService, modalService, messagingService, passwordRepromptService); } async ngOnInit() { diff --git a/apps/web/src/app/organizations/tools/weak-passwords-report.component.ts b/apps/web/src/app/organizations/tools/weak-passwords-report.component.ts index af2345918b9..ccf6bfb5e00 100644 --- a/apps/web/src/app/organizations/tools/weak-passwords-report.component.ts +++ b/apps/web/src/app/organizations/tools/weak-passwords-report.component.ts @@ -7,7 +7,6 @@ import { MessagingService } from "@bitwarden/common/abstractions/messaging.servi import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; import { Cipher } from "@bitwarden/common/models/domain/cipher"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; @@ -27,7 +26,6 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone passwordGenerationService: PasswordGenerationService, modalService: ModalService, messagingService: MessagingService, - stateService: StateService, private route: ActivatedRoute, private organizationService: OrganizationService, passwordRepromptService: PasswordRepromptService @@ -37,7 +35,6 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone passwordGenerationService, modalService, messagingService, - stateService, passwordRepromptService ); } diff --git a/apps/web/src/app/reports/pages/cipher-report.component.ts b/apps/web/src/app/reports/pages/cipher-report.component.ts index baf2ceb5635..baec67eee35 100644 --- a/apps/web/src/app/reports/pages/cipher-report.component.ts +++ b/apps/web/src/app/reports/pages/cipher-report.component.ts @@ -3,7 +3,6 @@ import { Directive, ViewChild, ViewContainerRef } from "@angular/core"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType"; import { Organization } from "@bitwarden/common/models/domain/organization"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; @@ -25,7 +24,6 @@ export class CipherReportComponent { private modalService: ModalService, protected messagingService: MessagingService, public requiresPaid: boolean, - private stateService: StateService, protected passwordRepromptService: PasswordRepromptService ) {} diff --git a/apps/web/src/app/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/reports/pages/exposed-passwords-report.component.ts index 99a707da5d2..5f51baa0024 100644 --- a/apps/web/src/app/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/reports/pages/exposed-passwords-report.component.ts @@ -5,7 +5,6 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CipherType } from "@bitwarden/common/enums/cipherType"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; @@ -23,10 +22,9 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple protected auditService: AuditService, modalService: ModalService, messagingService: MessagingService, - stateService: StateService, passwordRepromptService: PasswordRepromptService ) { - super(modalService, messagingService, true, stateService, passwordRepromptService); + super(modalService, messagingService, true, passwordRepromptService); } ngOnInit() { diff --git a/apps/web/src/app/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/reports/pages/inactive-two-factor-report.component.ts index 897746ff950..0beaa53035d 100644 --- a/apps/web/src/app/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/reports/pages/inactive-two-factor-report.component.ts @@ -5,7 +5,6 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CipherType } from "@bitwarden/common/enums/cipherType"; import { Utils } from "@bitwarden/common/misc/utils"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; @@ -24,11 +23,10 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl protected cipherService: CipherService, modalService: ModalService, messagingService: MessagingService, - stateService: StateService, private logService: LogService, passwordRepromptService: PasswordRepromptService ) { - super(modalService, messagingService, true, stateService, passwordRepromptService); + super(modalService, messagingService, true, passwordRepromptService); } async ngOnInit() { diff --git a/apps/web/src/app/reports/pages/reused-passwords-report.component.ts b/apps/web/src/app/reports/pages/reused-passwords-report.component.ts index 83e9229341f..adcfa0ec5c2 100644 --- a/apps/web/src/app/reports/pages/reused-passwords-report.component.ts +++ b/apps/web/src/app/reports/pages/reused-passwords-report.component.ts @@ -24,7 +24,7 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem stateService: StateService, passwordRepromptService: PasswordRepromptService ) { - super(modalService, messagingService, true, stateService, passwordRepromptService); + super(modalService, messagingService, true, passwordRepromptService); } async ngOnInit() { diff --git a/apps/web/src/app/reports/pages/unsecured-websites-report.component.ts b/apps/web/src/app/reports/pages/unsecured-websites-report.component.ts index 22e2c1ddede..3918116c221 100644 --- a/apps/web/src/app/reports/pages/unsecured-websites-report.component.ts +++ b/apps/web/src/app/reports/pages/unsecured-websites-report.component.ts @@ -4,7 +4,6 @@ import { ModalService } from "@bitwarden/angular/services/modal.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CipherType } from "@bitwarden/common/enums/cipherType"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; @@ -19,10 +18,9 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl protected cipherService: CipherService, modalService: ModalService, messagingService: MessagingService, - stateService: StateService, passwordRepromptService: PasswordRepromptService ) { - super(modalService, messagingService, true, stateService, passwordRepromptService); + super(modalService, messagingService, true, passwordRepromptService); } async ngOnInit() { diff --git a/apps/web/src/app/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/reports/pages/weak-passwords-report.component.ts index b6a2278285f..6d781784266 100644 --- a/apps/web/src/app/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/reports/pages/weak-passwords-report.component.ts @@ -5,7 +5,6 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CipherType } from "@bitwarden/common/enums/cipherType"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; import { BadgeTypes } from "@bitwarden/components"; @@ -26,10 +25,9 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen protected passwordGenerationService: PasswordGenerationService, modalService: ModalService, messagingService: MessagingService, - stateService: StateService, passwordRepromptService: PasswordRepromptService ) { - super(modalService, messagingService, true, stateService, passwordRepromptService); + super(modalService, messagingService, true, passwordRepromptService); } async ngOnInit() { From 44851fe231de3c52dfd6608f1cca0f2878041446 Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Wed, 11 Jan 2023 20:47:26 +0100 Subject: [PATCH 154/205] [PS-2238] Hide get android submenu on MacStore release (#4449) * Hide get android submenu on MacStore release * Add comment why Android is hidden on the MacAppStore release Co-authored-by: Oscar Hinton Co-authored-by: Oscar Hinton --- apps/desktop/src/main/menu/menu.help.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/desktop/src/main/menu/menu.help.ts b/apps/desktop/src/main/menu/menu.help.ts index fde742b3737..2abab06dfab 100644 --- a/apps/desktop/src/main/menu/menu.help.ts +++ b/apps/desktop/src/main/menu/menu.help.ts @@ -166,6 +166,7 @@ export class HelpMenu implements IMenubarMenu { { id: "android", label: "Android", + visible: !isMacAppStore(), // Apple Guideline 2.3.10 - Accurate Metadata click: () => { shell.openExternal( "https://play.google.com/store/apps/" + "details?id=com.x8bit.bitwarden" From 51ead2e7da7b24122dff2f0f90cae4c13133096e Mon Sep 17 00:00:00 2001 From: aj-rosado <109146700+aj-rosado@users.noreply.github.com> Date: Wed, 11 Jan 2023 22:47:26 +0000 Subject: [PATCH 155/205] [PS-2120] Forcing vault to refresh when the vault is purged or new items are imported (#4380) * [PS-2120] Forcing vault to refresh when the vault is purged or new items are imported * [PS-2120] Forcing vault refresh by calling fullSync with force as true --- .../tools/import-export/org-import.component.ts | 7 +++++-- apps/web/src/app/settings/purge-vault.component.ts | 5 ++++- apps/web/src/app/tools/import-export/import.component.ts | 5 ++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/web/src/app/organizations/tools/import-export/org-import.component.ts b/apps/web/src/app/organizations/tools/import-export/org-import.component.ts index 81a16a29680..2e70908cd87 100644 --- a/apps/web/src/app/organizations/tools/import-export/org-import.component.ts +++ b/apps/web/src/app/organizations/tools/import-export/org-import.component.ts @@ -8,6 +8,7 @@ import { LogService } from "@bitwarden/common/abstractions/log.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; +import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { ImportComponent } from "../../../tools/import-export/import.component"; @@ -28,7 +29,8 @@ export class OrganizationImportComponent extends ImportComponent { policyService: PolicyService, private organizationService: OrganizationService, logService: LogService, - modalService: ModalService + modalService: ModalService, + syncService: SyncService ) { super( i18nService, @@ -37,7 +39,8 @@ export class OrganizationImportComponent extends ImportComponent { platformUtilsService, policyService, logService, - modalService + modalService, + syncService ); } diff --git a/apps/web/src/app/settings/purge-vault.component.ts b/apps/web/src/app/settings/purge-vault.component.ts index e10782914f0..52cb89e4c61 100644 --- a/apps/web/src/app/settings/purge-vault.component.ts +++ b/apps/web/src/app/settings/purge-vault.component.ts @@ -5,6 +5,7 @@ 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 { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { Verification } from "@bitwarden/common/types/verification"; @@ -24,7 +25,8 @@ export class PurgeVaultComponent { private platformUtilsService: PlatformUtilsService, private userVerificationService: UserVerificationService, private router: Router, - private logService: LogService + private logService: LogService, + private syncService: SyncService ) {} async submit() { @@ -34,6 +36,7 @@ export class PurgeVaultComponent { .then((request) => this.apiService.postPurgeCiphers(request, this.organizationId)); await this.formPromise; this.platformUtilsService.showToast("success", null, this.i18nService.t("vaultPurged")); + this.syncService.fullSync(true); if (this.organizationId != null) { this.router.navigate(["organizations", this.organizationId, "vault"]); } else { 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 3abaebe2bce..da9a27542cf 100644 --- a/apps/web/src/app/tools/import-export/import.component.ts +++ b/apps/web/src/app/tools/import-export/import.component.ts @@ -10,6 +10,7 @@ import { ImportService } from "@bitwarden/common/abstractions/import.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; +import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { ImportOption, ImportType } from "@bitwarden/common/enums/importOptions"; import { PolicyType } from "@bitwarden/common/enums/policyType"; import { ImportError } from "@bitwarden/common/importers/import-error"; @@ -40,7 +41,8 @@ export class ImportComponent implements OnInit { protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService, private logService: LogService, - protected modalService: ModalService + protected modalService: ModalService, + protected syncService: SyncService ) {} async ngOnInit() { @@ -133,6 +135,7 @@ export class ImportComponent implements OnInit { //No errors, display success message this.platformUtilsService.showToast("success", null, this.i18nService.t("importSuccess")); + this.syncService.fullSync(true); this.router.navigate(this.successNavigate); } catch (e) { this.logService.error(e); From 4e0c26ddb8e103b48a5a565988047b920b8f0b2c Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 11 Jan 2023 17:51:00 -0800 Subject: [PATCH 156/205] [EC-891] Hide password character count button for hidden passwords (#4454) --- apps/web/src/app/vault/add-edit.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/app/vault/add-edit.component.html b/apps/web/src/app/vault/add-edit.component.html index 89b592110a1..7aff253e418 100644 --- a/apps/web/src/app/vault/add-edit.component.html +++ b/apps/web/src/app/vault/add-edit.component.html @@ -134,6 +134,7 @@ appStopClick [appA11yTitle]="'toggleCharacterCount' | i18n" (click)="togglePasswordCount()" + *ngIf="cipher.viewPassword" > From 23ec317767e92045a835c0d8021c97f5c48e1834 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 12 Jan 2023 13:23:14 +1000 Subject: [PATCH 157/205] [EC-648] Fix esm features in node testing environment (#4223) * Add AST transformer to remove import.meta in tests --- apps/web/src/app/app.module.ts | 2 +- apps/web/src/app/core/index.ts | 5 +-- .../bit-web/src/app/app.module.ts | 2 +- libs/shared/es2020-transformer.ts | 36 +++++++++++++++++++ libs/shared/jest.config.base.js | 3 ++ 5 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 libs/shared/es2020-transformer.ts diff --git a/apps/web/src/app/app.module.ts b/apps/web/src/app/app.module.ts index 0f29ea14fa7..5d1afd21224 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/core.module"; +import { CoreModule } from "./core"; 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 f9a92f361cc..80c1a44d50f 100644 --- a/apps/web/src/app/core/index.ts +++ b/apps/web/src/app/core/index.ts @@ -1,7 +1,4 @@ -// 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 "./core.module"; export * from "./event.service"; export * from "./policy-list.service"; export * from "./router.service"; diff --git a/bitwarden_license/bit-web/src/app/app.module.ts b/bitwarden_license/bit-web/src/app/app.module.ts index 9c8745d6b27..c26b04ff309 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/core.module"; +import { CoreModule } from "@bitwarden/web-vault/app/core"; 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/shared/es2020-transformer.ts b/libs/shared/es2020-transformer.ts new file mode 100644 index 00000000000..3a26e1c0c2e --- /dev/null +++ b/libs/shared/es2020-transformer.ts @@ -0,0 +1,36 @@ +import * as ts from "typescript"; + +// Custom Typescript AST transformer for use with ts-jest / jest-preset-angular +// Removes specified ES2020 syntax from source code, as node does not support it yet +// Reference: https://kulshekhar.github.io/ts-jest/docs/getting-started/options/astTransformers +// Use this tool to understand how we identify and filter AST nodes: https://ts-ast-viewer.com/ + +/** + * Remember to increase the version whenever transformer's content is changed. This is to inform Jest to not reuse + * the previous cache which contains old transformer's content + */ +export const version = 1; +export const name = "bit-es2020-transformer"; + +// Returns true for 'import.meta' statements +const isImportMetaStatement = (node: ts.Node) => + ts.isPropertyAccessExpression(node) && + ts.isMetaProperty(node.expression) && + node.expression.keywordToken === ts.SyntaxKind.ImportKeyword; + +export const factory = function (/*opts?: Opts*/) { + function visitor(ctx: ts.TransformationContext, sf: ts.SourceFile) { + const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult => { + if (isImportMetaStatement(node)) { + return null; + } + + // Continue searching child nodes + return ts.visitEachChild(node, visitor, ctx); + }; + return visitor; + } + return (ctx: ts.TransformationContext): ts.Transformer => { + return (sf: ts.SourceFile) => ts.visitNode(sf, visitor(ctx, sf)); + }; +}; diff --git a/libs/shared/jest.config.base.js b/libs/shared/jest.config.base.js index 056e54e9a7e..e0eda1c4cae 100644 --- a/libs/shared/jest.config.base.js +++ b/libs/shared/jest.config.base.js @@ -19,6 +19,9 @@ module.exports = { // Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code // See https://bitwarden.atlassian.net/browse/EC-497 for more info isolatedModules: true, + astTransformers: { + before: ["/../../libs/shared/es2020-transformer.ts"], + }, }, }, }; From 06aab48ea850c2ccec4c139214737c2dc86d29d2 Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Thu, 12 Jan 2023 09:18:03 -0500 Subject: [PATCH 158/205] reset extra files (#4422) Co-authored-by: William Martin --- .../secrets-manager/secrets/dialog/secret-dialog.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index ebd50fbbd46..938987b54dc 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -64,6 +64,10 @@ export class SecretDialogComponent implements OnInit { .get("project") .valueChanges.pipe(takeUntil(this.destroy$)) .subscribe(() => this.updateProjectList()); + + if (this.data.projectId) { + this.formGroup.get("project").setValue(this.data.projectId); + } } async loadData() { From 508979df89830c9a68661f743e60eea971ebfa91 Mon Sep 17 00:00:00 2001 From: Brandon Maharaj Date: Thu, 12 Jan 2023 13:25:05 -0500 Subject: [PATCH 159/205] fix: fixed path (#4348) --- apps/browser/src/popup/accounts/two-factor.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/popup/accounts/two-factor.component.html b/apps/browser/src/popup/accounts/two-factor.component.html index 71d7c2498dd..5263bf0c401 100644 --- a/apps/browser/src/popup/accounts/two-factor.component.html +++ b/apps/browser/src/popup/accounts/two-factor.component.html @@ -62,7 +62,7 @@

{{ "insertYubiKey" | i18n }}

- +
From 23897ae5fb288a985b5b19c6709ca0128e1e5886 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Thu, 12 Jan 2023 15:39:33 -0500 Subject: [PATCH 160/205] Use Memory Storage directly in Session Sync (#4423) * Use Memory Storage directly in Session Sync * Update apps/browser/src/decorators/session-sync-observable/browser-session.decorator.spec.ts Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * Fix up test Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> --- .../browser/src/background/main.background.ts | 7 ++- .../storage-service.factory.ts | 9 ++-- apps/browser/src/clipboard/clipboard-state.ts | 10 ---- .../browser-session.decorator.spec.ts | 40 ++++++++++++--- .../browser-session.decorator.ts | 51 +++++++++++++------ .../session-syncer.spec.ts | 47 +++++++++-------- .../session-sync-observable/session-syncer.ts | 12 +++-- .../src/popup/services/services.module.ts | 7 ++- .../abstractions/browser-state.service.ts | 5 -- .../services/browser-state.service.spec.ts | 43 ++-------------- .../src/services/browser-state.service.ts | 16 ------ .../localBackedSessionStorage.service.ts | 10 +--- apps/web/src/app/core/state/state.service.ts | 7 ++- libs/angular/src/services/injection-tokens.ts | 7 ++- .../src/abstractions/storage.service.ts | 11 ++-- .../src/services/memoryStorage.service.ts | 14 +++-- libs/common/src/services/state.service.ts | 4 +- 17 files changed, 146 insertions(+), 154 deletions(-) delete mode 100644 apps/browser/src/clipboard/clipboard-state.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 2fc72ede72f..f281fe356dc 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -28,7 +28,10 @@ import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstractions/send.service"; import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service"; -import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; +import { + AbstractMemoryStorageService, + AbstractStorageService, +} from "@bitwarden/common/abstractions/storage.service"; import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/abstractions/sync/syncNotifier.service.abstraction"; import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abstractions/system.service"; @@ -123,7 +126,7 @@ export default class MainBackground { messagingService: MessagingServiceAbstraction; storageService: AbstractStorageService; secureStorageService: AbstractStorageService; - memoryStorageService: AbstractStorageService; + memoryStorageService: AbstractMemoryStorageService; i18nService: I18nServiceAbstraction; platformUtilsService: PlatformUtilsServiceAbstraction; logService: LogServiceAbstraction; diff --git a/apps/browser/src/background/service_factories/storage-service.factory.ts b/apps/browser/src/background/service_factories/storage-service.factory.ts index 51993277c92..c30bda731e6 100644 --- a/apps/browser/src/background/service_factories/storage-service.factory.ts +++ b/apps/browser/src/background/service_factories/storage-service.factory.ts @@ -1,4 +1,7 @@ -import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; +import { + AbstractMemoryStorageService, + AbstractStorageService, +} from "@bitwarden/common/abstractions/storage.service"; import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; import { BrowserApi } from "../../browser/browserApi"; @@ -35,9 +38,9 @@ export function secureStorageServiceFactory( } export function memoryStorageServiceFactory( - cache: { memoryStorageService?: AbstractStorageService } & CachedServices, + cache: { memoryStorageService?: AbstractMemoryStorageService } & CachedServices, opts: MemoryStorageServiceInitOptions -): Promise { +): Promise { return factory(cache, "memoryStorageService", opts, async () => { if (BrowserApi.manifestVersion === 3) { return new LocalBackedSessionStorageService( diff --git a/apps/browser/src/clipboard/clipboard-state.ts b/apps/browser/src/clipboard/clipboard-state.ts deleted file mode 100644 index cfa2f9459f8..00000000000 --- a/apps/browser/src/clipboard/clipboard-state.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BrowserStateService } from "../services/abstractions/browser-state.service"; - -const clearClipboardStorageKey = "clearClipboardTime"; -export const getClearClipboardTime = async (stateService: BrowserStateService) => { - return await stateService.getFromSessionMemory(clearClipboardStorageKey); -}; - -export const setClearClipboardTime = async (stateService: BrowserStateService, time: number) => { - await stateService.setInSessionMemory(clearClipboardStorageKey, time); -}; diff --git a/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.spec.ts b/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.spec.ts index 92c5dfb0170..ab29c86e984 100644 --- a/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.spec.ts +++ b/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.spec.ts @@ -1,5 +1,8 @@ import { BehaviorSubject } from "rxjs"; +import { AbstractMemoryStorageService } from "@bitwarden/common/abstractions/storage.service"; +import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; + import { BrowserStateService } from "../../services/browser-state.service"; import { browserSession } from "./browser-session.decorator"; @@ -11,18 +14,24 @@ import { sessionSync } from "./session-sync.decorator"; jest.mock("./session-syncer"); describe("browserSession decorator", () => { - it("should throw if StateService is not a constructor argument", () => { + it("should throw if neither StateService nor MemoryStorageService is a constructor argument", () => { @browserSession class TestClass {} expect(() => { new TestClass(); }).toThrowError( - "Cannot decorate TestClass with browserSession, Browser's StateService must be injected" + "Cannot decorate TestClass with browserSession, Browser's AbstractMemoryStorageService must be accessible through the observed classes parameters" ); }); it("should create if StateService is a constructor argument", () => { - const stateService = Object.create(BrowserStateService.prototype, {}); + const stateService = Object.create(BrowserStateService.prototype, { + memoryStorageService: { + value: Object.create(MemoryStorageService.prototype, { + type: { value: MemoryStorageService.TYPE }, + }), + }, + }); @browserSession class TestClass { @@ -32,15 +41,28 @@ describe("browserSession decorator", () => { expect(new TestClass(stateService)).toBeDefined(); }); + it("should create if MemoryStorageService is a constructor argument", () => { + const memoryStorageService = Object.create(MemoryStorageService.prototype, { + type: { value: MemoryStorageService.TYPE }, + }); + + @browserSession + class TestClass { + constructor(private memoryStorageService: AbstractMemoryStorageService) {} + } + + expect(new TestClass(memoryStorageService)).toBeDefined(); + }); + describe("interaction with @sessionSync decorator", () => { - let stateService: BrowserStateService; + let memoryStorageService: MemoryStorageService; @browserSession class TestClass { @sessionSync({ initializer: (s: string) => s }) private behaviorSubject = new BehaviorSubject(""); - constructor(private stateService: BrowserStateService) {} + constructor(private memoryStorageService: MemoryStorageService) {} fromJSON(json: any) { this.behaviorSubject.next(json); @@ -48,16 +70,18 @@ describe("browserSession decorator", () => { } beforeEach(() => { - stateService = Object.create(BrowserStateService.prototype, {}) as BrowserStateService; + memoryStorageService = Object.create(MemoryStorageService.prototype, { + type: { value: MemoryStorageService.TYPE }, + }); }); it("should create a session syncer", () => { - const testClass = new TestClass(stateService) as any as SessionStorable; + const testClass = new TestClass(memoryStorageService) as any as SessionStorable; expect(testClass.__sessionSyncers.length).toEqual(1); }); it("should initialize the session syncer", () => { - const testClass = new TestClass(stateService) as any as SessionStorable; + const testClass = new TestClass(memoryStorageService) as any as SessionStorable; expect(testClass.__sessionSyncers[0].init).toHaveBeenCalled(); }); }); diff --git a/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts b/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts index 5d9d56c1d71..dbb45ddba8e 100644 --- a/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts +++ b/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts @@ -1,6 +1,6 @@ import { Constructor } from "type-fest"; -import { BrowserStateService } from "../../services/browser-state.service"; +import { AbstractMemoryStorageService } from "@bitwarden/common/abstractions/storage.service"; import { SessionStorable } from "./session-storable"; import { SessionSyncer } from "./session-syncer"; @@ -22,32 +22,51 @@ export function browserSession>(constructor: TCto super(...args); // Require state service to be injected - const stateService: BrowserStateService = [this as any] - .concat(args) - .find( - (arg) => - typeof arg.setInSessionMemory === "function" && - typeof arg.getFromSessionMemory === "function" - ); - if (!stateService) { - throw new Error( - `Cannot decorate ${constructor.name} with browserSession, Browser's StateService must be injected` - ); - } + const storageService: AbstractMemoryStorageService = this.findStorageService( + [this as any].concat(args) + ); if (this.__syncedItemMetadata == null || !(this.__syncedItemMetadata instanceof Array)) { return; } this.__sessionSyncers = this.__syncedItemMetadata.map((metadata) => - this.buildSyncer(metadata, stateService) + this.buildSyncer(metadata, storageService) ); } - buildSyncer(metadata: SyncedItemMetadata, stateService: BrowserStateService) { - const syncer = new SessionSyncer((this as any)[metadata.propertyKey], stateService, metadata); + buildSyncer(metadata: SyncedItemMetadata, storageSerice: AbstractMemoryStorageService) { + const syncer = new SessionSyncer( + (this as any)[metadata.propertyKey], + storageSerice, + metadata + ); syncer.init(); return syncer; } + + findStorageService(args: any[]): AbstractMemoryStorageService { + const storageService = args.find(this.isMemoryStorageService); + + if (storageService) { + return storageService; + } + + const stateService = args.find( + (arg) => + arg?.memoryStorageService != null && this.isMemoryStorageService(arg.memoryStorageService) + ); + if (stateService) { + return stateService.memoryStorageService; + } + + throw new Error( + `Cannot decorate ${constructor.name} with browserSession, Browser's AbstractMemoryStorageService must be accessible through the observed classes parameters` + ); + } + + isMemoryStorageService(arg: any): arg is AbstractMemoryStorageService { + return arg.type != null && arg.type === AbstractMemoryStorageService.TYPE; + } }; } diff --git a/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts b/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts index c37b640f3f9..05d0da7527b 100644 --- a/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts +++ b/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts @@ -1,9 +1,10 @@ -import { awaitAsync, awaitAsync as flushAsyncObservables } from "@bitwarden/angular/../test-utils"; +import { awaitAsync } from "@bitwarden/angular/../test-utils"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, ReplaySubject } from "rxjs"; +import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; + import { BrowserApi } from "../../browser/browserApi"; -import { BrowserStateService } from "../../services/abstractions/browser-state.service"; import { SessionSyncer } from "./session-syncer"; import { SyncedItemMetadata } from "./sync-item-metadata"; @@ -17,7 +18,7 @@ describe("session syncer", () => { initializer: (s: string) => s, initializeAs: "object", }; - let stateService: MockProxy; + let storageService: MockProxy; let sut: SessionSyncer; let behaviorSubject: BehaviorSubject; @@ -29,9 +30,9 @@ describe("session syncer", () => { manifest_version: 3, }); - stateService = mock(); - stateService.hasInSessionMemory.mockResolvedValue(false); - sut = new SessionSyncer(behaviorSubject, stateService, metaData); + storageService = mock(); + storageService.has.mockResolvedValue(false); + sut = new SessionSyncer(behaviorSubject, storageService, metaData); }); afterEach(() => { @@ -43,13 +44,13 @@ describe("session syncer", () => { describe("constructor", () => { it("should throw if subject is not an instance of Subject", () => { expect(() => { - new SessionSyncer({} as any, stateService, null); + new SessionSyncer({} as any, storageService, null); }).toThrowError("subject must inherit from Subject"); }); it("should create if either ctor or initializer is provided", () => { expect( - new SessionSyncer(behaviorSubject, stateService, { + new SessionSyncer(behaviorSubject, storageService, { propertyKey, sessionKey, ctor: String, @@ -57,7 +58,7 @@ describe("session syncer", () => { }) ).toBeDefined(); expect( - new SessionSyncer(behaviorSubject, stateService, { + new SessionSyncer(behaviorSubject, storageService, { propertyKey, sessionKey, initializer: (s: any) => s, @@ -67,7 +68,7 @@ describe("session syncer", () => { }); it("should throw if neither ctor or initializer is provided", () => { expect(() => { - new SessionSyncer(behaviorSubject, stateService, { + new SessionSyncer(behaviorSubject, storageService, { propertyKey, sessionKey, initializeAs: "object", @@ -82,7 +83,7 @@ describe("session syncer", () => { replaySubject.next("1"); replaySubject.next("2"); replaySubject.next("3"); - sut = new SessionSyncer(replaySubject, stateService, metaData); + sut = new SessionSyncer(replaySubject, storageService, metaData); // block observing the subject jest.spyOn(sut as any, "observe").mockImplementation(); @@ -93,7 +94,7 @@ describe("session syncer", () => { it("should ignore BehaviorSubject's initial value", () => { const behaviorSubject = new BehaviorSubject("initial"); - sut = new SessionSyncer(behaviorSubject, stateService, metaData); + sut = new SessionSyncer(behaviorSubject, storageService, metaData); // block observing the subject jest.spyOn(sut as any, "observe").mockImplementation(); @@ -103,7 +104,7 @@ describe("session syncer", () => { }); it("should grab an initial value from storage if it exists", async () => { - stateService.hasInSessionMemory.mockResolvedValue(true); + storageService.has.mockResolvedValue(true); //Block a call to update const updateSpy = jest.spyOn(sut as any, "update").mockImplementation(); @@ -114,7 +115,7 @@ describe("session syncer", () => { }); it("should not grab an initial value from storage if it does not exist", async () => { - stateService.hasInSessionMemory.mockResolvedValue(false); + storageService.has.mockResolvedValue(false); //Block a call to update const updateSpy = jest.spyOn(sut as any, "update").mockImplementation(); @@ -139,8 +140,8 @@ describe("session syncer", () => { it("should update the session memory", async () => { // await finishing of fire-and-forget operation await new Promise((resolve) => setTimeout(resolve, 100)); - expect(stateService.setInSessionMemory).toHaveBeenCalledTimes(1); - expect(stateService.setInSessionMemory).toHaveBeenCalledWith(sessionKey, "test"); + expect(storageService.save).toHaveBeenCalledTimes(1); + expect(storageService.save).toHaveBeenCalledWith(sessionKey, "test"); }); it("should update sessionSyncers in other contexts", async () => { @@ -170,27 +171,29 @@ describe("session syncer", () => { it("should ignore messages with the wrong command", async () => { await sut.updateFromMessage({ command: "wrong_command", id: sut.id }); - expect(stateService.getFromSessionMemory).not.toHaveBeenCalled(); + expect(storageService.getBypassCache).not.toHaveBeenCalled(); expect(nextSpy).not.toHaveBeenCalled(); }); it("should ignore messages from itself", async () => { await sut.updateFromMessage({ command: `${sessionKey}_update`, id: sut.id }); - expect(stateService.getFromSessionMemory).not.toHaveBeenCalled(); + expect(storageService.getBypassCache).not.toHaveBeenCalled(); expect(nextSpy).not.toHaveBeenCalled(); }); it("should update from message on emit from another instance", async () => { const builder = jest.fn(); jest.spyOn(SyncedItemMetadata, "builder").mockReturnValue(builder); - stateService.getFromSessionMemory.mockResolvedValue("test"); + storageService.getBypassCache.mockResolvedValue("test"); await sut.updateFromMessage({ command: `${sessionKey}_update`, id: "different_id" }); - await flushAsyncObservables(); + await awaitAsync(); - expect(stateService.getFromSessionMemory).toHaveBeenCalledTimes(1); - expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(sessionKey, builder); + expect(storageService.getBypassCache).toHaveBeenCalledTimes(1); + expect(storageService.getBypassCache).toHaveBeenCalledWith(sessionKey, { + deserializer: builder, + }); expect(nextSpy).toHaveBeenCalledTimes(1); expect(nextSpy).toHaveBeenCalledWith("test"); diff --git a/apps/browser/src/decorators/session-sync-observable/session-syncer.ts b/apps/browser/src/decorators/session-sync-observable/session-syncer.ts index 91b371ef817..8c24ffb1852 100644 --- a/apps/browser/src/decorators/session-sync-observable/session-syncer.ts +++ b/apps/browser/src/decorators/session-sync-observable/session-syncer.ts @@ -1,9 +1,9 @@ import { BehaviorSubject, concatMap, ReplaySubject, Subject, Subscription } from "rxjs"; +import { AbstractMemoryStorageService } from "@bitwarden/common/abstractions/storage.service"; import { Utils } from "@bitwarden/common/misc/utils"; import { BrowserApi } from "../../browser/browserApi"; -import { BrowserStateService } from "../../services/abstractions/browser-state.service"; import { SyncedItemMetadata } from "./sync-item-metadata"; @@ -16,7 +16,7 @@ export class SessionSyncer { constructor( private subject: Subject, - private stateService: BrowserStateService, + private memoryStorageService: AbstractMemoryStorageService, private metaData: SyncedItemMetadata ) { if (!(subject instanceof Subject)) { @@ -43,7 +43,7 @@ export class SessionSyncer { this.observe(); // must be synchronous - this.stateService.hasInSessionMemory(this.metaData.sessionKey).then((hasInSessionMemory) => { + this.memoryStorageService.has(this.metaData.sessionKey).then((hasInSessionMemory) => { if (hasInSessionMemory) { this.update(); } @@ -86,13 +86,15 @@ export class SessionSyncer { async update() { const builder = SyncedItemMetadata.builder(this.metaData); - const value = await this.stateService.getFromSessionMemory(this.metaData.sessionKey, builder); + const value = await this.memoryStorageService.getBypassCache(this.metaData.sessionKey, { + deserializer: builder, + }); this.ignoreNUpdates = 1; this.subject.next(value); } private async updateSession(value: any) { - await this.stateService.setInSessionMemory(this.metaData.sessionKey, value); + await this.memoryStorageService.save(this.metaData.sessionKey, value); await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id }); } diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 05cd4ede8f5..677e8648bc8 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -40,7 +40,10 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; import { SettingsService } from "@bitwarden/common/abstractions/settings.service"; import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service"; import { StateMigrationService } from "@bitwarden/common/abstractions/stateMigration.service"; -import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; +import { + AbstractMemoryStorageService, + AbstractStorageService, +} from "@bitwarden/common/abstractions/storage.service"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { TokenService } from "@bitwarden/common/abstractions/token.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service"; @@ -329,7 +332,7 @@ function getBgService(service: keyof MainBackground) { useFactory: ( storageService: AbstractStorageService, secureStorageService: AbstractStorageService, - memoryStorageService: AbstractStorageService, + memoryStorageService: AbstractMemoryStorageService, logService: LogServiceAbstraction, stateMigrationService: StateMigrationService ) => { diff --git a/apps/browser/src/services/abstractions/browser-state.service.ts b/apps/browser/src/services/abstractions/browser-state.service.ts index afe5e2ce694..0c8a35afbd2 100644 --- a/apps/browser/src/services/abstractions/browser-state.service.ts +++ b/apps/browser/src/services/abstractions/browser-state.service.ts @@ -1,5 +1,3 @@ -import { Jsonify } from "type-fest"; - import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service"; import { StorageOptions } from "@bitwarden/common/models/domain/storage-options"; @@ -9,9 +7,6 @@ import { BrowserGroupingsComponentState } from "../../models/browserGroupingsCom import { BrowserSendComponentState } from "../../models/browserSendComponentState"; export abstract class BrowserStateService extends BaseStateServiceAbstraction { - abstract hasInSessionMemory(key: string): Promise; - abstract getFromSessionMemory(key: string, deserializer?: (obj: Jsonify) => T): Promise; - abstract setInSessionMemory(key: string, value: any): Promise; getBrowserGroupingComponentState: ( options?: StorageOptions ) => Promise; diff --git a/apps/browser/src/services/browser-state.service.spec.ts b/apps/browser/src/services/browser-state.service.spec.ts index 7d6f8456314..84ac71664da 100644 --- a/apps/browser/src/services/browser-state.service.spec.ts +++ b/apps/browser/src/services/browser-state.service.spec.ts @@ -1,9 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { - MemoryStorageServiceInterface, + AbstractMemoryStorageService, AbstractStorageService, } from "@bitwarden/common/abstractions/storage.service"; import { SendType } from "@bitwarden/common/enums/sendType"; @@ -18,9 +17,10 @@ import { BrowserComponentState } from "../models/browserComponentState"; import { BrowserGroupingsComponentState } from "../models/browserGroupingsComponentState"; import { BrowserSendComponentState } from "../models/browserSendComponentState"; -import { AbstractKeyGenerationService } from "./abstractions/abstractKeyGeneration.service"; import { BrowserStateService } from "./browser-state.service"; -import { LocalBackedSessionStorageService } from "./localBackedSessionStorage.service"; + +// disable session syncing to just test class +jest.mock("../decorators/session-sync-observable/"); describe("Browser State Service", () => { let secureStorageService: MockProxy; @@ -50,41 +50,8 @@ describe("Browser State Service", () => { state.activeUserId = userId; }); - describe("direct memory storage access", () => { - let memoryStorageService: LocalBackedSessionStorageService; - - beforeEach(() => { - // We need `AbstractCachedStorageService` in the prototype chain to correctly test cache bypass. - memoryStorageService = new LocalBackedSessionStorageService( - mock(), - mock() - ); - - sut = new BrowserStateService( - diskStorageService, - secureStorageService, - memoryStorageService, - logService, - stateMigrationService, - stateFactory, - useAccountCache - ); - }); - - it("should bypass cache if possible", async () => { - const spyBypass = jest - .spyOn(memoryStorageService, "getBypassCache") - .mockResolvedValue("value"); - const spyGet = jest.spyOn(memoryStorageService, "get"); - const result = await sut.getFromSessionMemory("key"); - expect(spyBypass).toHaveBeenCalled(); - expect(spyGet).not.toHaveBeenCalled(); - expect(result).toBe("value"); - }); - }); - describe("state methods", () => { - let memoryStorageService: MockProxy; + let memoryStorageService: MockProxy; beforeEach(() => { memoryStorageService = mock(); diff --git a/apps/browser/src/services/browser-state.service.ts b/apps/browser/src/services/browser-state.service.ts index 57873f23073..5de24a2a4b8 100644 --- a/apps/browser/src/services/browser-state.service.ts +++ b/apps/browser/src/services/browser-state.service.ts @@ -1,7 +1,5 @@ import { BehaviorSubject } from "rxjs"; -import { Jsonify } from "type-fest"; -import { AbstractCachedStorageService } from "@bitwarden/common/abstractions/storage.service"; import { GlobalState } from "@bitwarden/common/models/domain/global-state"; import { StorageOptions } from "@bitwarden/common/models/domain/storage-options"; import { StateService as BaseStateService } from "@bitwarden/common/services/state.service"; @@ -36,20 +34,6 @@ export class BrowserStateService protected accountDeserializer = Account.fromJSON; - async hasInSessionMemory(key: string): Promise { - return await this.memoryStorageService.has(key); - } - - async getFromSessionMemory(key: string, deserializer?: (obj: Jsonify) => T): Promise { - return this.memoryStorageService instanceof AbstractCachedStorageService - ? await this.memoryStorageService.getBypassCache(key, { deserializer: deserializer }) - : await this.memoryStorageService.get(key); - } - - async setInSessionMemory(key: string, value: any): Promise { - await this.memoryStorageService.save(key, value); - } - async addAccount(account: Account) { // Apply browser overrides to default account values account = new Account(account); diff --git a/apps/browser/src/services/localBackedSessionStorage.service.ts b/apps/browser/src/services/localBackedSessionStorage.service.ts index 54b0537f725..662a431b797 100644 --- a/apps/browser/src/services/localBackedSessionStorage.service.ts +++ b/apps/browser/src/services/localBackedSessionStorage.service.ts @@ -1,10 +1,7 @@ import { Jsonify } from "type-fest"; import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; -import { - AbstractCachedStorageService, - MemoryStorageServiceInterface, -} from "@bitwarden/common/abstractions/storage.service"; +import { AbstractMemoryStorageService } from "@bitwarden/common/abstractions/storage.service"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { MemoryStorageOptions } from "@bitwarden/common/models/domain/storage-options"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; @@ -21,10 +18,7 @@ const keys = { sessionKey: "session", }; -export class LocalBackedSessionStorageService - extends AbstractCachedStorageService - implements MemoryStorageServiceInterface -{ +export class LocalBackedSessionStorageService extends AbstractMemoryStorageService { private cache = new Map(); private localStorage = new BrowserLocalStorageService(); private sessionStorage = new BrowserMemoryStorageService(); diff --git a/apps/web/src/app/core/state/state.service.ts b/apps/web/src/app/core/state/state.service.ts index 577301cf867..0b7a56790f4 100644 --- a/apps/web/src/app/core/state/state.service.ts +++ b/apps/web/src/app/core/state/state.service.ts @@ -8,7 +8,10 @@ import { } from "@bitwarden/angular/services/injection-tokens"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { StateMigrationService } from "@bitwarden/common/abstractions/stateMigration.service"; -import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; +import { + AbstractMemoryStorageService, + AbstractStorageService, +} from "@bitwarden/common/abstractions/storage.service"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { CipherData } from "@bitwarden/common/models/data/cipher.data"; import { CollectionData } from "@bitwarden/common/models/data/collection.data"; @@ -25,7 +28,7 @@ export class StateService extends BaseStateService { constructor( storageService: AbstractStorageService, @Inject(SECURE_STORAGE) secureStorageService: AbstractStorageService, - @Inject(MEMORY_STORAGE) memoryStorageService: AbstractStorageService, + @Inject(MEMORY_STORAGE) memoryStorageService: AbstractMemoryStorageService, logService: LogService, stateMigrationService: StateMigrationService, @Inject(STATE_FACTORY) stateFactory: StateFactory, diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 7e1b9124b1a..6ae4f45ea40 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -1,10 +1,13 @@ import { InjectionToken } from "@angular/core"; -import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; +import { + AbstractMemoryStorageService, + AbstractStorageService, +} from "@bitwarden/common/abstractions/storage.service"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; export const WINDOW = new InjectionToken("WINDOW"); -export const MEMORY_STORAGE = new InjectionToken("MEMORY_STORAGE"); +export const MEMORY_STORAGE = new InjectionToken("MEMORY_STORAGE"); export const SECURE_STORAGE = new InjectionToken("SECURE_STORAGE"); export const STATE_FACTORY = new InjectionToken("STATE_FACTORY"); export const STATE_SERVICE_USE_CACHE = new InjectionToken("STATE_SERVICE_USE_CACHE"); diff --git a/libs/common/src/abstractions/storage.service.ts b/libs/common/src/abstractions/storage.service.ts index d405d526694..cdd731f9d69 100644 --- a/libs/common/src/abstractions/storage.service.ts +++ b/libs/common/src/abstractions/storage.service.ts @@ -7,10 +7,11 @@ export abstract class AbstractStorageService { abstract remove(key: string, options?: StorageOptions): Promise; } -export abstract class AbstractCachedStorageService extends AbstractStorageService { +export abstract class AbstractMemoryStorageService extends AbstractStorageService { + // Used to identify the service in the session sync decorator framework + static readonly TYPE = "MemoryStorageService"; + readonly type = AbstractMemoryStorageService.TYPE; + + abstract get(key: string, options?: MemoryStorageOptions): Promise; abstract getBypassCache(key: string, options?: MemoryStorageOptions): Promise; } - -export interface MemoryStorageServiceInterface { - get(key: string, options?: MemoryStorageOptions): Promise; -} diff --git a/libs/common/src/services/memoryStorage.service.ts b/libs/common/src/services/memoryStorage.service.ts index cfb94193e5c..b4d65c0ffa6 100644 --- a/libs/common/src/services/memoryStorage.service.ts +++ b/libs/common/src/services/memoryStorage.service.ts @@ -1,12 +1,6 @@ -import { - AbstractStorageService, - MemoryStorageServiceInterface, -} from "../abstractions/storage.service"; +import { AbstractMemoryStorageService } from "../abstractions/storage.service"; -export class MemoryStorageService - extends AbstractStorageService - implements MemoryStorageServiceInterface -{ +export class MemoryStorageService extends AbstractMemoryStorageService { private store = new Map(); get(key: string): Promise { @@ -33,4 +27,8 @@ export class MemoryStorageService this.store.delete(key); return Promise.resolve(); } + + getBypassCache(key: string): Promise { + return this.get(key); + } } diff --git a/libs/common/src/services/state.service.ts b/libs/common/src/services/state.service.ts index f55ccc39314..eeb97c6531c 100644 --- a/libs/common/src/services/state.service.ts +++ b/libs/common/src/services/state.service.ts @@ -5,7 +5,7 @@ import { LogService } from "../abstractions/log.service"; import { StateService as StateServiceAbstraction } from "../abstractions/state.service"; import { StateMigrationService } from "../abstractions/stateMigration.service"; import { - MemoryStorageServiceInterface, + AbstractMemoryStorageService, AbstractStorageService, } from "../abstractions/storage.service"; import { HtmlStorageLocation } from "../enums/htmlStorageLocation"; @@ -87,7 +87,7 @@ export class StateService< constructor( protected storageService: AbstractStorageService, protected secureStorageService: AbstractStorageService, - protected memoryStorageService: AbstractStorageService & MemoryStorageServiceInterface, + protected memoryStorageService: AbstractMemoryStorageService, protected logService: LogService, protected stateMigrationService: StateMigrationService, protected stateFactory: StateFactory, From 344a054ba2eee76246d2b73698c63ad03a011408 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 12 Jan 2023 23:06:58 +0100 Subject: [PATCH 161/205] [SM-74] TableDataSource for sorting (#4079) * Initial draft of a table data source * Improve table data source * Migrate projects table for demo * Update existing tables * Fix access selector * remove sortDirection from custom fn * a11y improvements * update icons; make button full width * update storybook docs * apply code review changes * fix: add table body to projects list * Fix error on create secret. Fix project list setting projects on getter. Copy table data on set. Fix documentation * Change signature to protected, rename method to not start with underscore * add hover and focus effects Co-authored-by: William Martin --- .../access-selector.component.html | 4 +- .../manage/events.component.html | 4 +- .../dialogs/bulk-status-dialog.component.html | 4 +- .../projects-list.component.html | 12 +- .../projects-list/projects-list.component.ts | 5 + .../dialog/secret-dialog.component.html | 4 +- .../access/access-list.component.html | 4 +- .../service-account-dialog.component.html | 4 +- .../service-accounts-list.component.html | 4 +- .../shared/secrets-list.component.html | 4 +- .../src/stories/button-docs.stories.mdx | 2 +- .../src/stories/table-docs.stories.mdx | 104 +++++++++++ libs/components/src/table/index.ts | 1 + .../src/table/sortable.component.ts | 125 +++++++++++++ .../components/src/table/table-data-source.ts | 164 ++++++++++++++++++ .../components/src/table/table.component.html | 4 +- libs/components/src/table/table.component.ts | 47 ++++- libs/components/src/table/table.module.ts | 13 +- libs/components/src/table/table.stories.ts | 81 ++++++++- 19 files changed, 557 insertions(+), 33 deletions(-) create mode 100644 libs/components/src/stories/table-docs.stories.mdx create mode 100644 libs/components/src/table/sortable.component.ts create mode 100644 libs/components/src/table/table-data-source.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 index cc4d1d7fa78..6e5a299b57e 100644 --- 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 @@ -40,7 +40,7 @@ - + {{ emptySelectionText }} - + diff --git a/apps/web/src/app/organizations/manage/events.component.html b/apps/web/src/app/organizations/manage/events.component.html index e2c03f0dde5..f936cc7a863 100644 --- a/apps/web/src/app/organizations/manage/events.component.html +++ b/apps/web/src/app/organizations/manage/events.component.html @@ -75,7 +75,7 @@ {{ "event" | i18n }} - + {{ e.date | date: "medium" }} @@ -86,7 +86,7 @@ - +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html index b874614316b..89e7010c1d4 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-list/projects-list.component.html @@ -11,7 +11,7 @@ - + @@ -25,8 +25,8 @@ {{ "all" | i18n }} - {{ "name" | i18n }} - {{ "lastEdited" | i18n }} + {{ "name" | i18n }} + {{ "lastEdited" | i18n }} - + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html index dd53ccd08b4..3cceeb1d0e6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html @@ -39,7 +39,7 @@ - + - + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.html index 9c41d839990..fa329edbe32 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.html @@ -29,13 +29,13 @@ {{ "permissions" | i18n }} - + example example - +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.html index 7a12d8bc517..5212e9d205e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.html @@ -39,7 +39,7 @@ - + - + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html index 46a05021456..aed896b3ff9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html @@ -39,7 +39,7 @@ - + - + diff --git a/libs/components/src/stories/button-docs.stories.mdx b/libs/components/src/stories/button-docs.stories.mdx index 0c40e780775..cdcdb87f9db 100644 --- a/libs/components/src/stories/button-docs.stories.mdx +++ b/libs/components/src/stories/button-docs.stories.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Source } from "@storybook/addon-docs"; +import { Meta, Story } from "@storybook/addon-docs"; diff --git a/libs/components/src/stories/table-docs.stories.mdx b/libs/components/src/stories/table-docs.stories.mdx new file mode 100644 index 00000000000..139574a18fe --- /dev/null +++ b/libs/components/src/stories/table-docs.stories.mdx @@ -0,0 +1,104 @@ +import { Meta, Story, Source } from "@storybook/addon-docs"; + + + +# Table + +## Overview + +All tables should have a visible horizontal header and label for each column. + + + +The below code is the absolute minimum required to create a table. However we stronly advice you to +use the `dataSource` input to provide a data source for your table. This allows you to easily sort +data. + +```html + + + + Header 1 + Header 2 + Header 3 + + + + + Cell 1 + Cell 2 + Cell 3 + + + +``` + +## Data Source + +Bitwarden provides a data source for tables that can be used in place of a traditional data array. +The `TableDataSource` implements sorting and will in the future also support filtering. This allows +the `bitTable` component to focus on rendering while offloading the data management to the data +source. + +```ts +// External data source +const data: T[]; + +const dataSource = new TableDataSource(); +dataSource.data = data; +``` + +We use the `dataSource` as an input to the `bit-table` component, and access the rows to render +within the `ng-template`which provides access to the rows using `let-rows$`. + + + +### Sortable + +We provide a simple component for displaying sortable column headers. The `bitSortable` component +wires up to the `TableDataSource` and will automatically sort the data when clicked and display +an indicator for which column is currently sorted. The dafault sorting can be specified by setting +the `default`. + +```html +Id +Name +``` + +It's also possible to define a custom sorting function by setting the `fn` input. + +```ts +const sortFn = (a: T, b: T) => (a.id > b.id ? 1 : -1); +``` + +### Virtual Scrolling + +It's heavily adviced to use virtual scrolling if you expect the table to have any significant amount +of data. This is easily done by wrapping the table in the `cdk-virtual-scroll-viewport` component, +specify a `itemSize`, set `scrollWindow` to `true` and replace `*ngFor` with `*cdkVirtualFor`. + +```html + + + + + Id + Name + Other + + + + + {{ r.id }} + {{ r.name }} + {{ r.other }} + + + + +``` + +## Accessibility + +- Always incude a row or column header with your table; this allows screen readers to better contextualize the data +- Avoid spanning data across cells diff --git a/libs/components/src/table/index.ts b/libs/components/src/table/index.ts index 02fed986ac7..1981f1a6613 100644 --- a/libs/components/src/table/index.ts +++ b/libs/components/src/table/index.ts @@ -1 +1,2 @@ export * from "./table.module"; +export * from "./table-data-source"; diff --git a/libs/components/src/table/sortable.component.ts b/libs/components/src/table/sortable.component.ts new file mode 100644 index 00000000000..9f53f956da3 --- /dev/null +++ b/libs/components/src/table/sortable.component.ts @@ -0,0 +1,125 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { Component, HostBinding, Input, OnInit } from "@angular/core"; + +import type { SortFn } from "./table-data-source"; +import { TableComponent } from "./table.component"; + +@Component({ + selector: "th[bitSortable]", + template: ` + + `, +}) +export class SortableComponent implements OnInit { + /** + * Mark the column as sortable and specify the key to sort by + */ + @Input() bitSortable: string; + + private _default: boolean; + /** + * Mark the column as the default sort column + */ + @Input() set default(value: boolean | "") { + this._default = coerceBooleanProperty(value); + } + + /** + * Custom sorting function + * + * @example + * fn = (a, b) => a.name.localeCompare(b.name) + */ + @Input() fn: SortFn; + + constructor(private table: TableComponent) {} + + ngOnInit(): void { + if (this._default && !this.isActive) { + this.setActive(); + } + } + + @HostBinding("attr.aria-sort") get ariaSort() { + if (!this.isActive) { + return undefined; + } + return this.sort.direction === "asc" ? "ascending" : "descending"; + } + + protected setActive() { + if (this.table.dataSource) { + const direction = this.isActive && this.direction === "asc" ? "desc" : "asc"; + this.table.dataSource.sort = { column: this.bitSortable, direction: direction, fn: this.fn }; + } + } + + private get sort() { + return this.table.dataSource?.sort; + } + + get isActive() { + return this.sort?.column === this.bitSortable; + } + + get direction() { + return this.sort?.direction; + } + + get icon() { + if (!this.isActive) { + return "bwi-chevron-up tw-opacity-0 group-hover:tw-opacity-100 group-focus-visible:tw-opacity-100"; + } + return this.direction === "asc" ? "bwi-chevron-up" : "bwi-angle-down"; + } + + get classList() { + return [ + // Offset to border and padding + "-tw-m-1.5", + + // Below is copied from BitIconButtonComponent + "tw-font-semibold", + "tw-border", + "tw-border-solid", + "tw-rounded", + "tw-transition", + "hover:tw-no-underline", + "focus:tw-outline-none", + + "tw-bg-transparent", + "!tw-text-muted", + "tw-border-transparent", + "hover:tw-bg-transparent-hover", + "hover:tw-border-primary-700", + "focus-visible:before:tw-ring-primary-700", + "disabled:tw-opacity-60", + "disabled:hover:tw-border-transparent", + "disabled:hover:tw-bg-transparent", + + // Workaround for box-shadow with transparent offset issue: + // https://github.com/tailwindlabs/tailwindcss/issues/3595 + // Remove `before:` and use regular `tw-ring` when browser no longer has bug, or better: + // switch to `outline` with `outline-offset` when Safari supports border radius on outline. + // Using `box-shadow` to create outlines is a hack and as such `outline` should be preferred. + "tw-relative", + "before:tw-content-['']", + "before:tw-block", + "before:tw-absolute", + "before:-tw-inset-[3px]", + "before:tw-rounded-md", + "before:tw-transition", + "before:tw-ring", + "before:tw-ring-transparent", + "focus-visible:tw-z-10", + ]; + } +} diff --git a/libs/components/src/table/table-data-source.ts b/libs/components/src/table/table-data-source.ts new file mode 100644 index 00000000000..d5f4473a8f7 --- /dev/null +++ b/libs/components/src/table/table-data-source.ts @@ -0,0 +1,164 @@ +import { _isNumberValue } from "@angular/cdk/coercion"; +import { DataSource } from "@angular/cdk/collections"; +import { BehaviorSubject, combineLatest, map, Observable, Subscription } from "rxjs"; + +export type SortDirection = "asc" | "desc"; +export type SortFn = (a: any, b: any) => number; +export type Sort = { + column?: string; + direction: SortDirection; + fn?: SortFn; +}; + +// Loosely based on CDK TableDataSource +// https://github.com/angular/components/blob/main/src/material/table/table-data-source.ts +export class TableDataSource extends DataSource { + private readonly _data: BehaviorSubject; + private readonly _sort: BehaviorSubject; + private readonly _renderData = new BehaviorSubject([]); + + private _renderChangesSubscription: Subscription | null = null; + + constructor() { + super(); + this._data = new BehaviorSubject([]); + this._sort = new BehaviorSubject({ direction: "asc" }); + } + + get data() { + return this._data.value; + } + + set data(data: T[]) { + this._data.next(data ? [...data] : []); + } + + set sort(sort: Sort) { + this._sort.next(sort); + } + + get sort() { + return this._sort.value; + } + + connect(): Observable { + if (!this._renderChangesSubscription) { + this.updateChangeSubscription(); + } + + return this._renderData; + } + + disconnect(): void { + this._renderChangesSubscription?.unsubscribe(); + this._renderChangesSubscription = null; + } + + private updateChangeSubscription() { + const orderedData = combineLatest([this._data, this._sort]).pipe( + map(([data]) => this.orderData(data)) + ); + + this._renderChangesSubscription?.unsubscribe(); + this._renderChangesSubscription = orderedData.subscribe((data) => this._renderData.next(data)); + } + + private orderData(data: T[]): T[] { + if (!this.sort) { + return data; + } + + return this.sortData(data, this.sort); + } + + /** + * Copied from https://github.com/angular/components/blob/main/src/material/table/table-data-source.ts + * License: MIT + * Copyright (c) 2022 Google LLC. + * + * Data accessor function that is used for accessing data properties for sorting through + * the default sortData function. + * This default function assumes that the sort header IDs (which defaults to the column name) + * matches the data's properties (e.g. column Xyz represents data['Xyz']). + * May be set to a custom function for different behavior. + * @param data Data object that is being accessed. + * @param sortHeaderId The name of the column that represents the data. + */ + protected sortingDataAccessor(data: T, sortHeaderId: string): string | number { + const value = (data as unknown as Record)[sortHeaderId]; + + if (_isNumberValue(value)) { + const numberValue = Number(value); + + return numberValue < Number.MAX_SAFE_INTEGER ? numberValue : value; + } + + return value; + } + + /** + * Copied from https://github.com/angular/components/blob/main/src/material/table/table-data-source.ts + * License: MIT + * Copyright (c) 2022 Google LLC. + * + * Gets a sorted copy of the data array based on the state of the MatSort. Called + * after changes are made to the filtered data or when sort changes are emitted from MatSort. + * By default, the function retrieves the active sort and its direction and compares data + * by retrieving data using the sortingDataAccessor. May be overridden for a custom implementation + * of data ordering. + * @param data The array of data that should be sorted. + * @param sort The connected MatSort that holds the current sort state. + */ + protected sortData(data: T[], sort: Sort): T[] { + const column = sort.column; + const direction = sort.direction; + if (!column) { + return data; + } + + return data.sort((a, b) => { + // If a custom sort function is provided, use it instead of the default. + if (sort.fn) { + return sort.fn(a, b) * (direction === "asc" ? 1 : -1); + } + + let valueA = this.sortingDataAccessor(a, column); + let valueB = this.sortingDataAccessor(b, column); + + // If there are data in the column that can be converted to a number, + // it must be ensured that the rest of the data + // is of the same type so as not to order incorrectly. + const valueAType = typeof valueA; + const valueBType = typeof valueB; + + if (valueAType !== valueBType) { + if (valueAType === "number") { + valueA += ""; + } + if (valueBType === "number") { + valueB += ""; + } + } + + // If both valueA and valueB exist (truthy), then compare the two. Otherwise, check if + // one value exists while the other doesn't. In this case, existing value should come last. + // This avoids inconsistent results when comparing values to undefined/null. + // If neither value exists, return 0 (equal). + let comparatorResult = 0; + if (valueA != null && valueB != null) { + // Check if one value is greater than the other; if equal, comparatorResult should remain 0. + if (valueA > valueB) { + comparatorResult = 1; + } else if (valueA < valueB) { + comparatorResult = -1; + } + } else if (valueA != null) { + comparatorResult = 1; + } else if (valueB != null) { + comparatorResult = -1; + } + + return comparatorResult * (direction === "asc" ? 1 : -1); + }); + } +} diff --git a/libs/components/src/table/table.component.html b/libs/components/src/table/table.component.html index 11108d8ec1a..57e3853f976 100644 --- a/libs/components/src/table/table.component.html +++ b/libs/components/src/table/table.component.html @@ -5,6 +5,8 @@ - + diff --git a/libs/components/src/table/table.component.ts b/libs/components/src/table/table.component.ts index 8099c35b929..40a2da70927 100644 --- a/libs/components/src/table/table.component.ts +++ b/libs/components/src/table/table.component.ts @@ -1,7 +1,50 @@ -import { Component } from "@angular/core"; +import { isDataSource } from "@angular/cdk/collections"; +import { + AfterContentChecked, + Component, + ContentChild, + Directive, + Input, + OnDestroy, + TemplateRef, +} from "@angular/core"; +import { Observable } from "rxjs"; + +import { TableDataSource } from "./table-data-source"; + +@Directive({ + selector: "ng-template[body]", +}) +export class TableBodyDirective { + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + constructor(public readonly template: TemplateRef) {} +} @Component({ selector: "bit-table", templateUrl: "./table.component.html", }) -export class TableComponent {} +export class TableComponent implements OnDestroy, AfterContentChecked { + @Input() dataSource: TableDataSource; + + @ContentChild(TableBodyDirective) templateVariable: TableBodyDirective; + + protected rows: Observable; + + private _initialized = false; + + ngAfterContentChecked(): void { + if (!this._initialized && isDataSource(this.dataSource)) { + this._initialized = true; + + const dataStream = this.dataSource.connect(); + this.rows = dataStream; + } + } + + ngOnDestroy(): void { + if (isDataSource(this.dataSource)) { + this.dataSource.disconnect(); + } + } +} diff --git a/libs/components/src/table/table.module.ts b/libs/components/src/table/table.module.ts index 2ca88a349f0..753e4362e6f 100644 --- a/libs/components/src/table/table.module.ts +++ b/libs/components/src/table/table.module.ts @@ -3,11 +3,18 @@ import { NgModule } from "@angular/core"; import { CellDirective } from "./cell.directive"; import { RowDirective } from "./row.directive"; -import { TableComponent } from "./table.component"; +import { SortableComponent } from "./sortable.component"; +import { TableBodyDirective, TableComponent } from "./table.component"; @NgModule({ imports: [CommonModule], - declarations: [TableComponent, CellDirective, RowDirective], - exports: [TableComponent, CellDirective, RowDirective], + declarations: [ + TableComponent, + CellDirective, + RowDirective, + SortableComponent, + TableBodyDirective, + ], + exports: [TableComponent, CellDirective, RowDirective, SortableComponent, TableBodyDirective], }) export class TableModule {} diff --git a/libs/components/src/table/table.stories.ts b/libs/components/src/table/table.stories.ts index 961636dd9cc..60e80117bef 100644 --- a/libs/components/src/table/table.stories.ts +++ b/libs/components/src/table/table.stories.ts @@ -1,12 +1,14 @@ +import { ScrollingModule } from "@angular/cdk/scrolling"; import { Meta, moduleMetadata, Story } from "@storybook/angular"; +import { TableDataSource } from "./table-data-source"; import { TableModule } from "./table.module"; export default { title: "Component Library/Table", decorators: [ moduleMetadata({ - imports: [TableModule], + imports: [TableModule, ScrollingModule], }), ], argTypes: { @@ -34,7 +36,7 @@ const Template: Story = (args) => ({ Header 3 - + Cell 1 Cell 2
Multiline Cell @@ -50,9 +52,8 @@ const Template: Story = (args) => ({ Cell 8 Cell 9 -
+ - `, }); @@ -60,3 +61,75 @@ export const Default = Template.bind({}); Default.args = { alignRowContent: "baseline", }; + +const data = new TableDataSource<{ id: number; name: string; other: string }>(); + +data.data = [...Array(5).keys()].map((i) => ({ + id: i, + name: `name-${i}`, + other: `other-${i}`, +})); + +const DataSourceTemplate: Story = (args) => ({ + props: { + dataSource: data, + sortFn: (a: any, b: any) => a.id - b.id, + }, + template: ` + + + + Id + Name + Other + + + + + {{ r.id }} + {{ r.name }} + {{ r.other }} + + + + `, +}); + +export const DataSource = DataSourceTemplate.bind({}); + +const data2 = new TableDataSource<{ id: number; name: string; other: string }>(); + +data2.data = [...Array(100).keys()].map((i) => ({ + id: i, + name: `name-${i}`, + other: `other-${i}`, +})); + +const ScrollableTemplate: Story = (args) => ({ + props: { + dataSource: data2, + sortFn: (a: any, b: any) => a.id - b.id, + }, + template: ` + + + + + Id + Name + Other + + + + + {{ r.id }} + {{ r.name }} + {{ r.other }} + + + + + `, +}); + +export const Scrollable = ScrollableTemplate.bind({}); From a7ff8cce32386c8e9bcc359acc4e2ca33a2472a3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Jan 2023 10:49:00 +0100 Subject: [PATCH 162/205] Autosync the updated translations (#4460) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 58 +- apps/browser/src/_locales/be/messages.json | 2 +- apps/browser/src/_locales/bg/messages.json | 14 +- apps/browser/src/_locales/ca/messages.json | 2 +- apps/browser/src/_locales/fa/messages.json | 496 ++--- apps/browser/src/_locales/fr/messages.json | 170 +- apps/browser/src/_locales/id/messages.json | 38 +- apps/browser/src/_locales/ne/messages.json | 2047 ++++++++++++++++++++ apps/browser/src/_locales/nl/messages.json | 2 +- apps/browser/src/_locales/sk/messages.json | 2 +- apps/browser/src/_locales/vi/messages.json | 302 +-- apps/browser/store/locales/bg/copy.resx | 30 +- apps/browser/store/locales/fa/copy.resx | 30 +- apps/browser/store/locales/fr/copy.resx | 26 +- apps/browser/store/locales/lv/copy.resx | 30 +- apps/browser/store/locales/ne/copy.resx | 175 ++ apps/browser/store/locales/vi/copy.resx | 6 +- 17 files changed, 2826 insertions(+), 604 deletions(-) create mode 100644 apps/browser/src/_locales/ne/messages.json create mode 100644 apps/browser/store/locales/ne/copy.resx diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index dd73807f25f..d0066f44e6c 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -828,7 +828,7 @@ "message": "أدخل رمز التحقق من 6 أرقام من تطبيق المصادقة الخاص بك." }, "enterVerificationCodeEmail": { - "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", + "message": "أدخل رمز التحقق المكون من 6 أرقام الذي تم إرساله إلى $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -837,7 +837,7 @@ } }, "verificationCodeEmailSent": { - "message": "Verification email sent to $EMAIL$.", + "message": "تم إرسال رسالة التحقق إلى $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -846,91 +846,91 @@ } }, "rememberMe": { - "message": "Remember me" + "message": "تذكرني" }, "sendVerificationCodeEmailAgain": { - "message": "Send verification code email again" + "message": "إرسال رمز التحقق إلى البريد الإلكتروني مرة أخرى" }, "useAnotherTwoStepMethod": { - "message": "Use another two-step login method" + "message": "استخدام طريقة أخرى لتسجيل الدخول بخطوتين" }, "insertYubiKey": { - "message": "Insert your YubiKey into your computer's USB port, then touch its button." + "message": "أدخل YubiKey الخاص بك في منفذ USB في كمبيوترك، ثم المس الزر." }, "insertU2f": { - "message": "Insert your security key into your computer's USB port. If it has a button, touch it." + "message": "أدخل مفتاح الأمان الخاص بك في منفذ USB كمبيوترك، إذا كان يحتوي على زر، إلمسه." }, "webAuthnNewTab": { - "message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab." + "message": "لبدء التحقق من WebAuthn 2FA. انقر على الزر أدناه لفتح علامة تبويب جديدة واتبع التعليمات المقدمة في علامة التبويب الجديدة." }, "webAuthnNewTabOpen": { - "message": "Open new tab" + "message": "فتح علامة تبويب جديدة" }, "webAuthnAuthenticate": { "message": "مصادقة WebAuthn" }, "loginUnavailable": { - "message": "Login unavailable" + "message": "تسجيل الدخول غير متاح" }, "noTwoStepProviders": { - "message": "This account has two-step login set up, however, none of the configured two-step providers are supported by this web browser." + "message": "تم إعداد تسجيل الدخول من خطوتين لهذا الحساب، ومع ذلك، لا يدعم هذا المتصفح أيًا من موفري تسجيل الدخول من خطوتين." }, "noTwoStepProviders2": { - "message": "Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported across web browsers (such as an authenticator app)." + "message": "الرجاء استخدام متصفح ويب مدعوم (مثل Chrome) و/أو إضافة موفري إضافيين مدعومين بشكل أفضل عبر متصفحات الويب (مثل تطبيق المصادقة)." }, "twoStepOptions": { - "message": "Two-step login options" + "message": "خيارات تسجيل الدخول بخطوتين" }, "recoveryCodeDesc": { - "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." + "message": "هل تفقد الوصول إلى جميع مزودي التحقق بعاملين؟ استخدم رمز الاسترداد الخاص بك لتعطيل جميع مزودي التحقق بعاملين من حسابك." }, "recoveryCodeTitle": { - "message": "Recovery code" + "message": "رمز الاسترداد" }, "authenticatorAppTitle": { - "message": "Authenticator app" + "message": "تطبيق المصادقة" }, "authenticatorAppDesc": { - "message": "Use an authenticator app (such as Authy or Google Authenticator) to generate time-based verification codes.", + "message": "استخدام تطبيق مصادقة (مثل Authy أو Google Authenticator) لإنشاء رموز تحقق مستندة إلى الوقت.", "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." }, "yubiKeyTitle": { - "message": "YubiKey OTP Security Key" + "message": "مفتاح أمان YubiKey OTP" }, "yubiKeyDesc": { - "message": "Use a YubiKey to access your account. Works with YubiKey 4, 4 Nano, 4C, and NEO devices." + "message": "استخدم YubiKey للوصول إلى حسابك. يعمل مع YubiKey 4 ،4 Nano ،4C، وأجهزة NEO." }, "duoDesc": { - "message": "Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.", + "message": "التحقق باستخدام نظام الحماية الثنائي باستخدام تطبيق Duo Mobile أو الرسائل القصيرة أو المكالمة الهاتفية أو مفتاح الأمان U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", + "message": "تحقق من خلال نظام الحماية الثنائي لمؤسستك باستخدام تطبيق Duo Mobile أو الرسائل القصيرة أو المكالمة الهاتفية أو مفتاح الأمان U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Use any WebAuthn compatible security key to access your account." + "message": "استخدم أي مفتاح أمان متوافق مع WebAuthn للوصول إلى حسابك." }, "emailTitle": { "message": "البريد الإلكتروني" }, "emailDesc": { - "message": "Verification codes will be emailed to you." + "message": "سيتم إرسال رمز التحقق إليك بالبريد الإلكتروني." }, "selfHostedEnvironment": { - "message": "Self-hosted environment" + "message": "البيئة المستضافة ذاتيا" }, "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." + "message": "حدد عنوان URL الأساسي لتثبيت Bitwarden المستضاف محليًا." }, "customEnvironment": { - "message": "Custom environment" + "message": "بيئة مخصصة" }, "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." + "message": "للمستخدمين المتقدمين. يمكنك تحديد عنوان URL الأساسي لكل خدمة بشكل مستقل." }, "baseUrl": { "message": "رابط الخادم" @@ -951,10 +951,10 @@ "message": "رابط خادم الأيقونات" }, "environmentSaved": { - "message": "Environment URLs saved" + "message": "روابط البيئة المحفوظة" }, "enableAutoFillOnPageLoad": { - "message": "Auto-fill on page load" + "message": "ملء تلقائي عند تحميل الصفحة" }, "enableAutoFillOnPageLoadDesc": { "message": "If a login form is detected, auto-fill when the web page loads." diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 5fd73c5d4df..1c9b61a7927 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -642,7 +642,7 @@ "description": "Light color" }, "solarizedDark": { - "message": "Цёмная Solarized", + "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportVault": { diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index fcca7309fa6..25b5b1d52b7 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -86,7 +86,7 @@ "message": "Копиране на номера" }, "copySecurityCode": { - "message": "Копиране на кода да сигурност" + "message": "Копиране на кода за сигурност" }, "autoFill": { "message": "Автоматично дописване" @@ -291,7 +291,7 @@ "message": "Парола" }, "passphrase": { - "message": "Парола за преминаване" + "message": "Парола-фраза" }, "favorite": { "message": "Любими" @@ -1923,17 +1923,17 @@ "message": "Тип потребителско име" }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "Адрес на е-поща с плюс", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { "message": "Използвайте възможностите за под-адресиране на е-поща на своя доставчик." }, "catchallEmail": { - "message": "Catch-all email" + "message": "Хващаща всичко е-поща" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "Използвайте конфигурираната входяща кутия за събиране на всичко." }, "random": { "message": "Произволно" @@ -1954,10 +1954,10 @@ "message": "Услуга" }, "forwardedEmail": { - "message": "Forwarded email alias" + "message": "Псевдоним на препратена е-поща" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "Създайте псевдоним на е-поща с външна услуга за препращане." }, "hostname": { "message": "Име на сървъра", diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 2bcfbed2bb1..ab640e96cf3 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1393,7 +1393,7 @@ "message": "Una o més polítiques d’organització afecten la configuració del generador." }, "vaultTimeoutAction": { - "message": "Acció del temps d'espera de la caixa forta" + "message": "Acció quan acabe el temps d'espera de la caixa forta" }, "lock": { "message": "Bloqueja", diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 1adeb72b4b9..c129740c509 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "یک مدیریت کننده کلمه عبور رایگان برای تمامی دستگاههایتان.", + "message": "یک مدیریت کننده کلمه عبور رایگان برای تمامی دستگاه‌هایتان.", "description": "Extension description" }, "loginOrCreateNewAccount": { @@ -32,22 +32,22 @@ "message": "ثبت" }, "emailAddress": { - "message": "آدرس ایمیل" + "message": "نشانی ایمیل" }, "masterPass": { "message": "کلمه عبور اصلی" }, "masterPassDesc": { - "message": "کلمه عبور اصلی کلمه عبوری است که شما برای دسترسی به گاوصندوق خود استفاده میکنید. بیاد داشتن کلمه عبور اصلی بسیار اهمیت دارد. اگر فراموشش کنید هیچ راهی برای بازگردانی آن وجود ندارد." + "message": "کلمه عبور اصلی، کلمه عبوری است که شما برای دسترسی به گاوصندوق خود استفاده می‌کنید. به یاد داشتن کلمه عبور اصلی بسیار اهمیت دارد. اگر فراموشش کنید هیچ راهی برای بازگردانی آن وجود ندارد." }, "masterPassHintDesc": { - "message": "راهنمای کلمه عبور اصلی می تواند در صورت فراموشی آن را بیاد بیارید." + "message": "یادآور کلمه عبور اصلی کمک می‌کند در صورت فراموشی آن را به یاد بیارید." }, "reTypeMasterPass": { - "message": "تایپ دوباره کلمه عبور اصلی" + "message": "نوشتن دوباره کلمه عبور اصلی" }, "masterPassHint": { - "message": "راهنمای کلمه عبور اصلی (اختیاری)" + "message": "یادآور کلمه عبور اصلی (اختیاری)" }, "tab": { "message": "زبانه" @@ -71,13 +71,13 @@ "message": "زبانه فعلی" }, "copyPassword": { - "message": "کپی رمز عبور" + "message": "کپی کلمه عبور" }, "copyNote": { "message": "کپی یادداشت" }, "copyUri": { - "message": "کپی آدرس اینترنتی" + "message": "کپی نشانی اینترنتی" }, "copyUsername": { "message": "کپی نام کاربری" @@ -95,40 +95,40 @@ "message": "ساخت کلمه عبور (کپی شد)" }, "copyElementIdentifier": { - "message": "رونوشت نام فیلد سفارشی" + "message": "کپی نام فیلد سفارشی" }, "noMatchingLogins": { - "message": "ورودیها منتطبق نیست." + "message": "ورودی‌ها منتطبق نیست" }, "unlockVaultMenu": { - "message": "بازکردن گاو‌صندوقتان" + "message": "قفل گاوصندوق خود را باز کنید" }, "loginToVaultMenu": { "message": "وارد شدن به گاو‌صندوقتان" }, "autoFillInfo": { - "message": "پرکردن خودکار برای برگه فعلی مرورگر در دسترس نیست." + "message": "پر کردن خودکار برای برگه فعلی مرورگر در دسترس نیست." }, "addLogin": { - "message": "افزودن ورود جدید" + "message": "افزودن یک ورود" }, "addItem": { "message": "افزودن مورد" }, "passwordHint": { - "message": "راهنمای کلمه عبور" + "message": "یادآور کلمه عبور" }, "enterEmailToGetHint": { - "message": "برای دریافت راهنمایی کلمه عبور اصلی خود آدرس ایملیتان را وارد کنید." + "message": "برای دریافت یادآور کلمه عبور اصلی خود نشانی ایمیل‌تان را وارد کنید." }, "getMasterPasswordHint": { - "message": "دریافت راهنمای کلمه عبور اصلی" + "message": "دریافت یادآور کلمه عبور اصلی" }, "continue": { "message": "ادامه" }, "sendVerificationCode": { - "message": "ارسال یک کد تأیید به ایمیل شما" + "message": "یک کد تأیید به ایمیل خود ارسال کنید" }, "sendCode": { "message": "ارسال کد" @@ -137,10 +137,10 @@ "message": "کد ارسال شد" }, "verificationCode": { - "message": "کد تایید" + "message": "کد تأیید" }, "confirmIdentity": { - "message": "برای ادامه هویت خود را تأیید کنید." + "message": "برای ادامه، هویت خود را تأیید کنید." }, "account": { "message": "حساب" @@ -163,7 +163,7 @@ "message": "خروج" }, "about": { - "message": "درباره ما" + "message": "درباره" }, "version": { "message": "نسخه" @@ -187,38 +187,38 @@ "message": "حذف پوشه" }, "folders": { - "message": "پوشه ها" + "message": "پوشه‌ها" }, "noFolders": { - "message": "هیچ موردی برای نمایش وجود ندارد." + "message": "هیچ پوشه‌ای برای نمایش وجود ندارد." }, "helpFeedback": { "message": "کمک و بازخورد" }, "sync": { - "message": "همگام سازی" + "message": "همگام‌سازی" }, "syncVaultNow": { - "message": "همگام سازی گاوصندوق" + "message": "همگام‌سازی گاوصندوق" }, "lastSync": { - "message": "آخرین همگام سازی:" + "message": "آخرین همگام‌سازی:" }, "passGen": { - "message": "تولید کلمه عبور" + "message": "تولید کننده کلمه عبور" }, "generator": { "message": "تولید کننده", "description": "Short for 'Password Generator'." }, "passGenInfo": { - "message": "به طور خودکار کلمه های عبور قوی و منحصر به فرد برای ورود به سیستم خود ایجاد کنید." + "message": "به طور خودکار کلمه‌های عبور قوی و منحصر به فرد برای ورود به سیستم خود ایجاد کنید." }, "bitWebVault": { "message": "گاوصندوق وب Bitwarden" }, "importItems": { - "message": "واردن کردن موارد" + "message": "درون ریزی موارد" }, "select": { "message": "انتخاب" @@ -230,7 +230,7 @@ "message": "تولید مجدد کلمه عبور" }, "options": { - "message": "گزینه ها" + "message": "گزینه‌ها" }, "length": { "message": "طول" @@ -251,7 +251,7 @@ "message": "تعداد کلمات" }, "wordSeparator": { - "message": "کلمه جداکننده" + "message": "جداکننده کلمات" }, "capitalize": { "message": "بزرگ کردن", @@ -267,7 +267,7 @@ "message": "حداقل حرف خاص" }, "avoidAmbChar": { - "message": "از کاراکترهای مبهم اجتناب شود" + "message": "از کاراکترهای مبهم اجتناب کن" }, "searchVault": { "message": "جستجوی گاوصندوق" @@ -291,13 +291,13 @@ "message": "کلمه عبور" }, "passphrase": { - "message": "کلمه عبور" + "message": "عبارت عبور" }, "favorite": { "message": "مورد علاقه" }, "notes": { - "message": "یادداشت ها" + "message": "یادداشت‌ها" }, "note": { "message": "یادداشت" @@ -318,10 +318,10 @@ "message": "راه اندازی" }, "website": { - "message": "وب سایت" + "message": "وب‌سایت" }, "toggleVisibility": { - "message": "تغییر قابلیت نمایش" + "message": "قابلیت مشاهده را تغییر دهید" }, "manage": { "message": "مدیریت" @@ -336,16 +336,16 @@ "message": "لطفاً با یک بررسی خوب به ما کمک کنید!" }, "browserNotSupportClipboard": { - "message": "مرورگر شما از کپی کلیپ بورد آسان پشتیبانی نمی کند. به جای آن به صورت دستی کپی کنید." + "message": "مرورگر شما از کپی کلیپ بورد آسان پشتیبانی نمی‌کند. به جای آن به صورت دستی کپی کنید." }, "verifyIdentity": { "message": "تأیید هویت" }, "yourVaultIsLocked": { - "message": "گاوصندوق شما قفل است. برای ادامه کلمه عبور اصلی خود را وارد کنید." + "message": "گاوصندوق شما قفل شده است. برای ادامه هویت خود را تأیید کنید." }, "unlock": { - "message": "بازکردن" + "message": "باز کردن قفل" }, "loggedInAsOn": { "message": "وارد شده با $EMAIL$ در $HOSTNAME$.", @@ -403,7 +403,7 @@ "message": "4 ساعت" }, "onLocked": { - "message": "هنگام قفل سیستم" + "message": "در قفل سیستم" }, "onRestart": { "message": "هنگام راه اندازی مجدد" @@ -418,34 +418,34 @@ "message": "خطایی رخ داده است" }, "emailRequired": { - "message": "آدرس ایمیل ضروری است." + "message": "نشانی ایمیل ضروری است." }, "invalidEmail": { - "message": "آدرس ایمیل نامعتبر است." + "message": "نشانی ایمیل نامعتبر است." }, "masterPasswordRequired": { - "message": "گذرواژه اصلی ضروری است." + "message": "کلمه عبور اصلی ضروری است." }, "confirmMasterPasswordRequired": { - "message": "تایپ مجدد گذرواژه اصلی نیاز است." + "message": "نوشتن مجدد کلمه عبور اصلی ضروری است." }, "masterPasswordMinlength": { "message": "طول کلمه عبور اصلی باید حداقل ۸ کاراکتر باشد." }, "masterPassDoesntMatch": { - "message": "کلمه عبور اصلی با تکرار کلمه عبور اصلی مطابقت ندارد." + "message": "کلمه عبور اصلی با تکرار آن مطابقت ندارد." }, "newAccountCreated": { - "message": "حساب شما ساختته شد! حالا میتوانید وارد شوید." + "message": "حساب کاربری جدید شما ساخته شد! حالا می‌توانید وارد شوید." }, "masterPassSent": { "message": "ما یک ایمیل همراه با راهنمای کلمه عبور اصلی برایتان ارسال کردیم." }, "verificationCodeRequired": { - "message": "کد تایید مورد نیاز است." + "message": "کد تأیید مورد نیاز است." }, "invalidVerificationCode": { - "message": "کد تایید نامعتبر" + "message": "کد تأیید نامعتبر است" }, "valueCopied": { "message": " کپی شده", @@ -458,13 +458,13 @@ } }, "autofillError": { - "message": "ناتوان در پرکردن خودکار آیتم انتخاب شده در این صفحه. اطلاعات را کپی و جایگذاری کنید." + "message": "ناتوان در پر کردن خودکار مورد انتخاب شده در این صفحه. اطلاعات را کپی و جای‌گذاری کنید." }, "loggedOut": { - "message": "خارج شده" + "message": "خارج شد" }, "loginExpired": { - "message": "جلسه ورود شما منقضی شده است." + "message": "نشست ورود شما منقضی شده است." }, "logOutConfirmation": { "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" @@ -482,7 +482,7 @@ "message": "نام ضروری است." }, "addedFolder": { - "message": "پوشه اضافه شده" + "message": "پوشه اضافه شد" }, "changeMasterPass": { "message": "تغییر کلمه عبور اصلی" @@ -491,16 +491,16 @@ "message": "شما می‌توانید کلمه عبور اصلی خود را در bitwarden.com تغییر دهید. آیا می‌خواهید از سایت بازدید کنید؟" }, "twoStepLoginConfirmation": { - "message": "ورودی دو مرحله ای باعث می شود که حساب کاربری شما با استفاده از یک دستگاه دیگر مانند کلید امنیتی، برنامه تأیید هویت، پیامک، تماس تلفنی و یا ایمیل، اعتبار خود را با ایمنی بیشتر ثابت کند. ورودی دو مرحله ای می تواند در bitwarden.com فعال شود. آیا می خواهید از سایت بازدید کنید؟" + "message": "ورود دو مرحله ای باعث می‌شود که حساب کاربری شما با استفاده از یک دستگاه دیگر مانند کلید امنیتی، برنامه احراز هویت، پیامک، تماس تلفنی و یا ایمیل، اعتبار خود را با ایمنی بیشتر اثبات کند. ورود دو مرحله ای می تواند در bitwarden.com فعال شود. آیا می‌خواهید از سایت بازدید کنید؟" }, "editedFolder": { - "message": "پوشه ویرایش شده" + "message": "پوشه ذخیره شد" }, "deleteFolderConfirmation": { "message": "آیا از حذف این پوشه اطمینان دارید؟" }, "deletedFolder": { - "message": "پوشه حذف شده" + "message": "پوشه حذف شد" }, "gettingStartedTutorial": { "message": "آغاز نمودن آموزش" @@ -509,10 +509,10 @@ "message": "برای یادگیری بیشتر نحوه استفاده از افزونه مرورگر آموزش شروع به کار را تماشا کنید." }, "syncingComplete": { - "message": "همگام سازی کامل شد" + "message": "همگام‌سازی کامل شد" }, "syncingFailed": { - "message": "همگام سازی شکست خورد" + "message": "همگام‌سازی شکست خورد" }, "passwordCopied": { "message": "کلمه عبور کپی شد" @@ -521,7 +521,7 @@ "message": "نشانی اینترنتی" }, "uriPosition": { - "message": "آدرس اینترنتی $POSITION$", + "message": "نشانی اینترنتی $POSITION$", "description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.", "placeholders": { "position": { @@ -531,19 +531,19 @@ } }, "newUri": { - "message": "آدرس اینترنتی جدید" + "message": "نشانی اینترنتی جدید" }, "addedItem": { - "message": "مورد افزوده شده" + "message": "مورد اضافه شد" }, "editedItem": { - "message": "مورد ویرایش شده" + "message": "مورد ذخیره شد" }, "deleteItemConfirmation": { - "message": "آیا مطمئن هستید می خواهید این مورد را حذف کنید؟" + "message": "واقعاً می‌خواهید این آیتم را به سطل زباله ارسال کنید؟" }, "deletedItem": { - "message": "مورد حذف شده" + "message": "مورد به زباله‌ها فرستاده شد" }, "overwritePassword": { "message": "بازنویسی کلمه عبور" @@ -571,10 +571,10 @@ "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { - "message": "درخواست اضافه کردن ورود به سیستم" + "message": "درخواست افزودن ورود به سیستم" }, "addLoginNotificationDesc": { - "message": "\"افزودن اعلانیه ورود\" به صورت خودکار از شما می خواهد هر بار که برای اولین بار وارد سیستم میشوید ورودی های جدید را در گاوصندوق خود ذخیره کنید." + "message": "در صورتی که موردی در گاوصندوق شما یافت نشد، درخواست افزودن کنید." }, "showCardsCurrentTab": { "message": "نمایش کارت‌ها در صفحه برگه" @@ -589,43 +589,43 @@ "message": "موارد هویتی را در صفحه برگه برای پر کردن خودکار آسان فهرست کن." }, "clearClipboard": { - "message": "پاک‌سازی کلیپ‌ برد", + "message": "پاکسازی کلیپ بورد", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { - "message": "مقدار های کپی شده را به صورت خودکار از کلیپ برد خود پاک کنید.", + "message": "به صورت خودکار، مقادیر کپی شده را از کلیپ بورد پاک کن.", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "notificationAddDesc": { "message": "آیا Bitwarden باید این کلمه عبور را برایتان بخاطر بسپارد؟" }, "notificationAddSave": { - "message": "بله، ذخیره کن" + "message": "ذخیره" }, "enableChangedPasswordNotification": { - "message": "درخواست برای بروزرسانی ورود به سیستم موجود" + "message": "درخواست برای به‌روزرسانی ورود به سیستم موجود" }, "changedPasswordNotificationDesc": { "message": "هنگامی که تغییری در یک وب‌سایت شناسایی شد، درخواست به‌روزرسانی کلمه عبور ورود کن." }, "notificationChangeDesc": { - "message": "آیا مایل به بروزرسانی این پسورد در Bitwarden هستید؟" + "message": "آیا مایل به به‌روزرسانی این کلمه عبور در Bitwarden هستید؟" }, "notificationChangeSave": { - "message": "بله، بروزرسانی کن" + "message": "به‌روزرسانی" }, "enableContextMenuItem": { - "message": "نمایش گزینه های منوی زمینه" + "message": "نمایش گزینه‌های منوی زمینه" }, "contextMenuItemDesc": { "message": "از یک کلیک ثانویه برای دسترسی به تولید کلمه عبور و ورودهای منطبق برای وب سایت استفاده کن." }, "defaultUriMatchDetection": { - "message": "بازرسی تطابق URL پیشفرض", + "message": "بررسی مطابقت نشانی اینترنتی پیش‌فرض", "description": "Default URI match detection for auto-fill." }, "defaultUriMatchDetectionDesc": { - "message": "روش پیش فرض اعمال بازرسی تطابق ‌‌URL را هنگامی که عملی را مثل پر کردن خودکار انتخاب کنید." + "message": "هنگام انجام دادن کارهایی مانند پر کردن خودکار، روش پیش‌فرضی را که برای شناسایی ورود نشانی اینترنتی انجام می‌شود انتخاب کنید." }, "theme": { "message": "پوسته" @@ -646,38 +646,38 @@ "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportVault": { - "message": "صادر کردن گاوصندوق" + "message": "برون ریزی گاوصندوق" }, "fileFormat": { - "message": "File Format" + "message": "فرمت پرونده" }, "warning": { "message": "اخطار", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "صادرات گاوصندوق را تأیید کنید" + "message": "برون ریزی گاوصندوق را تأیید کنید" }, "exportWarningDesc": { - "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + "message": "این برون ریزی شامل داده‌های گاوصندوق در یک قالب رمزنگاری نشده است. شما نباید آن را از طریق یک راه ارتباطی نا امن (مثل ایمیل) ذخیره یا ارسال کنید. به محض اینکه کارتان با آن تمام شد، آن را حذف کنید." }, "encExportKeyWarningDesc": { - "message": "این صادرات با استفاده از کلید رمزگذاری حساب شما ، اطلاعات شما را رمزگذاری می کند. اگر حتی کلید رمزگذاری حساب خود را بچرخانید ، باید دوباره صادر کنید چون قادر به رمزگشایی این پرونده صادراتی نخواهید بود." + "message": "این برون ریزی با استفاده از کلید رمزگذاری حساب شما، اطلاعاتتان را رمزگذاری می کند. اگر زمانی کلید رمزگذاری حساب خود را بچرخانید، باید دوباره خروجی بگیرید، چون قادر به رمزگشایی این پرونده برون ریزی نخواهید بود." }, "encExportAccountWarningDesc": { - "message": "کلیدهای رمزگذاری حساب برای هر حساب کاربری Bitwarden منحصر به فرد است ، بنابراین نمی توانید صادرات رمزگذاری شده را به حساب دیگری وارد کنید." + "message": "کلیدهای رمزگذاری حساب برای هر حساب کاربری Bitwarden منحصر به فرد است، بنابراین نمی‌توانید برون ریزی رمزگذاری شده را به حساب دیگری وارد کنید." }, "exportMasterPassword": { - "message": "کلمه عبور اصلی خود را برای صادرات داده ها از گاوصندوقتان وارد کنید." + "message": "کلمه عبور اصلی خود را برای برون ریزی داده‌های گاوصندوقتان وارد کنید." }, "shared": { "message": "اشتراک گذاری شد" }, "learnOrg": { - "message": "درباره سازمانها اطلاعات کسب کنید" + "message": "درباره سازمان‌ها اطلاعات کسب کنید" }, "learnOrgConfirmation": { - "message": "Bitwarden به شما اجازه می دهد با استفاده از یک سازمان، موارد گاوصندوق خود را با دیگران به اشتراک بگذارید. آیا مایل به بازدید از وب سایت bitwarden.com برای کسب اطلاعات بیشتر هستید؟" + "message": "Bitwarden به شما اجازه می‌دهد با استفاده از سازماندهی، موارد گاوصندوق خود را با دیگران به اشتراک بگذارید. آیا مایل به بازدید از وب سایت bitwarden.com برای کسب اطلاعات بیشتر هستید؟" }, "moveToOrganization": { "message": "انتقال به سازمان" @@ -699,13 +699,13 @@ } }, "moveToOrgDesc": { - "message": "سازمانی را انتخاب کنید که می خواهید این مورد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت مورد را به آن سازمان منتقل می کند. پس از انتقال این مورد، دیگر مالک مستقیم آن نخواهید بود." + "message": "سازمانی را انتخاب کنید که می‌خواهید این مورد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت مورد را به آن سازمان منتقل می‌کند. پس از انتقال این مورد، دیگر مالک مستقیم آن نخواهید بود." }, "learnMore": { "message": "بیشتر بدانید" }, "authenticatorKeyTotp": { - "message": "کلید تأیید کننده (TOTP)" + "message": "کلید احراز هویت (TOTP)" }, "verificationCodeTotp": { "message": "کد تأیید (TOTP)" @@ -723,7 +723,7 @@ "message": "آیا از پاک کردن این پیوست مطمئن هستید؟" }, "deletedAttachment": { - "message": "حذف پیوست" + "message": "پیوست حذف شد" }, "newAttachment": { "message": "افزودن پیوست جدید" @@ -732,22 +732,22 @@ "message": "بدون پیوست." }, "attachmentSaved": { - "message": "پیوست ذخیره شد." + "message": "پیوست ذخیره شد" }, "file": { - "message": "فایل" + "message": "پرونده" }, "selectFile": { - "message": "انتخاب یک فایل." + "message": "ﺍﻧﺘﺨﺎﺏ یک ﭘﺮﻭﻧﺪﻩ" }, "maxFileSize": { - "message": "بیشترین حجم فایل 500 مگابایت است." + "message": "بیشترین حجم پرونده ۵۰۰ مگابایت است." }, "featureUnavailable": { "message": "ویژگی موجود نیست" }, "updateKey": { - "message": "تا زمانی که کد رمزنگاری را بروز نکنید نمیتوانید از این قابلیت استفاده کنید." + "message": "تا زمانی که کد رمزنگاری را به‌روز نکنید نمی‌توانید از این قابلیت استفاده کنید." }, "premiumMembership": { "message": "عضویت پرمیوم" @@ -756,7 +756,7 @@ "message": "مدیریت عضویت" }, "premiumManageAlert": { - "message": "شما میتوانید عضویت خود را در نسخه وب گاوصندوق در bitwarden.com مدیریت کنید. آیا مایل به دیدن وبسایت هستید؟" + "message": "شما می‌توانید عضویت خود را در نسخه وب گاوصندوق در bitwarden.com مدیریت کنید. آیا مایل به دیدن وب‌سایت هستید؟" }, "premiumRefresh": { "message": "نوسازی عضویت" @@ -765,31 +765,31 @@ "message": "شما در حال حاظر کاربر پرمیوم نیستید." }, "premiumSignUpAndGet": { - "message": "ثبت نام برای عضویت پرمیوم و گرفتن:" + "message": "برای عضویت پرمیوم ثبت نام کنید و دریافت کنید:" }, "ppremiumSignUpStorage": { - "message": "۱ گیگابایت فضای ذخیره سازی رمزنگاری شده." + "message": "۱ گیگابایت فضای ذخیره سازی رمزگذاری شده برای پیوست های پرونده." }, "ppremiumSignUpTwoStep": { - "message": "گزینه های ورود اضافی دو مرحله ای مانند YubiKey, FIDO U2F و Duo." + "message": "گزینه‌های ورود دو مرحله‌ای اضافی مانند YubiKey, FIDO U2F و Duo." }, "ppremiumSignUpReports": { - "message": "بهداشت کلمه عبور، سلامت اکانت و گزارش های نقض اطلاعات تا صندوق شما امن نگه داشته شود." + "message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." }, "ppremiumSignUpTotp": { - "message": "تولید کننده کد تایید (2FA) از نوع TOTP برای ورود به گاوصندوقتان." + "message": "تولید کننده کد تأیید (2FA) از نوع TOTP برای ورودهای در گاوصندوقتان." }, "ppremiumSignUpSupport": { "message": "اولویت پشتیبانی از مشتری." }, "ppremiumSignUpFuture": { - "message": "تمام ویژگی های پرمیوم آینده. به زودی بیشتر!" + "message": "تمام ویژگی‌های پرمیوم آینده. به زودی بیشتر!" }, "premiumPurchase": { "message": "خرید پرمیوم" }, "premiumPurchaseAlert": { - "message": "شما میوانید عضویت پرمیوم را از وب گاوصندوق bitwarden.com خریداری کنید. مایلید اکنون از وبسایت بازید کنید؟" + "message": "شما می‌توانید عضویت پرمیوم را از گاوصندوق وب bitwarden.com خریداری کنید. مایلید اکنون از وب‌سایت بازید کنید؟" }, "premiumCurrentMember": { "message": "شما یک عضو پرمیوم هستید!" @@ -813,22 +813,22 @@ "message": "TOTP را به صورت خودکار کپی کن" }, "disableAutoTotpCopyDesc": { - "message": "اگر ورود شما دارای یک کلید تأیید کننده است که به آن متصل شده است، هر زمان که بصورت خودکار وارد سایت شوید کد تأیید TOTP به صورت خودکار به کلیپ بورد شما کپی می شود." + "message": "اگر یک ورود دارای یک کلید احراز هویت باشد، هنگام پر کردن خودکار ورود، کد تأیید TOTP را در کلیپ بورد خود کپی کن." }, "enableAutoBiometricsPrompt": { "message": "درخواست بیومتریک هنگام راه اندازی" }, "premiumRequired": { - "message": "در نسخه پرمیوم کار میکند" + "message": "در نسخه پرمیوم کار می‌کند" }, "premiumRequiredDesc": { "message": "برای استفاده از این ویژگی عضویت پرمیوم لازم است." }, "enterVerificationCodeApp": { - "message": "کد ۶ رقمی تایید را از برنامه تایید کننده وارد کنید." + "message": "کد ۶ رقمی تأیید را از برنامه احراز هویت وارد کنید." }, "enterVerificationCodeEmail": { - "message": "Enter the 6 digit verification code that was emailed to", + "message": "کد ۶ رقمی تأیید را که به $EMAIL$ ایمیل شده را وارد کنید.", "placeholders": { "email": { "content": "$1", @@ -837,7 +837,7 @@ } }, "verificationCodeEmailSent": { - "message": "ایمیل تایید به $EMAIL$ ارسال شد.", + "message": "ایمیل تأیید به $EMAIL$ ارسال شد.", "placeholders": { "email": { "content": "$1", @@ -849,10 +849,10 @@ "message": "مرا به خاطر بسپار" }, "sendVerificationCodeEmailAgain": { - "message": "ارسال دوباره ایمیل کد تایید" + "message": "ارسال دوباره ایمیل کد تأیید" }, "useAnotherTwoStepMethod": { - "message": "استفاده از روش ورود دو مرحله ای دیگر" + "message": "استفاده از روش ورود دو مرحله‌ای دیگر" }, "insertYubiKey": { "message": "YubiKey خود را وارد پورت USB رایانه کنید، بعد دکمه آن را بفشارید." @@ -870,42 +870,42 @@ "message": "تأیید اعتبار در WebAuthn" }, "loginUnavailable": { - "message": "ورود به سیستم موجود نیست" + "message": "ورود به سیستم در دسترس نیست" }, "noTwoStepProviders": { - "message": "این حساب با سیستم دو مرحله ورود فعال است، با این حال، هیچ یک از ارائه دهندگان دو مرحله ای پیکربندی شده توسط این مرورگر وب پشتیبانی نمی شوند." + "message": "ورود دو مرحله‌ای برای این حساب فعال است، با این حال، هیچ یک از ارائه‌دهندگان دو مرحله‌ای پیکربندی شده توسط این مرورگر وب پشتیبانی نمی‌شوند." }, "noTwoStepProviders2": { - "message": "لطفا از یک مرورگر وب پشتیبانی شده (مانند کروم) استفاده کنید و / یا ارائه دهندگان اضافی را که در مرورگر وب بهتر پشتیانی میکنند را اضافه کنید (مانند یک برنامه تأیید کننده هویت)." + "message": "لطفاً از یک مرورگر وب پشتیبانی شده (مانند کروم) استفاده کنید و یا ارائه دهندگان اضافی را که از مرورگر وب بهتر پشتیانی می‌کنند را اضافه کنید (مانند یک برنامه احراز هویت)." }, "twoStepOptions": { - "message": "گزینه های ورود دو مرحله ای" + "message": "گزینه‌های ورود دو مرحله‌ای" }, "recoveryCodeDesc": { - "message": "دسترسی به تمامی ارائه دهندگان دو مرحله ای را از دست داده اید؟ از کد بازیابی خود برای غیرفعال سازی ارائه دهندگان دو مرحله ای از حسابتان استفاده کنید." + "message": "دسترسی به تمامی ارائه‌دهندگان دو مرحله‌ای را از دست داده‌اید؟ از کد بازیابی خود برای غیرفعال‌سازی ارائه‌دهندگان دو مرحله‌ای از حسابتان استفاده کنید." }, "recoveryCodeTitle": { "message": "کد بازیابی" }, "authenticatorAppTitle": { - "message": "برنامه تأیید کننده" + "message": "برنامه احراز هویت" }, "authenticatorAppDesc": { - "message": "از یک اپ تایید کننده (همانند Authy یا Google Authenticator) استفاده کنید تا کدهای تایید بر پایه زمان تولید کنید.", + "message": "از یک برنامه احراز هویت (مانند Authy یا Google Authenticator) استفاده کنید تا کدهای تأیید بر پایه زمان تولید کنید.", "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." }, "yubiKeyTitle": { - "message": "کلید امنیت YubiKey OTP" + "message": "کلید امنیتی YubiKey OTP" }, "yubiKeyDesc": { - "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. همراه با دستگاه های YubiKey 4،4 Nano، NEO کار میکند." + "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. همراه با دستگاه‌های YubiKey 4 ،4 Nano ،NEO کار می‌کند." }, "duoDesc": { - "message": "تایید کنید همراه با Duo Security از اپ موبایل Dou استفاده کنید، پیامک،تماس تلفنی، یا کلید امنیتی U2F.", + "message": "با Duo Security با استفاده از برنامه تلفن همراه، پیامک، تماس تلفنی، یا کلید امنیتی U2F تأیید کنید.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "Duo Security را برای سازماندهی خود با استفاده از برنامه Duo Mobile، SMS، تماس تلفنی یا کلید امنیتی U2F تأیید کنید.", + "message": "از Duo Security با استفاده از برنامه تلفن همراه، پیامک، تماس تلفنی یا کلید امنیتی U2F برای تأیید سازمان خود استفاده کنید.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "webAuthnTitle": { @@ -918,79 +918,79 @@ "message": "ایمیل" }, "emailDesc": { - "message": "کد تایید برایتان فرستاده می شود." + "message": "کد تأیید برایتان ارسال می‌شود." }, "selfHostedEnvironment": { "message": "محیط خود میزبان" }, "selfHostedEnvironmentFooter": { - "message": "آدرس اینترنتی پایه فرضی نصب Bitwarden میزبانی شده را مشخص کنید." + "message": "نشانی اینترنتی پایه فرضی نصب Bitwarden میزبانی شده را مشخص کنید." }, "customEnvironment": { - "message": "محیط های سفارشی" + "message": "محیط سفارشی" }, "customEnvironmentFooter": { - "message": "برای کاربران پیشرفته. شما می توانید آدرس پایه هر سرویس را مستقل تعیین کنید." + "message": "برای کاربران پیشرفته. شما می‌توانید نشانی پایه هر سرویس را مستقلاً تعیین کنید." }, "baseUrl": { - "message": "آدرس سرور" + "message": "نشانی اینترنتی سرور" }, "apiUrl": { - "message": "آدرس سرور API" + "message": "نشانی API سرور" }, "webVaultUrl": { - "message": "آدرس سرور گاوصندوق وب" + "message": "نشانی اینترنتی سرور گاوصندوق وب" }, "identityUrl": { - "message": "آدرس سرور شناسایی" + "message": "نشانی سرور شناسایی" }, "notificationsUrl": { - "message": "اعلانهای آدرس سرور" + "message": "نشانی سرور اعلان‌ها" }, "iconsUrl": { - "message": "آدرس سرور آیکون ها" + "message": "نشانی سرور آیکون ها" }, "environmentSaved": { - "message": "آدرس های اینترنتی محیط ذخیره شد." + "message": "نشانی‌های اینترنتی محیط ذخیره شد" }, "enableAutoFillOnPageLoad": { - "message": "فعال کردن پرکردن خودکار هنگام بارگذاری صفحه" + "message": "پر کردن خودکار هنگام بارگذاری صفحه" }, "enableAutoFillOnPageLoadDesc": { - "message": "اگر یک فرم ورودی شناسایی شود، وقتی صفحه وب بارگذاری می شود، به صورت اتوماتیک انجام می شود." + "message": "اگر یک فرم ورودی شناسایی شد، وقتی صفحه وب بارگذاری شد، به صورت خودکار پر شود." }, "experimentalFeature": { "message": "در حال حاضر این یک ویژگی آزمایشی است. با مسئولیت خود استفاده کنید." }, "defaultAutoFillOnPageLoad": { - "message": "تنظیم خودکار پیش فرض برای موارد ورود به سیستم" + "message": "تنظیم تکمیل خودکار پیش‌فرض برای موارد ورود به سیستم" }, "defaultAutoFillOnPageLoadDesc": { - "message": "پس از فعال کردن پرکردن خودکار بارگذاری صفحه، می توانید این ویژگی را برای موارد ورود به سیستم جداگانه فعال یا غیرفعال کنید. این تنظیمات پیش فرض برای موارد ورود به سیستم است که به طور جداگانه پیکربندی نشده اند." + "message": "می‌توانید پر کردن خودکار هنگام بارگیری صفحه را برای موارد ورود به سیستم از نمای ویرایش مورد خاموش کنید." }, "itemAutoFillOnPageLoad": { "message": "پر کردن خودکار بارگذاری صفحه (درصورت فعال بودن در گزینه ها)" }, "autoFillOnPageLoadUseDefault": { - "message": "استفاده از تنظيمات پيشفرض" + "message": "استفاده از تنظیمات پیش‌فرض" }, "autoFillOnPageLoadYes": { - "message": "بارگذاری صفحه را به طور خودکار پر کن" + "message": "پر کردن خودکار هنگام بارگذاری صفحه" }, "autoFillOnPageLoadNo": { - "message": "بارگذاری صفحه را به طور خودکار پر نکن" + "message": "هنگام بارگذاری صفحه پر کردن خودکار انجام نده" }, "commandOpenPopup": { - "message": "بازکردن پنجره گاوصندوق" + "message": "باز کردن پنجره گاوصندوق" }, "commandOpenSidebar": { - "message": "بازکردن گاوصندوق در نوار کناری" + "message": "باز کردن گاوصندوق در نوار کناری" }, "commandAutofillDesc": { - "message": "آخرین ورودی مورد استفاده برای وب سایت فعلی را به صورت خودکار پر کنید." + "message": "آخرین ورودی مورد استفاده برای وب سایت فعلی را به صورت خودکار پر کنید" }, "commandGeneratePasswordDesc": { - "message": "یک کلمه عبور تصادفی جدید ایجاد کنید و آن را در کلیپ بورد کپی کنید." + "message": "یک کلمه عبور تصادفی جدید ایجاد کنید و آن را در کلیپ بورد کپی کنید" }, "commandLockVaultDesc": { "message": "قفل گاوصندوق" @@ -1011,7 +1011,7 @@ "message": "فیلد سفارشی جدید" }, "dragToSort": { - "message": "برای مرتب سازی بکشید" + "message": "برای مرتب‌سازی بکشید" }, "cfTypeText": { "message": "متن" @@ -1020,24 +1020,24 @@ "message": "مخفی" }, "cfTypeBoolean": { - "message": "بولین" + "message": "منطقی" }, "cfTypeLinked": { - "message": "لینک شده", + "message": "پیوند شده", "description": "This describes a field that is 'linked' (tied) to another field." }, "linkedValue": { - "message": "ارزش لینک شده", + "message": "مقدار پیوند شده", "description": "This describes a value that is 'linked' (tied) to another value." }, "popup2faCloseMessage": { - "message": "در خارج از پنجره پاپ آپ کلیک کنید که ایملیتان بررسی شود برای کد تأییدیه که باعث میشود این پنجره بسته شود. آیا می خواهید این پاپ آپ را در یک پنجره جدید باز کنید تا آن را نبندید؟" + "message": "در خارج از پنجره پاپ آپ کلیک کنید که ایمیل‌تان بررسی شود برای کد تأییدیه که باعث می‌شود این پنجره بسته شود. آیا می‌خواهید این پاپ آپ را در یک پنجره جدید باز کنید تا آن را نبندید؟" }, "popupU2fCloseMessage": { - "message": "این مرورگر نمیتواند درخواستهای U2F را در این پنجره پاپ آپ پردازش کند. آیا می خواهید این پنجره را در یک پنجره جدید باز کنید تا بتوانید با استفاده از U2F وارد شوید؟" + "message": "این مرورگر نمی‌تواند درخواستهای U2F را در این پنجره پاپ آپ پردازش کند. آیا می‌خواهید این پنجره را در یک پنجره جدید باز کنید تا بتوانید با استفاده از U2F وارد شوید؟" }, "enableFavicon": { - "message": "نمایش نمادهای وب سایت" + "message": "نمایش نمادهای وب‌سایت" }, "faviconDesc": { "message": "یک تصویر قابل تشخیص در کنار هر ورود نشان دهید." @@ -1106,7 +1106,7 @@ "message": "کد امنیتی" }, "ex": { - "message": "سابق." + "message": "مثال." }, "title": { "message": "عنوان" @@ -1118,7 +1118,7 @@ "message": "خانم" }, "ms": { - "message": "خانم" + "message": "بانو" }, "dr": { "message": "دکتر" @@ -1136,19 +1136,19 @@ "message": "نام کامل" }, "identityName": { - "message": "نام شناسایی" + "message": "نام هویت" }, "company": { "message": "شرکت" }, "ssn": { - "message": "شماره امنیتی اجتماعی" + "message": "کد ملی" }, "passportNumber": { - "message": "شماره پاسپورت" + "message": "شماره گذرنامه" }, "licenseNumber": { - "message": "شماره مجوز" + "message": "شماره گواهی‌نامه" }, "email": { "message": "ایمیل" @@ -1157,7 +1157,7 @@ "message": "تلفن" }, "address": { - "message": "آدرس" + "message": "نشانی" }, "address1": { "message": "نشانی ۱" @@ -1187,7 +1187,7 @@ "message": "ورود" }, "typeLogins": { - "message": "ورود" + "message": "ورودها" }, "typeSecureNote": { "message": "یادداشت امن" @@ -1196,7 +1196,7 @@ "message": "کارت" }, "typeIdentity": { - "message": "مشخصات" + "message": "هویت" }, "passwordHistory": { "message": "تاریخچه کلمه عبور" @@ -1205,38 +1205,38 @@ "message": "بازگشت" }, "collections": { - "message": "مجموعه ها" + "message": "مجموعه‌ها" }, "favorites": { - "message": "مورد علاقه" + "message": "مورد علاقه‌ها" }, "popOutNewWindow": { - "message": "به یک پنجره جدید پاپ بزنید" + "message": "به یک پنجره جدید پاپ بزن" }, "refresh": { "message": "تازه کردن" }, "cards": { - "message": "کارتها" + "message": "کارت‌ها" }, "identities": { - "message": "مشخصات" + "message": "هویت‌ها" }, "logins": { - "message": "ورود" + "message": "ورودها" }, "secureNotes": { - "message": "یادداشت های امن" + "message": "یادداشت‌های امن" }, "clear": { "message": "پاک کردن", "description": "To clear something out. example: To clear browser history." }, "checkPassword": { - "message": "بررسی اینکه که آیا کلمه عبور افشا شده است." + "message": "بررسی کنید که آیا کلمه عبور افشا شده است." }, "passwordExposed": { - "message": "This password has been exposed in data breaches. You should change it.", + "message": "این کلمه عبور $VALUE$ بار در رخنه داده‌ها افشا شده است. باید آن را تغییر دهید.", "placeholders": { "value": { "content": "$1", @@ -1245,7 +1245,7 @@ } }, "passwordSafe": { - "message": "این کلمه عبور در هیچ رخنه داده ای شناخته نشده است. باید برای امنیت از آن استفاده کنید." + "message": "این کلمه عبور در هیچ رخنه داده ای شناخته نشده است. استفاده از آن باید ایمن باشد." }, "baseDomain": { "message": "دامنه پایه", @@ -1263,18 +1263,18 @@ "message": "دقیق" }, "startsWith": { - "message": "شروع می شود با" + "message": "شروع می‌شود با" }, "regEx": { "message": "عبارت منظم", "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "بازرسی تطابق", + "message": "تشخیص مطابقت", "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { - "message": "بازرسی تطابق پیشفرض", + "message": "بررسی مطابقت پیش‌فرض", "description": "Default URI match detection for auto-fill." }, "toggleOptions": { @@ -1289,7 +1289,7 @@ "description": "The URI of one of the current open tabs in the browser." }, "organization": { - "message": "سازماندهی", + "message": "سازمان", "description": "An entity of multiple related people (ex. a team or business organization)." }, "types": { @@ -1299,31 +1299,31 @@ "message": "تمام موارد" }, "noPasswordsInList": { - "message": "هیچ کلمه عبوری در لیست وجود ندارد." + "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, "remove": { "message": "حذف" }, "default": { - "message": "پیش فرض" + "message": "پیش‌فرض" }, "dateUpdated": { - "message": "بروزرسانی شد", + "message": "به‌روزرسانی شد", "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "ایجاد شده", + "message": "ایجاد شد", "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "کلمه عبور بروزرسانی شد", + "message": "کلمه عبور به‌روزرسانی شد", "description": "ex. Date this password was updated" }, "neverLockWarning": { - "message": "آیا جدا میخواهید از گزینه \"هرگز\" استفاده کنید؟ تنظیم کردن کردن گزینه قفل به \"هرگز\" کلیدهای رمزنگاری گاوصندوقتان را بر روی دستگاه شما ذخیره خواهد کرد. اگر از این گزینه استفاده میکنید باید اطمینان داشته باشید که دستگاه شما کاملا محافظت شده است." + "message": "آیا جداً می‌خواهید از گزینه \"هرگز\" استفاده کنید؟ تنظیم کردن گزینه قفل به \"هرگز\" کلیدهای رمزنگاری گاوصندوقتان را بر روی دستگاه شما ذخیره خواهد کرد. اگر از این گزینه استفاده می‌کنید باید اطمینان داشته باشید که دستگاه شما کاملا محافظت شده است." }, "noOrganizationsList": { - "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." + "message": "شما به هیچ سازمانی تعلق ندارید. سازمان‌ها به شما اجازه می‌دهند تا داده‌های خود را با کاربران دیگر به صورت امن به اشتراک بگذارید." }, "noCollectionsInList": { "message": "هیچ مجموعه ای برای لیست کردن وجود ندارد." @@ -1357,16 +1357,16 @@ "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { - "message": "بازکردن با پین" + "message": "باز کردن با پین" }, "setYourPinCode": { - "message": "پین کد خود را برای باز کردن بیت واردن خود استفاده کنید. اگر به صورت کامل از حساب خود خارج شوید تنظیمات پین شما ریست می شود." + "message": "کد پین خود را برای باز کردن Bitwarden تنظیم کنید. اگر به طور کامل از برنامه خارج شوید، تنظیمات پین شما از بین می‌رود." }, "pinRequired": { "message": "کد پین الزامیست." }, "invalidPin": { - "message": "کد پین معتبر نیست. " + "message": "کد پین معتبر نیست." }, "unlockWithBiometrics": { "message": "با استفاده از بیومتریک باز کنید" @@ -1378,7 +1378,7 @@ "message": "لطفاً استفاده از بیومتریک را در برنامه دسکتاپ Bitwarden تأیید کنید تا بیومتریک را برای مرورگر فعال کنید." }, "lockWithMasterPassOnRestart": { - "message": "در زمان شروع مجدد مرورگر، با رمز اصلی قفل کن" + "message": "در زمان شروع مجدد، با کلمه عبور اصلی قفل کن" }, "selectOneCollection": { "message": "شما باید حداقل یک مجموعه را انتخاب کنید." @@ -1390,7 +1390,7 @@ "message": "شبیه سازی" }, "passwordGeneratorPolicyInEffect": { - "message": "یک یا چند خط مشی سازمان بر تنظیمات تولیدکننده شما تأثیر می گذارد." + "message": "یک یا چند سیاست سازمان بر تنظیمات تولید کننده شما تأثیر می‌گذارد." }, "vaultTimeoutAction": { "message": "عمل متوقف شدن گاو‌صندوق" @@ -1410,7 +1410,7 @@ "message": "حذف دائمی مورد" }, "permanentlyDeleteItemConfirmation": { - "message": "آیا مطمئن هستید که می خواهید این مورد را برای همیشه حذف کنید؟" + "message": "مطمئن هستید که می‌خواهید این مورد را برای همیشه پاک کنید؟" }, "permanentlyDeletedItem": { "message": "مورد برای همیشه حذف شد" @@ -1419,22 +1419,22 @@ "message": "بازیابی مورد" }, "restoreItemConfirmation": { - "message": "آیا مطمئن هستید می خواهید این مورد را بازیابی کنید؟" + "message": "آیا مطمئن هستید که می‌خواهید این مورد را بازیابی کنید؟" }, "restoredItem": { "message": "مورد بازیابی شد" }, "vaultTimeoutLogOutConfirmation": { - "message": "خروج از سیستم تمام دسترسی ها به گاو‌صندوق شما را از بین می برد و نیاز به احراز هویت آنلاین پس از مدت زمان توقف دارد. آیا مطمئن هستید که می خواهید از این تنظیمات استفاده کنید؟" + "message": "خروج از سیستم، تمام دسترسی ها به گاو‌صندوق شما را از بین می‌برد و نیاز به احراز هویت آنلاین پس از مدت زمان توقف دارد. آیا مطمئن هستید که می‌خواهید از این تنظیمات استفاده کنید؟" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "تایید عمل توقف" + "message": "تأیید عمل توقف" }, "autoFillAndSave": { - "message": "پرکردن خودکار و ذخیره" + "message": "پر کردن خودکار و ذخیره" }, "autoFillSuccessAndSavedUri": { - "message": "مورد خودکار پر شد و آدرس اینترنتی ذخیره شد" + "message": "مورد خودکار پر شد و نشانی اینترنتی ذخیره شد" }, "autoFillSuccess": { "message": "مورد خودکار پر شد" @@ -1482,10 +1482,10 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پپیروی نمی کند." + "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پیروی نمی‌کند." }, "acceptPolicies": { - "message": "با علامت زدن این کادر با موارد زیر موافقت می کنید:" + "message": "با علامت زدن این کادر با موارد زیر موافقت می‌کنید:" }, "acceptPoliciesRequired": { "message": "شرایط خدمات و سیاست حفظ حریم خصوصی تأیید نشده است." @@ -1497,16 +1497,16 @@ "message": "سیاست حفظ حریم خصوصی" }, "hintEqualsPassword": { - "message": "نکته کلمه عبور شما نمی تواند همان کلمه عبور شما باشد." + "message": "اشاره به کلمه عبور شما نمی‌تواند همان کلمه عبور شما باشد." }, "ok": { - "message": "تایید" + "message": "تأیید" }, "desktopSyncVerificationTitle": { - "message": "تأیید همگام سازی دسکتاپ" + "message": "تأیید همگام‌سازی دسکتاپ" }, "desktopIntegrationVerificationText": { - "message": "لطفاً تأیید کنید که برنامه دسکتاپ این اثر انگشت را نشان می دهد:" + "message": "لطفاً تأیید کنید که برنامه دسکتاپ این اثر انگشت را نشان می‌دهد:" }, "desktopIntegrationDisabledTitle": { "message": "ادغام مرورگر فعال نیست" @@ -1536,37 +1536,37 @@ "message": "برنامه دسکتاپ به یک حساب دیگر وارد شده است. لطفاً اطمینان حاصل کنید که هر دو برنامه به یک حساب وارد شده اند." }, "nativeMessagingWrongUserTitle": { - "message": "عدم تطابق حساب" + "message": "عدم مطابقت حساب کاربری" }, "biometricsNotEnabledTitle": { - "message": "بیومتریک فعال نیست" + "message": "بیومتریک برپا نشده" }, "biometricsNotEnabledDesc": { "message": "بیومتریک مرورگر ابتدا نیاز به فعالسازی بیومتریک دسکتاپ در تنظیمات دارد." }, "biometricsNotSupportedTitle": { - "message": "بیومتریک پشتیبانی نمی شود" + "message": "بیومتریک پشتیبانی نمی‌شود" }, "biometricsNotSupportedDesc": { - "message": "بیومتریک مرورگر در این دستگاه پشتیبانی نمی شود." + "message": "بیومتریک مرورگر در این دستگاه پشتیبانی نمی‌شود." }, "nativeMessaginPermissionErrorTitle": { "message": "مجوز ارائه نشده است" }, "nativeMessaginPermissionErrorDesc": { - "message": "بدون اجازه ارتباط با برنامه دسکتاپ Bitwarden ، نمی توانیم بیومتریک را در افزونه مرورگر ارائه دهیم. لطفا دوباره تلاش کنید." + "message": "بدون اجازه برای ارتباط با برنامه دسکتاپ Bitwarden، نمی‌توانیم بیومتریک را در افزونه مرورگر ارائه دهیم. لطفاً دوباره تلاش کنید." }, "nativeMessaginPermissionSidebarTitle": { "message": "خطای درخواست مجوز" }, "nativeMessaginPermissionSidebarDesc": { - "message": "این عمل را نمی توان در نوار کناری انجام داد، لطفاً اقدام را در پنجره بازشو یا پنجره بازخوانی کنید." + "message": "این عمل را نمی‌توان در نوار کناری انجام داد، لطفاً اقدام را در پنجره بازشو بازخوانی کنید." }, "personalOwnershipSubmitError": { - "message": "به دلیل خط مشی Enterprise ، برای ذخیره موارد در گاوصندوق شخصی خود محدود شده اید. گزینه مالکیت را به یک سازمان تغییر دهید و مجموعه های موجود را انتخاب کنید." + "message": "به دلیل سیاست پرمیوم، برای ذخیره موارد در گاوصندوق شخصی خود محدود شده اید. گزینه مالکیت را به یک سازمان تغییر دهید و مجموعه های موجود را انتخاب کنید." }, "personalOwnershipPolicyInEffect": { - "message": "خط مشی سازمانی بر تنظیمات مالکیت شما تأثیر می گذارد." + "message": "سیاست سازمانی بر تنظیمات مالکیت شما تأثیر می‌گذارد." }, "excludedDomains": { "message": "دامنه های مستثنی" @@ -1588,7 +1588,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "searchSends": { - "message": "ارسال ها را جستجو کن", + "message": "جستجوی ارسال‌ها", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "addSend": { @@ -1599,7 +1599,7 @@ "message": "متن" }, "sendTypeFile": { - "message": "فایل" + "message": "پرونده" }, "allSends": { "message": "همه ارسال ها", @@ -1632,7 +1632,7 @@ "message": "کلمه عبور حذف شد" }, "deletedSend": { - "message": "ارسال پاک شد", + "message": "ارسال حذف شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { @@ -1643,14 +1643,14 @@ "message": "غیرفعال شد" }, "removePasswordConfirmation": { - "message": "مطمئنید می‌خواهید این کلمه عبور حذف شود؟" + "message": "مطمئنید که می‌خواهید کلمه عبور حذف شود؟" }, "deleteSend": { - "message": "ارسال پاک شد", + "message": "ارسال حذف شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendConfirmation": { - "message": "آیا مطمئن هستید می خواهید این ارسال را حذف کنید؟", + "message": "آیا مطمئن هستید که می‌خواهید این ارسال را حذف کنید؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -1666,7 +1666,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFileDesc": { - "message": "فایلی که می خواهید ارسال کنید." + "message": "پرونده ای که می‌خواهید ارسال کنید." }, "deletionDate": { "message": "تاریخ حذف" @@ -1679,7 +1679,7 @@ "message": "تاريخ انقضاء" }, "expirationDateDesc": { - "message": "در صورت تنظیم، دسترسی به این ارسال در تاریخ و ساعت مشخص شده منقضی می شود.", + "message": "در صورت تنظیم، دسترسی به این ارسال در تاریخ و ساعت مشخص شده منقضی می‌شود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "oneDay": { @@ -1701,7 +1701,7 @@ "message": "تعداد دسترسی حداکثر" }, "maximumAccessCountDesc": { - "message": "در صورت تنظیم، با رسیدن به حداکثر تعداد دسترسی، کاربران دیگر نمی توانند به این ارسال دسترسی پیدا کنند.", + "message": "در صورت تنظیم، با رسیدن به حداکثر تعداد دسترسی، کاربران دیگر نمی‌توانند به این ارسال دسترسی پیدا کنند.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendPasswordDesc": { @@ -1721,7 +1721,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { - "message": "متنی که می خواهید ارسال کنید." + "message": "متنی که می‌خواهید ارسال کنید." }, "sendHideText": { "message": "متن این ارسال را به طور پیش فرض پنهان کن.", @@ -1731,36 +1731,36 @@ "message": "تعداد دسترسی فعلی" }, "createSend": { - "message": "ساختن ارسال جدید", + "message": "ارسال جدید", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { "message": "کلمه عبور جدید" }, "sendDisabled": { - "message": "ارسال غیرفعال شد", + "message": "ارسال حذف شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "به دلیل خط مشی سازمانی، شما فقط می توانید ارسال موجود را حذف کنید.", + "message": "به دلیل سیاست سازمانی، شما فقط می‌توانید ارسال موجود را حذف کنید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "ارسال جدید ساخته شد", + "message": "ارسال ساخته شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "ارسال جدید ویرایش شد", + "message": "ارسال ذخیره شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { - "message": "برای انتخاب پرونده، پسوند را در نوار کناری باز کنید (در صورت امکان) یا با کلیک بر روی این آگهی به پنجره جدیدی باز شوید." + "message": "برای انتخاب پرونده، پسوند را در نوار کناری باز کنید (در صورت امکان) یا با کلیک بر روی این بنر پنجره جدیدی باز کنید." }, "sendFirefoxFileWarning": { - "message": "برای انتخاب یک فایل با استفاده از Firefox، افزونه را در نوار کناری باز کنید یا با کلیک بر روی این بنر به پنجره جدید باز شوید." + "message": "برای انتخاب یک پرونده با استفاده از Firefox، افزونه را در نوار کناری باز کنید یا با کلیک بر روی این بنر پنجره جدیدی باز کنید." }, "sendSafariFileWarning": { - "message": "برای انتخاب فایلی با استفاده از Safari، با کلیک روی این بنر به پنجره جدیدی باز شوید." + "message": "برای انتخاب پرونده ای با استفاده از Safari، با کلیک روی این بنر پنجره جدیدی باز کنید." }, "sendFileCalloutHeader": { "message": "قبل از اینکه شروع کنی" @@ -1778,61 +1778,61 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" }, "expirationDateIsInvalid": { - "message": "تاریخ انقضا ارائه شده معتبر نیست." + "message": "تاریخ انقضاء ارائه شده معتبر نیست." }, "deletionDateIsInvalid": { "message": "تاریخ حذف ارائه شده معتبر نیست." }, "expirationDateAndTimeRequired": { - "message": "تاریخ انقضا و زمان لازم است." + "message": "تاریخ انقضاء و زمان لازم است." }, "deletionDateAndTimeRequired": { "message": "تاریخ و زمان حذف لازم است." }, "dateParsingError": { - "message": "هنگام ذخیره حذف و تاریخ انقضا شما خطایی روی داد." + "message": "هنگام ذخیره حذف و تاریخ انقضاء شما خطایی روی داد." }, "hideEmail": { - "message": "آدرس ایمیلم را از گیرندگان مخفی کن." + "message": "نشانی ایمیلم را از گیرندگان مخفی کن." }, "sendOptionsPolicyInEffect": { - "message": "یک یا چند سیاست سازمان بر گزینه های ارسال شما تأثیر می گذارد." + "message": "یک یا چند سیاست سازمان بر گزینه های ارسال شما تأثیر می‌گذارد." }, "passwordPrompt": { - "message": "کلمه عبور اصلی دوباره تولید می شود" + "message": "درخواست مجدد کلمه عبور اصلی" }, "passwordConfirmation": { "message": "تأیید کلمه عبور اصلی" }, "passwordConfirmationDesc": { - "message": "این عمل محافظت می شود. برای ادامه، لطفاً کلمه ورود اصلی خود را دوباره وارد کنید تا هویتان را تأیید کنید." + "message": "این عمل محافظت می‌شود. برای ادامه، لطفاً کلمه عبور اصلی خود را دوباره وارد کنید تا هویت‌تان را تأیید کنید." }, "emailVerificationRequired": { - "message": "تایید ایمیل لازم است" + "message": "تأیید ایمیل لازم است" }, "emailVerificationRequiredDesc": { - "message": "برای استفاده از این ویژگی باید ایمیل خود را تأیید کنید. می توانید ایمیل خود را در گاوصندوق وب تأیید کنید." + "message": "برای استفاده از این ویژگی باید ایمیل خود را تأیید کنید. می‌توانید ایمیل خود را در گاوصندوق وب تأیید کنید." }, "updatedMasterPassword": { - "message": "کلمه عبور اصلی بروز شد" + "message": "کلمه عبور اصلی به‌روز شد" }, "updateMasterPassword": { - "message": "بروزرسانی کلمه عبور اصلی" + "message": "به‌روزرسانی کلمه عبور اصلی" }, "updateMasterPasswordWarning": { - "message": "کلمه عبور اصلی شما اخیراً توسط سرپرست سازمانتان تغییر کرده است. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." + "message": "کلمه عبور اصلی شما اخیراً توسط سرپرست سازمان‌تان تغییر کرده است. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, "resetPasswordPolicyAutoEnroll": { "message": "ثبت نام خودکار" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "این سازمان دارای خط مشی سازمانی ای است که به طور خودکار شما را در بازنشانی کلمه عبور ثبت نام می کند. این ثبت نام به مدیران سازمان اجازه می دهد تا کلمه عبور اصلی شما را تغییر دهند." + "message": "این سازمان دارای سیاست سازمانی ای است که به طور خودکار شما را در بازنشانی کلمه عبور ثبت نام می‌کند. این ثبت نام به مدیران سازمان اجازه می‌دهد تا کلمه عبور اصلی شما را تغییر دهند." }, "selectFolder": { "message": "پوشه را انتخاب کنید..." }, "ssoCompleteRegistration": { - "message": "برای تکمیل ورود به سیستم با SSO ، لطفاً یک کلمه عبور اصلی برای دسترسی و محافظت از گاوصندوق خود تنظیم کنید." + "message": "برای پر کردن ورود به سیستم با SSO، لطفاً یک کلمه عبور اصلی برای دسترسی و محافظت از گاوصندوق خود تنظیم کنید." }, "hours": { "message": "ساعت" @@ -1841,7 +1841,7 @@ "message": "دقیقه" }, "vaultTimeoutPolicyInEffect": { - "message": "خط مشی های سازمانتان بر مهلت زمانی گاوصندوق شما تأثیر می گذارد. حداکثر زمان مجاز گاوصندوق $HOURS$ ساعت و $MINUTES$ دقیقه است", + "message": "سیاست‌های سازمانتان بر مهلت زمانی گاوصندوق شما تأثیر می‌گذارد. حداکثر زمان مجاز گاوصندوق $HOURS$ ساعت و $MINUTES$ دقیقه است", "placeholders": { "hours": { "content": "$1", @@ -1857,10 +1857,10 @@ "message": "مهلت زمانی شما بیش از محدودیت های تعیین شده توسط سازمانتان است." }, "vaultExportDisabled": { - "message": "صادرات گاوصندوق غیرفعال شده است" + "message": "برون ریزی گاوصندوق غیرفعال شده است" }, "personalVaultExportPolicyInEffect": { - "message": "یک یا چند خط مشی سازمان از صادرات گاوصندوق شخصی شما جلوگیری می کند." + "message": "یک یا چند سیاست سازمان از برون ریزی گاوصندوق شخصی شما جلوگیری می‌کند." }, "copyCustomFieldNameInvalidElement": { "message": "شناسایی عنصر فرم معتبر امکان پذیر نیست. به جای آن سعی کنید HTML را بررسی کنید." @@ -1881,16 +1881,16 @@ "message": "ترک سازمان" }, "removeMasterPassword": { - "message": "پاک کردن کلمه عبور اصلی" + "message": "حذف کلمه عبور اصلی" }, "removedMasterPassword": { - "message": "کلمه عبور اصلی پاک شد." + "message": "کلمه عبور اصلی حذف شد" }, "leaveOrganizationConfirmation": { "message": "آيا مطمئن هستيد که می خواهيد سازمان های انتخاب شده را ترک کنيد؟" }, "leftOrganization": { - "message": "شما از سازمان ها خارج شده اید." + "message": "شما از سازمان خارج شده اید." }, "toggleCharacterCount": { "message": "تغییر تعداد کاراکترها" @@ -1899,10 +1899,10 @@ "message": "زمان نشست شما به پایان رسید. لطفاً برگردید و دوباره وارد سیستم شوید." }, "exportingPersonalVaultTitle": { - "message": "صادرات گاو‌صندوق شخصی" + "message": "برون ریزی گاو‌صندوق شخصی" }, "exportingPersonalVaultDescription": { - "message": "فقط موارد گاو‌صندوق شخصی مرتبط با $EMAIL$ صادر خواهد شد. موارد گاو‌صندوق سازمان شامل نخواهد شد.", + "message": "فقط موارد گاو‌صندوق شخصی مرتبط با $EMAIL$ برون ریزی خواهد شد. موارد گاو‌صندوق سازمان شامل نخواهد شد.", "placeholders": { "email": { "content": "$1", @@ -1923,14 +1923,14 @@ "message": "نوع نام کاربری" }, "plusAddressedEmail": { - "message": "به علاوه ایمیل آدرس داده شده", + "message": "به علاوه نشانی ایمیل داده شده", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { "message": "از قابلیت های آدرس دهی فرعی ارائه دهنده ایمیل خود استفاده کنید." }, "catchallEmail": { - "message": "رایانامه همه‌گیر" + "message": "دریافت همه ایمیل‌ها" }, "catchallEmailDesc": { "message": "از صندوق ورودی پیکربندی شده دامنه خود استفاده کنید." @@ -1942,22 +1942,22 @@ "message": "کلمه تصادفی" }, "websiteName": { - "message": "نام وب سایت" + "message": "نام وب‌سایت" }, "whatWouldYouLikeToGenerate": { "message": "چه چیزی دوست دارید تولید کنید؟" }, "passwordType": { - "message": "نوع گذرواژه" + "message": "نوع کلمه عبور" }, "service": { - "message": "خدمت" + "message": "سرویس" }, "forwardedEmail": { "message": "نام مستعار ایمیل فوروارد شده" }, "forwardedEmailDesc": { - "message": "یک نام مستعار ایمیل با یک سرویس فروارد خارجی ایجاد کنید." + "message": "یک نام مستعار ایمیل با یک سرویس ارسال خارجی ایجاد کنید." }, "hostname": { "message": "نام میزبان", @@ -1970,7 +1970,7 @@ "message": "کلید API" }, "ssoKeyConnectorError": { - "message": "خطای Key Connector: مطمئن شوید که Key Connector در دسترس است و به درستی کار می کند." + "message": "خطای رابط کلید: مطمئن شوید که رابط کلید در دسترس است و به درستی کار می‌کند." }, "premiumSubcriptionRequired": { "message": "اشتراک پرمیوم نیاز است" @@ -2006,7 +2006,7 @@ "message": "نسخه سرور" }, "selfHosted": { - "message": "میزبانی خود" + "message": "خود میزبان" }, "thirdParty": { "message": "شخص ثالث" diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 35cee204848..7fbf60b0d3d 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -32,22 +32,22 @@ "message": "Soumettre" }, "emailAddress": { - "message": "Adresse e-mail" + "message": "Adresse électronique" }, "masterPass": { - "message": "Mot de passe maître" + "message": "Mot de passe principal" }, "masterPassDesc": { - "message": "Le mot de passe maître est le mot de passe que vous utilisez pour accéder à votre coffre. Il est très important de ne pas l'oublier. Il n'existe aucun moyen de le récupérer si vous le perdez." + "message": "Le mot de passe principal est le mot de passe que vous utilisez pour accéder à votre coffre. Il est très important de ne pas oublier votre mot de passe principal. Il n'existe aucun moyen de récupérer le mot de passe si vous l'oubliez." }, "masterPassHintDesc": { - "message": "Un indice de mot de passe maître peut vous aider à vous rappeler de votre mot de passe en cas d'oubli." + "message": "Un indice de mot de passe principal peut vous aider à vous souvenir de votre mot de passe si vous l'oubliez." }, "reTypeMasterPass": { - "message": "Saisir à nouveau le mot de passe maître" + "message": "Ressaisir le mot de passe principal" }, "masterPassHint": { - "message": "Indice du mot de passe maître (facultatif)" + "message": "Indice du mot de passe principal (facultatif)" }, "tab": { "message": "Onglet" @@ -107,7 +107,7 @@ "message": "Connectez-vous à votre coffre" }, "autoFillInfo": { - "message": "Il n'existe aucun site disponible pour le remplissage automatique pour l'onglet actuel du navigateur." + "message": "Il n'y a pas d'identifiants disponibles pour la saisie automatique de l'onglet actuel du navigateur." }, "addLogin": { "message": "Ajouter un site" @@ -119,10 +119,10 @@ "message": "Indice mot de passe" }, "enterEmailToGetHint": { - "message": "Saisissez l'adresse e-mail de votre compte pour recevoir l'indice de votre mot de passe maître." + "message": "Saisissez l'adresse électronique de votre compte pour recevoir l'indice de votre mot de passe principal." }, "getMasterPasswordHint": { - "message": "Obtenir l'indice du mot de passe maître" + "message": "Obtenir l'indice du mot de passe principal" }, "continue": { "message": "Continuer" @@ -146,7 +146,7 @@ "message": "Compte" }, "changeMasterPassword": { - "message": "Changer le mot de passe maître" + "message": "Changer le mot de passe principal" }, "fingerprintPhrase": { "message": "Phrase d'empreinte", @@ -157,10 +157,10 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "twoStepLogin": { - "message": "Identification à deux facteurs" + "message": "Authentification à deux facteurs" }, "logOut": { - "message": "Déconnexion" + "message": "Se déconnecter" }, "about": { "message": "À propos" @@ -212,13 +212,13 @@ "description": "Short for 'Password Generator'." }, "passGenInfo": { - "message": "Générer automatiquement des mots de passe forts et uniques pour vos identifiants." + "message": "Générer automatiquement des mots de passe robustes et uniques pour vos identifiants." }, "bitWebVault": { "message": "Coffre en ligne de bitwarden" }, "importItems": { - "message": "Importer des identifiants" + "message": "Importer des éléments" }, "select": { "message": "Sélectionner" @@ -261,10 +261,10 @@ "message": "Inclure le numéro" }, "minNumbers": { - "message": "Nombre minimum de chiffres" + "message": "Minimum de chiffres" }, "minSpecial": { - "message": "Nombre minimum de caractères spéciaux" + "message": "Minimum de caractères spéciaux" }, "avoidAmbChar": { "message": "Éviter les caractères ambigus" @@ -282,7 +282,7 @@ "message": "Aucun identifiant à afficher." }, "itemInformation": { - "message": "Information sur l'élément" + "message": "Informations sur l'élément" }, "username": { "message": "Nom d'utilisateur" @@ -303,16 +303,16 @@ "message": "Note" }, "editItem": { - "message": "Modifier l'identifiant" + "message": "Éditer l'élément" }, "folder": { "message": "Dossier" }, "deleteItem": { - "message": "Supprimer l'identifiant" + "message": "Supprimer l'élément" }, "viewItem": { - "message": "Voir l'élément" + "message": "Afficher l'élément" }, "launch": { "message": "Ouvrir" @@ -361,7 +361,7 @@ } }, "invalidMasterPassword": { - "message": "Mot de passe maître invalide" + "message": "Mot de passe principal invalide" }, "vaultTimeout": { "message": "Délai d'expiration du coffre" @@ -424,22 +424,22 @@ "message": "Adresse e-mail invalide." }, "masterPasswordRequired": { - "message": "Le mot de passe maître est requis." + "message": "Le mot de passe principal est requis." }, "confirmMasterPasswordRequired": { - "message": "Le mot de passe maître doit être entré de nouveau." + "message": "Une nouvelle saisie du mot de passe principal est nécessaire." }, "masterPasswordMinlength": { - "message": "Le mot de passe maître doit au moins contenir 8 caractères." + "message": "Le mot de passe principal doit comporter au moins 8 caractères." }, "masterPassDoesntMatch": { - "message": "La confirmation du mot de passe maître ne correspond pas." + "message": "La confirmation du mot de passe principal ne correspond pas." }, "newAccountCreated": { "message": "Votre nouveau compte a été créé ! Vous pouvez maintenant vous authentifier." }, "masterPassSent": { - "message": "Nous vous avons envoyé un e-mail contenant votre indice de mot de passe maître." + "message": "Nous vous avons envoyé un courriel avec votre indice de mot de passe principal." }, "verificationCodeRequired": { "message": "Le code de vérification est requis." @@ -485,13 +485,13 @@ "message": "Dossier ajouté" }, "changeMasterPass": { - "message": "Modifier le mot de passe maître" + "message": "Changer le mot de passe principal" }, "changeMasterPasswordConfirmation": { - "message": "Vous pouvez modifier votre mot de passe maître depuis le coffre web sur bitwarden.com. Souhaitez-vous visiter le site maintenant ?" + "message": "Vous pouvez changer votre mot de passe principal depuis le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" }, "twoStepLoginConfirmation": { - "message": "L'identification en deux étapes rend votre compte plus sécurisé en vous demandant de saisir un code de sécurité depuis une application d'authentification à chaque fois que vous vous identifiez. L'identification en deux étapes peut être activée depuis le coffre web sur bitwarden.com. Souhaitez-vous visiter le site maintenant ?" + "message": "L'authentification à deux facteurs rend votre compte plus sûr en vous demandant de vérifier votre connexion avec un autre dispositif tel qu'une clé de sécurité, une application d'authentification, un SMS, un appel téléphonique ou un courriel. L'authentification à deux facteurs peut être configurée sur le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" }, "editedFolder": { "message": "Dossier modifié" @@ -534,16 +534,16 @@ "message": "Nouvel URI" }, "addedItem": { - "message": "Identifiant ajouté" + "message": "Élément ajouté" }, "editedItem": { - "message": "Identifiant modifié" + "message": "Élément enregistré" }, "deleteItemConfirmation": { "message": "Êtes-vous sûr de vouloir supprimer cet identifiant ?" }, "deletedItem": { - "message": "L'élément a été envoyé dans la corbeille" + "message": "Élément envoyé à la corbeille" }, "overwritePassword": { "message": "Écraser le mot de passe" @@ -574,19 +574,19 @@ "message": "Demander à ajouter un identifiant" }, "addLoginNotificationDesc": { - "message": "La \"Notification de demande d'ajout d'identifiant\" apparait automatiquement pour vous demander d'enregistrer dans votre coffre les identifiants que vous utilisez pour la première fois." + "message": "Demander à ajouter un élément si aucun n'est trouvé dans votre coffre." }, "showCardsCurrentTab": { "message": "Afficher les cartes sur la page de l'onglet" }, "showCardsCurrentTabDesc": { - "message": "Lister les éléments de la carte sur la page de l'onglet pour faciliter le remplissage automatique." + "message": "Lister les éléments de la carte sur la page de l'onglet pour faciliter la saisie automatique." }, "showIdentitiesCurrentTab": { "message": "Afficher les identités sur la page Onglet courant" }, "showIdentitiesCurrentTabDesc": { - "message": "Lister les éléments d'identité sur la page de l'onglet pour faciliter le remplissage automatique." + "message": "Lister les éléments d'identité sur la page de l'onglet pour faciliter la saisie automatique." }, "clearClipboard": { "message": "Effacer le presse-papiers", @@ -668,7 +668,7 @@ "message": "Les clés de chiffrement du compte sont spécifiques à chaque utilisateur Bitwarden. Vous ne pouvez donc pas importer d'export chiffré dans un compte différent." }, "exportMasterPassword": { - "message": "Saisissez votre mot de passe maître pour exporter les données de votre coffre." + "message": "Saisissez votre mot de passe principal pour exporter les données de votre coffre." }, "shared": { "message": "Partagé" @@ -756,49 +756,49 @@ "message": "Gérer l'adhésion" }, "premiumManageAlert": { - "message": "Vous pouvez gérer votre adhésion depuis le coffre web sur bitwarden.com. Souhaitez-vous visiter le site web maintenant ?" + "message": "Vous pouvez gérer votre adhésion sur le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" }, "premiumRefresh": { "message": "Actualiser l'adhésion" }, "premiumNotCurrentMember": { - "message": "Vous n'êtes actuellement pas un membre premium." + "message": "Vous n'êtes pas actuellement un membre Premium." }, "premiumSignUpAndGet": { - "message": "Devenez un membre premium et obtenez :" + "message": "Inscrivez-vous pour une adhésion Premium et obtenez :" }, "ppremiumSignUpStorage": { - "message": "1 Go de stockage de fichiers chiffrés." + "message": "1 Go de stockage chiffré pour les fichiers joints." }, "ppremiumSignUpTwoStep": { - "message": "Options d'identification en deux étapes additionnelles comme YubiKey, FIDO U2F et Duo." + "message": "Options additionnelles d'identification à deux étapes telles que YubiKey, FIDO U2F et Duo." }, "ppremiumSignUpReports": { - "message": "Rapports sur l'hygiène des mots de passe, la santé des comptes et les fuites de données pour assurer la sécurité de votre coffre." + "message": "Hygiène du mot de passe, santé du compte et rapports sur les brèches de données pour assurer la sécurité de votre coffre." }, "ppremiumSignUpTotp": { - "message": "Génération d'un code de vérification TOTP (2FA) pour les identifiants de votre coffre." + "message": "Générateur de code de vérification TOTP (2FA) pour les identifiants dans votre coffre." }, "ppremiumSignUpSupport": { - "message": "Support client prioritaire." + "message": "Assistance client prioritaire." }, "ppremiumSignUpFuture": { - "message": "Toutes les futures options premium. Prochainement !" + "message": "Toutes les futures fonctionnalités Premium. Plus à venir prochainement !" }, "premiumPurchase": { "message": "Acheter Premium" }, "premiumPurchaseAlert": { - "message": "Vous pouvez opter pour une adhésion premium depuis le coffre web sur bitwarden.com. Souhaitez-vous consulter le site web maintenant ?" + "message": "Vous pouvez acheter une adhésion Premium sur le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" }, "premiumCurrentMember": { - "message": "Vous êtes un adhérent premium !" + "message": "Vous êtes un membre Premium !" }, "premiumCurrentMemberThanks": { - "message": "Merci de supporter Bitwarden." + "message": "Merci de soutenir Bitwarden." }, "premiumPrice": { - "message": "Tout pour seulement $PRICE$ /an !", + "message": "Tout pour seulement $PRICE$/an !", "placeholders": { "price": { "content": "$1", @@ -819,10 +819,10 @@ "message": "Demander la biométrie au lancement" }, "premiumRequired": { - "message": "Version Premium requise" + "message": "Premium requis" }, "premiumRequiredDesc": { - "message": "Une adhésion premium est requise pour utiliser cette fonctionnalité." + "message": "Une adhésion Premium est requise pour utiliser cette fonctionnalité." }, "enterVerificationCodeApp": { "message": "Saisissez le code de vérification à 6 chiffres depuis votre application d'authentification." @@ -873,13 +873,13 @@ "message": "Identifiant non disponible" }, "noTwoStepProviders": { - "message": "Ce compte dispose d'une authentification à double facteurs, cependant, aucun service d'authentification à double facteurs n'est supporté par ce navigateur web." + "message": "Ce compte dispose d'une authentification à deux facteurs mise en place, mais aucun des fournisseurs d'authentification à deux facteurs configurés ne sont pris en charge par ce navigateur web." }, "noTwoStepProviders2": { "message": "Merci d'utiliser un navigateur web compatible (comme Chrome) et/ou d'ajouter des services additionnels d'identification en deux étapes qui sont mieux supportés par les navigateurs web (comme par exemple une application d'authentification)." }, "twoStepOptions": { - "message": "Options d'identification à double facteurs" + "message": "Options d'authentification à feux facteurs" }, "recoveryCodeDesc": { "message": "Accès perdu à tous vos services d'authentification à double facteurs ? Utilisez votre code de récupération pour désactiver tous les services de double authentifications sur votre compte." @@ -966,7 +966,7 @@ "message": "Paramètre de saisie automatique par défaut pour les identifiants" }, "defaultAutoFillOnPageLoadDesc": { - "message": "Après avoir activé le remplissage automatique au chargement de la page, vous pourrez activer ou désactiver la fonctionnalité pour chaque identifiant. Ceci est le paramètre par défaut pour les identifiants qui ne sont pas configurés individuellement." + "message": "Vous pouvez désactiver la saisie automatique au chargement de la page pour les éléments de connexion individuels à partir de la vue Éditer l'élément." }, "itemAutoFillOnPageLoad": { "message": "Remplissage automatique au chargement de la page (si activé dans les options)" @@ -1347,10 +1347,10 @@ "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Mot de passe maître faible" + "message": "Mot de passe principal faible" }, "weakMasterPasswordDesc": { - "message": "Le mot de passe maître que vous avez choisi est faible. Vous devriez utiliser un mot de passe (ou une phrase de passe) fort(e) pour protéger correctement votre compte Bitwarden. Êtes-vous sûr de vouloir utiliser ce mot de passe maître ?" + "message": "Le mot de passe principal que vous avez choisi est faible. Vous devriez utiliser un mot de passe principal fort (ou une phrase de passe) pour protéger correctement votre compte Bitwarden. Êtes-vous sûr de vouloir utiliser ce mot de passe principal ?" }, "pin": { "message": "Code PIN", @@ -1378,13 +1378,13 @@ "message": "Veuillez confirmer l'utilisation de la biométrie dans l'application Bitwarden de bureau pour activer la biométrie dans le navigateur." }, "lockWithMasterPassOnRestart": { - "message": "Verrouiller avec le mot de passe maître lors du redémarrage du navigateur." + "message": "Verrouiller avec le mot de passe principal au redémarrage du navigateur" }, "selectOneCollection": { "message": "Vous devez sélectionner au moins une collection." }, "cloneItem": { - "message": "Cloner l’élément" + "message": "Cloner l'élément" }, "clone": { "message": "Cloner" @@ -1393,7 +1393,7 @@ "message": "Une ou plusieurs politiques d'organisation affectent les paramètres de votre générateur." }, "vaultTimeoutAction": { - "message": "Action lors de l'expiration du délai du coffre" + "message": "Action après délai d'expiration du coffre" }, "lock": { "message": "Verrouiller", @@ -1413,7 +1413,7 @@ "message": "Êtes-vous sûr de vouloir supprimer définitivement cet élément ?" }, "permanentlyDeletedItem": { - "message": "Élément supprimé définitivement" + "message": "Élément définitivement supprimé" }, "restoreItem": { "message": "Restaurer l'élément" @@ -1425,7 +1425,7 @@ "message": "Élément restauré" }, "vaultTimeoutLogOutConfirmation": { - "message": "La déconnexion supprimera tous les accès à votre coffre et nécessite une authentification en ligne après la période d'expiration. Êtes-vous sûr de vouloir utiliser ce paramètre?" + "message": "La déconnexion supprimera tout accès à votre coffre et nécessitera une authentification en ligne après la période d'expiration. Êtes-vous sûr de vouloir utiliser ce paramètre ?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Confirmation de l'action lors de l'expiration du délai" @@ -1434,16 +1434,16 @@ "message": "Remplir automatiquement et enregistrer" }, "autoFillSuccessAndSavedUri": { - "message": "Élément rempli automatiquement et URI sauvegardée" + "message": "Élément saisi automatiquement et URI sauvegardé" }, "autoFillSuccess": { - "message": "Élément rempli automatiquement" + "message": "Élément saisi automatiquement" }, "setMasterPassword": { - "message": "Définir le mot de passe maître" + "message": "Définir le mot de passe principal" }, "masterPasswordPolicyInEffect": { - "message": "Une ou plusieurs politiques de l'organisation exigent que votre mot de passe maître réponde aux exigences suivantes :" + "message": "Une ou plusieurs politiques de l'organisation exigent que votre mot de passe principal réponde aux exigences suivantes :" }, "policyInEffectMinComplexity": { "message": "Score de complexité minimum de $SCORE$", @@ -1482,7 +1482,7 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "Votre nouveau mot de passe maître ne répond pas aux exigences de la politique." + "message": "Votre nouveau mot de passe principal ne répond pas aux exigences en matière de politique de sécurité." }, "acceptPolicies": { "message": "En cochant cette case, vous acceptez les éléments suivants :" @@ -1563,7 +1563,7 @@ "message": "Cette action ne peut pas être effectuée dans la barre latérale, veuillez réessayer l'action dans la popup ou la nouvelle fenêtre." }, "personalOwnershipSubmitError": { - "message": "En raison d'une politique d'entreprise, il vous est interdit d'enregistrer des éléments dans votre coffre personnel. Sélectionnez une organisation dans l'option Propriété et choisissez parmi les collections disponibles." + "message": "En raison d'une politique d'entreprise, il vous est interdit d'enregistrer des éléments dans votre coffre personnel. Changez l'option Propriété au profit d'une organisation et choisissez parmi les collections disponibles." }, "personalOwnershipPolicyInEffect": { "message": "Une politique d'organisation affecte vos options de propriété." @@ -1724,7 +1724,7 @@ "message": "Le texte que vous voulez envoyer." }, "sendHideText": { - "message": "Cacher par défaut le texte de ce Send.", + "message": "Masquer le texte de ce Send par défaut.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { @@ -1793,19 +1793,19 @@ "message": "Une erreur s'est produite lors de l'enregistrement de vos dates de suppression et d'expiration." }, "hideEmail": { - "message": "Cacher mon adresse e-mail aux destinataires." + "message": "Masquer mon adresse électronique aux destinataires." }, "sendOptionsPolicyInEffect": { "message": "Une ou plusieurs politiques d'organisation affectent vos options Send." }, "passwordPrompt": { - "message": "Ressaisie du mot de passe maître" + "message": "Ressaisir le mot de passe principal" }, "passwordConfirmation": { - "message": "Confirmation du mot de passe maître" + "message": "Confirmation du mot de passe principal" }, "passwordConfirmationDesc": { - "message": "Cette action est protégée. Pour continuer, veuillez ressaisir votre mot de passe maître pour vérifier votre identité." + "message": "Cette action est protégée. Pour continuer, veuillez saisir à nouveau votre mot de passe principal pour vérifier votre identité." }, "emailVerificationRequired": { "message": "Vérification de l'adresse e-mail nécessaire" @@ -1814,25 +1814,25 @@ "message": "Vous devez vérifier votre adresse e-mail pour utiliser cette fonctionnalité. Vous pouvez vérifier votre adresse e-mail dans le coffre web." }, "updatedMasterPassword": { - "message": "Mot de passe maître mis à jour" + "message": "Mot de passe principal mis à jour" }, "updateMasterPassword": { - "message": "Mettre à jour le mot de passe maître" + "message": "Mettre à jour le mot de passe principal" }, "updateMasterPasswordWarning": { - "message": "Votre mot de passe maître a récemment été modifié par un administrateur de votre organisation. Pour pouvoir accéder au coffre-fort, vous devez mettre à jour votre mot de passe maître maintenant. Poursuivre vous déconnectera de votre session actuelle, vous obligeant à vous reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives jusqu'à une heure." + "message": "Votre mot de passe principal a été récemment changé par un administrateur de votre organisation. Pour pouvoir accéder au coffre, vous devez le mettre à jour maintenant. En poursuivant, vous serez déconnecté de votre session actuelle et vous devrez vous reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives pendant encore une heure." }, "resetPasswordPolicyAutoEnroll": { "message": "Inscription automatique" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Cette organisation a une politique d'entreprise qui vous inscrira automatiquement à la réinitialisation du mot de passe. L'inscription permettra aux administrateurs de l'organisation de changer votre mot de passe maître." + "message": "Cette organisation dispose d'une politique d'entreprise qui vous inscrira automatiquement à la réinitialisation du mot de passe. L'inscription permettra aux administrateurs de l'organisation de changer votre mot de passe principal." }, "selectFolder": { "message": "Sélectionnez un dossier..." }, "ssoCompleteRegistration": { - "message": "Afin de terminer la connexion avec SSO, veuillez définir un mot de passe maître pour accéder à votre coffre et le protéger." + "message": "Afin de finaliser la connexion avec SSO, veuillez définir un mot de passe principal pour accéder et protéger votre coffre." }, "hours": { "message": "Heures" @@ -1869,7 +1869,7 @@ "message": "Aucun identifiant unique trouvé." }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ utilise SSO avec un serveur de clés auto-hébergé. Un mot de passe maître n'est plus nécessaire aux membres de cette organisation pour se connecter.", + "message": "$ORGANIZATION$ utilise SSO avec un serveur de clés auto-hébergé. Un mot de passe principal n'est plus nécessaire aux membres de cette organisation pour se connecter.", "placeholders": { "organization": { "content": "$1", @@ -1881,10 +1881,10 @@ "message": "Quitter l'organisation" }, "removeMasterPassword": { - "message": "Supprimer le mot de passe maître" + "message": "Supprimer le mot de passe principal" }, "removedMasterPassword": { - "message": "Mot de passe maître supprimé." + "message": "Mot de passe principal supprimé" }, "leaveOrganizationConfirmation": { "message": "Êtes-vous sûr·e de vouloir quitter cette organisation ?" @@ -1902,7 +1902,7 @@ "message": "Export du coffre personnel" }, "exportingPersonalVaultDescription": { - "message": "Seuls les éléments du coffre personnel associé à l'adresse e-mail $EMAIL$ seront exportés. Les éléments du coffre de l'organisation ne seront pas inclus.", + "message": "Seuls les éléments individuels du coffre associés à $EMAIL$ seront exportés. Les éléments du coffre de l'organisation ne seront pas inclus.", "placeholders": { "email": { "content": "$1", @@ -1970,16 +1970,16 @@ "message": "Clé d'API" }, "ssoKeyConnectorError": { - "message": "Erreur du connecteur de clé: veuillez vérifier que le connecteur de clé est disponible et qu'il fonctionne correctement." + "message": "Erreur Key Connector : vérifiez que Key Connector est disponible et fonctionne correctement." }, "premiumSubcriptionRequired": { - "message": "Un abonnement Premium est requis" + "message": "Abonnement Premium requis" }, "organizationIsDisabled": { "message": "L'organisation est désactivée." }, "disabledOrganizationFilterError": { - "message": "Les éléments des Organisations désactivées ne sont pas accessibles. Contactez le propriétaire de votre Organisation pour obtenir de l'aide." + "message": "Les éléments des organisations suspendues ne sont pas accessibles. Contactez le propriétaire de votre organisation pour obtenir de l'aide." }, "cardBrandMir": { "message": "Mir" @@ -2030,7 +2030,7 @@ } }, "loginWithMasterPassword": { - "message": "Connectez-vous avec le mot de passe maître" + "message": "Se connecter avec le mot de passe principal" }, "loggingInAs": { "message": "Connexion en tant que" diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index b23d1e55d6b..fddd275872b 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -47,19 +47,19 @@ "message": "Ketik ulang Kata Sandi Utama" }, "masterPassHint": { - "message": "Petunjuk Kata Sandi Utama (pilihan)" + "message": "Petunjuk Kata Sandi Utama (opsional)" }, "tab": { "message": "Tab" }, "vault": { - "message": "Vault" + "message": "Brankas" }, "myVault": { "message": "Brankas Saya" }, "allVaults": { - "message": "All vaults" + "message": "Semua brankas" }, "tools": { "message": "Alat" @@ -134,7 +134,7 @@ "message": "Kirim Kode" }, "codeSent": { - "message": "Kode Terkirim!" + "message": "Kode sudah dikirim" }, "verificationCode": { "message": "Kode Verifikasi" @@ -424,13 +424,13 @@ "message": "Alamat email tidak valid." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "Kata sandi utama diperlukan." }, "confirmMasterPasswordRequired": { "message": "Master password retype is required." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "Kata sandi utama harus memiliki panjang setidaknya 8 karakter." }, "masterPassDoesntMatch": { "message": "Konfirmasi sandi utama tidak cocok." @@ -810,7 +810,7 @@ "message": "Penyegaran selesai" }, "enableAutoTotpCopy": { - "message": "Copy TOTP automatically" + "message": "Salin TOTP secara otomatis" }, "disableAutoTotpCopyDesc": { "message": "Jika info masuk Anda memiliki kunci autentikasi yang menyertainya, kode verifikasi TOTP akan disalin secara otomatis ke clipboard Anda setiap kali Anda mengisi info masuk secara otomatis." @@ -1312,7 +1312,7 @@ "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "Dibuat", "description": "ex. Date this item was created" }, "datePasswordUpdated": { @@ -1488,7 +1488,7 @@ "message": "Dengan mencentang kotak ini, Anda menyetujui yang berikut:" }, "acceptPoliciesRequired": { - "message": "Terms of Service and Privacy Policy have not been acknowledged." + "message": "Persyaratan Layanan dan Kebijakan Privasi belum disetujui." }, "termsOfService": { "message": "Persyaratan Layanan" @@ -1911,16 +1911,16 @@ } }, "error": { - "message": "Error" + "message": "Galat" }, "regenerateUsername": { - "message": "Regenerate username" + "message": "Buat nama pengguna baru" }, "generateUsername": { - "message": "Generate username" + "message": "Buat nama pengguna baru" }, "usernameType": { - "message": "Username type" + "message": "Jenis nama pengguna" }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -1936,19 +1936,19 @@ "message": "Use your domain's configured catch-all inbox." }, "random": { - "message": "Random" + "message": "Acak" }, "randomWord": { - "message": "Random word" + "message": "Kata acak" }, "websiteName": { - "message": "Website name" + "message": "Nama situs web" }, "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" + "message": "Apa yang ingin Anda buat?" }, "passwordType": { - "message": "Password type" + "message": "Jenis kata sandi" }, "service": { "message": "Service" @@ -2042,6 +2042,6 @@ "message": "New around here?" }, "rememberEmail": { - "message": "Remember email" + "message": "Ingat email" } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json new file mode 100644 index 00000000000..ba083bf1e36 --- /dev/null +++ b/apps/browser/src/_locales/ne/messages.json @@ -0,0 +1,2047 @@ +{ + "appName": { + "message": "Bitwarden" + }, + "extName": { + "message": "Bitwarden - Free Password Manager", + "description": "Extension name, MUST be less than 40 characters (Safari restriction)" + }, + "extDesc": { + "message": "A secure and free password manager for all of your devices.", + "description": "Extension description" + }, + "loginOrCreateNewAccount": { + "message": "Log in or create a new account to access your secure vault." + }, + "createAccount": { + "message": "Create account" + }, + "login": { + "message": "Log in" + }, + "enterpriseSingleSignOn": { + "message": "Enterprise single sign-on" + }, + "cancel": { + "message": "Cancel" + }, + "close": { + "message": "Close" + }, + "submit": { + "message": "Submit" + }, + "emailAddress": { + "message": "Email address" + }, + "masterPass": { + "message": "Master password" + }, + "masterPassDesc": { + "message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it." + }, + "masterPassHintDesc": { + "message": "A master password hint can help you remember your password if you forget it." + }, + "reTypeMasterPass": { + "message": "Re-type master password" + }, + "masterPassHint": { + "message": "Master password hint (optional)" + }, + "tab": { + "message": "Tab" + }, + "vault": { + "message": "Vault" + }, + "myVault": { + "message": "My vault" + }, + "allVaults": { + "message": "All vaults" + }, + "tools": { + "message": "Tools" + }, + "settings": { + "message": "Settings" + }, + "currentTab": { + "message": "Current tab" + }, + "copyPassword": { + "message": "Copy password" + }, + "copyNote": { + "message": "Copy note" + }, + "copyUri": { + "message": "Copy URI" + }, + "copyUsername": { + "message": "Copy username" + }, + "copyNumber": { + "message": "Copy number" + }, + "copySecurityCode": { + "message": "Copy security code" + }, + "autoFill": { + "message": "Auto-fill" + }, + "generatePasswordCopied": { + "message": "Generate password (copied)" + }, + "copyElementIdentifier": { + "message": "Copy custom field name" + }, + "noMatchingLogins": { + "message": "No matching logins" + }, + "unlockVaultMenu": { + "message": "Unlock your vault" + }, + "loginToVaultMenu": { + "message": "Log in to your vault" + }, + "autoFillInfo": { + "message": "There are no logins available to auto-fill for the current browser tab." + }, + "addLogin": { + "message": "Add a login" + }, + "addItem": { + "message": "Add item" + }, + "passwordHint": { + "message": "Password hint" + }, + "enterEmailToGetHint": { + "message": "Enter your account email address to receive your master password hint." + }, + "getMasterPasswordHint": { + "message": "Get master password hint" + }, + "continue": { + "message": "Continue" + }, + "sendVerificationCode": { + "message": "Send a verification code to your email" + }, + "sendCode": { + "message": "Send code" + }, + "codeSent": { + "message": "Code sent" + }, + "verificationCode": { + "message": "Verification code" + }, + "confirmIdentity": { + "message": "Confirm your identity to continue." + }, + "account": { + "message": "Account" + }, + "changeMasterPassword": { + "message": "Change master password" + }, + "fingerprintPhrase": { + "message": "Fingerprint phrase", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "yourAccountsFingerprint": { + "message": "Your account's fingerprint phrase", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "twoStepLogin": { + "message": "Two-step login" + }, + "logOut": { + "message": "Log out" + }, + "about": { + "message": "About" + }, + "version": { + "message": "Version" + }, + "save": { + "message": "Save" + }, + "move": { + "message": "Move" + }, + "addFolder": { + "message": "Add folder" + }, + "name": { + "message": "Name" + }, + "editFolder": { + "message": "Edit folder" + }, + "deleteFolder": { + "message": "Delete folder" + }, + "folders": { + "message": "Folders" + }, + "noFolders": { + "message": "There are no folders to list." + }, + "helpFeedback": { + "message": "Help & feedback" + }, + "sync": { + "message": "Sync" + }, + "syncVaultNow": { + "message": "Sync vault now" + }, + "lastSync": { + "message": "Last sync:" + }, + "passGen": { + "message": "Password generator" + }, + "generator": { + "message": "Generator", + "description": "Short for 'Password Generator'." + }, + "passGenInfo": { + "message": "Automatically generate strong, unique passwords for your logins." + }, + "bitWebVault": { + "message": "Bitwarden web vault" + }, + "importItems": { + "message": "Import items" + }, + "select": { + "message": "Select" + }, + "generatePassword": { + "message": "Generate password" + }, + "regeneratePassword": { + "message": "Regenerate password" + }, + "options": { + "message": "Options" + }, + "length": { + "message": "Length" + }, + "uppercase": { + "message": "Uppercase (A-Z)" + }, + "lowercase": { + "message": "Lowercase (a-z)" + }, + "numbers": { + "message": "Numbers (0-9)" + }, + "specialCharacters": { + "message": "Special characters (!@#$%^&*)" + }, + "numWords": { + "message": "Number of words" + }, + "wordSeparator": { + "message": "Word separator" + }, + "capitalize": { + "message": "Capitalize", + "description": "Make the first letter of a work uppercase." + }, + "includeNumber": { + "message": "Include number" + }, + "minNumbers": { + "message": "Minimum numbers" + }, + "minSpecial": { + "message": "Minimum special" + }, + "avoidAmbChar": { + "message": "Avoid ambiguous characters" + }, + "searchVault": { + "message": "Search vault" + }, + "edit": { + "message": "Edit" + }, + "view": { + "message": "View" + }, + "noItemsInList": { + "message": "There are no items to list." + }, + "itemInformation": { + "message": "Item information" + }, + "username": { + "message": "Username" + }, + "password": { + "message": "Password" + }, + "passphrase": { + "message": "Passphrase" + }, + "favorite": { + "message": "Favorite" + }, + "notes": { + "message": "Notes" + }, + "note": { + "message": "Note" + }, + "editItem": { + "message": "Edit item" + }, + "folder": { + "message": "Folder" + }, + "deleteItem": { + "message": "Delete item" + }, + "viewItem": { + "message": "View item" + }, + "launch": { + "message": "Launch" + }, + "website": { + "message": "Website" + }, + "toggleVisibility": { + "message": "Toggle visibility" + }, + "manage": { + "message": "Manage" + }, + "other": { + "message": "Other" + }, + "rateExtension": { + "message": "Rate the extension" + }, + "rateExtensionDesc": { + "message": "Please consider helping us out with a good review!" + }, + "browserNotSupportClipboard": { + "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." + }, + "verifyIdentity": { + "message": "Verify identity" + }, + "yourVaultIsLocked": { + "message": "Your vault is locked. Verify your identity to continue." + }, + "unlock": { + "message": "Unlock" + }, + "loggedInAsOn": { + "message": "Logged in as $EMAIL$ on $HOSTNAME$.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "hostname": { + "content": "$2", + "example": "bitwarden.com" + } + } + }, + "invalidMasterPassword": { + "message": "Invalid master password" + }, + "vaultTimeout": { + "message": "Vault timeout" + }, + "lockNow": { + "message": "Lock now" + }, + "immediately": { + "message": "Immediately" + }, + "tenSeconds": { + "message": "10 seconds" + }, + "twentySeconds": { + "message": "20 seconds" + }, + "thirtySeconds": { + "message": "30 seconds" + }, + "oneMinute": { + "message": "1 minute" + }, + "twoMinutes": { + "message": "2 minutes" + }, + "fiveMinutes": { + "message": "5 minutes" + }, + "fifteenMinutes": { + "message": "15 minutes" + }, + "thirtyMinutes": { + "message": "30 minutes" + }, + "oneHour": { + "message": "1 hour" + }, + "fourHours": { + "message": "4 hours" + }, + "onLocked": { + "message": "On system lock" + }, + "onRestart": { + "message": "On browser restart" + }, + "never": { + "message": "Never" + }, + "security": { + "message": "Security" + }, + "errorOccurred": { + "message": "An error has occurred" + }, + "emailRequired": { + "message": "Email address is required." + }, + "invalidEmail": { + "message": "Invalid email address." + }, + "masterPasswordRequired": { + "message": "Master password is required." + }, + "confirmMasterPasswordRequired": { + "message": "Master password retype is required." + }, + "masterPasswordMinlength": { + "message": "Master password must be at least 8 characters long." + }, + "masterPassDoesntMatch": { + "message": "Master password confirmation does not match." + }, + "newAccountCreated": { + "message": "Your new account has been created! You may now log in." + }, + "masterPassSent": { + "message": "We've sent you an email with your master password hint." + }, + "verificationCodeRequired": { + "message": "Verification code is required." + }, + "invalidVerificationCode": { + "message": "Invalid verification code" + }, + "valueCopied": { + "message": "$VALUE$ copied", + "description": "Value has been copied to the clipboard.", + "placeholders": { + "value": { + "content": "$1", + "example": "Password" + } + } + }, + "autofillError": { + "message": "Unable to auto-fill the selected item on this page. Copy and paste the information instead." + }, + "loggedOut": { + "message": "Logged out" + }, + "loginExpired": { + "message": "Your login session has expired." + }, + "logOutConfirmation": { + "message": "Are you sure you want to log out?" + }, + "yes": { + "message": "Yes" + }, + "no": { + "message": "No" + }, + "unexpectedError": { + "message": "An unexpected error has occurred." + }, + "nameRequired": { + "message": "Name is required." + }, + "addedFolder": { + "message": "Folder added" + }, + "changeMasterPass": { + "message": "Change master password" + }, + "changeMasterPasswordConfirmation": { + "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "twoStepLoginConfirmation": { + "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "editedFolder": { + "message": "Folder saved" + }, + "deleteFolderConfirmation": { + "message": "Are you sure you want to delete this folder?" + }, + "deletedFolder": { + "message": "Folder deleted" + }, + "gettingStartedTutorial": { + "message": "Getting started tutorial" + }, + "gettingStartedTutorialVideo": { + "message": "Watch our getting started tutorial to learn how to get the most out of the browser extension." + }, + "syncingComplete": { + "message": "Syncing complete" + }, + "syncingFailed": { + "message": "Syncing failed" + }, + "passwordCopied": { + "message": "Password copied" + }, + "uri": { + "message": "URI" + }, + "uriPosition": { + "message": "URI $POSITION$", + "description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.", + "placeholders": { + "position": { + "content": "$1", + "example": "2" + } + } + }, + "newUri": { + "message": "New URI" + }, + "addedItem": { + "message": "Item added" + }, + "editedItem": { + "message": "Item saved" + }, + "deleteItemConfirmation": { + "message": "Do you really want to send to the trash?" + }, + "deletedItem": { + "message": "Item sent to trash" + }, + "overwritePassword": { + "message": "Overwrite password" + }, + "overwritePasswordConfirmation": { + "message": "Are you sure you want to overwrite the current password?" + }, + "overwriteUsername": { + "message": "Overwrite username" + }, + "overwriteUsernameConfirmation": { + "message": "Are you sure you want to overwrite the current username?" + }, + "searchFolder": { + "message": "Search folder" + }, + "searchCollection": { + "message": "Search collection" + }, + "searchType": { + "message": "Search type" + }, + "noneFolder": { + "message": "No folder", + "description": "This is the folder for uncategorized items" + }, + "enableAddLoginNotification": { + "message": "Ask to add login" + }, + "addLoginNotificationDesc": { + "message": "Ask to add an item if one isn't found in your vault." + }, + "showCardsCurrentTab": { + "message": "Show cards on Tab page" + }, + "showCardsCurrentTabDesc": { + "message": "List card items on the Tab page for easy auto-fill." + }, + "showIdentitiesCurrentTab": { + "message": "Show identities on Tab page" + }, + "showIdentitiesCurrentTabDesc": { + "message": "List identity items on the Tab page for easy auto-fill." + }, + "clearClipboard": { + "message": "Clear clipboard", + "description": "Clipboard is the operating system thing where you copy/paste data to on your device." + }, + "clearClipboardDesc": { + "message": "Automatically clear copied values from your clipboard.", + "description": "Clipboard is the operating system thing where you copy/paste data to on your device." + }, + "notificationAddDesc": { + "message": "Should Bitwarden remember this password for you?" + }, + "notificationAddSave": { + "message": "Save" + }, + "enableChangedPasswordNotification": { + "message": "Ask to update existing login" + }, + "changedPasswordNotificationDesc": { + "message": "Ask to update a login's password when a change is detected on a website." + }, + "notificationChangeDesc": { + "message": "Do you want to update this password in Bitwarden?" + }, + "notificationChangeSave": { + "message": "Update" + }, + "enableContextMenuItem": { + "message": "Show context menu options" + }, + "contextMenuItemDesc": { + "message": "Use a secondary click to access password generation and matching logins for the website. " + }, + "defaultUriMatchDetection": { + "message": "Default URI match detection", + "description": "Default URI match detection for auto-fill." + }, + "defaultUriMatchDetectionDesc": { + "message": "Choose the default way that URI match detection is handled for logins when performing actions such as auto-fill." + }, + "theme": { + "message": "Theme" + }, + "themeDesc": { + "message": "Change the application's color theme." + }, + "dark": { + "message": "Dark", + "description": "Dark color" + }, + "light": { + "message": "Light", + "description": "Light color" + }, + "solarizedDark": { + "message": "Solarized dark", + "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." + }, + "exportVault": { + "message": "Export vault" + }, + "fileFormat": { + "message": "File format" + }, + "warning": { + "message": "WARNING", + "description": "WARNING (should stay in capitalized letters if the language permits)" + }, + "confirmVaultExport": { + "message": "Confirm vault export" + }, + "exportWarningDesc": { + "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + }, + "encExportKeyWarningDesc": { + "message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." + }, + "encExportAccountWarningDesc": { + "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + }, + "exportMasterPassword": { + "message": "Enter your master password to export your vault data." + }, + "shared": { + "message": "Shared" + }, + "learnOrg": { + "message": "Learn about organizations" + }, + "learnOrgConfirmation": { + "message": "Bitwarden allows you to share your vault items with others by using an organization. Would you like to visit the bitwarden.com website to learn more?" + }, + "moveToOrganization": { + "message": "Move to organization" + }, + "share": { + "message": "Share" + }, + "movedItemToOrg": { + "message": "$ITEMNAME$ moved to $ORGNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + }, + "orgname": { + "content": "$2", + "example": "Company Name" + } + } + }, + "moveToOrgDesc": { + "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + }, + "learnMore": { + "message": "Learn more" + }, + "authenticatorKeyTotp": { + "message": "Authenticator key (TOTP)" + }, + "verificationCodeTotp": { + "message": "Verification code (TOTP)" + }, + "copyVerificationCode": { + "message": "Copy verification code" + }, + "attachments": { + "message": "Attachments" + }, + "deleteAttachment": { + "message": "Delete attachment" + }, + "deleteAttachmentConfirmation": { + "message": "Are you sure you want to delete this attachment?" + }, + "deletedAttachment": { + "message": "Attachment deleted" + }, + "newAttachment": { + "message": "Add new attachment" + }, + "noAttachments": { + "message": "No attachments." + }, + "attachmentSaved": { + "message": "Attachment saved" + }, + "file": { + "message": "File" + }, + "selectFile": { + "message": "Select a file" + }, + "maxFileSize": { + "message": "Maximum file size is 500 MB." + }, + "featureUnavailable": { + "message": "Feature unavailable" + }, + "updateKey": { + "message": "You cannot use this feature until you update your encryption key." + }, + "premiumMembership": { + "message": "Premium membership" + }, + "premiumManage": { + "message": "Manage membership" + }, + "premiumManageAlert": { + "message": "You can manage your membership on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "premiumRefresh": { + "message": "Refresh membership" + }, + "premiumNotCurrentMember": { + "message": "You are not currently a Premium member." + }, + "premiumSignUpAndGet": { + "message": "Sign up for a Premium membership and get:" + }, + "ppremiumSignUpStorage": { + "message": "1 GB encrypted storage for file attachments." + }, + "ppremiumSignUpTwoStep": { + "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + }, + "ppremiumSignUpReports": { + "message": "Password hygiene, account health, and data breach reports to keep your vault safe." + }, + "ppremiumSignUpTotp": { + "message": "TOTP verification code (2FA) generator for logins in your vault." + }, + "ppremiumSignUpSupport": { + "message": "Priority customer support." + }, + "ppremiumSignUpFuture": { + "message": "All future Premium features. More coming soon!" + }, + "premiumPurchase": { + "message": "Purchase Premium" + }, + "premiumPurchaseAlert": { + "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "premiumCurrentMember": { + "message": "You are a Premium member!" + }, + "premiumCurrentMemberThanks": { + "message": "Thank you for supporting Bitwarden." + }, + "premiumPrice": { + "message": "All for just $PRICE$ /year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, + "refreshComplete": { + "message": "Refresh complete" + }, + "enableAutoTotpCopy": { + "message": "Copy TOTP automatically" + }, + "disableAutoTotpCopyDesc": { + "message": "If a login has an authenticator key, copy the TOTP verification code to your clip-board when you auto-fill the login." + }, + "enableAutoBiometricsPrompt": { + "message": "Ask for biometrics on launch" + }, + "premiumRequired": { + "message": "Premium required" + }, + "premiumRequiredDesc": { + "message": "A Premium membership is required to use this feature." + }, + "enterVerificationCodeApp": { + "message": "Enter the 6 digit verification code from your authenticator app." + }, + "enterVerificationCodeEmail": { + "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", + "placeholders": { + "email": { + "content": "$1", + "example": "example@gmail.com" + } + } + }, + "verificationCodeEmailSent": { + "message": "Verification email sent to $EMAIL$.", + "placeholders": { + "email": { + "content": "$1", + "example": "example@gmail.com" + } + } + }, + "rememberMe": { + "message": "Remember me" + }, + "sendVerificationCodeEmailAgain": { + "message": "Send verification code email again" + }, + "useAnotherTwoStepMethod": { + "message": "Use another two-step login method" + }, + "insertYubiKey": { + "message": "Insert your YubiKey into your computer's USB port, then touch its button." + }, + "insertU2f": { + "message": "Insert your security key into your computer's USB port. If it has a button, touch it." + }, + "webAuthnNewTab": { + "message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab." + }, + "webAuthnNewTabOpen": { + "message": "Open new tab" + }, + "webAuthnAuthenticate": { + "message": "Authenticate WebAuthn" + }, + "loginUnavailable": { + "message": "Login unavailable" + }, + "noTwoStepProviders": { + "message": "This account has two-step login set up, however, none of the configured two-step providers are supported by this web browser." + }, + "noTwoStepProviders2": { + "message": "Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported across web browsers (such as an authenticator app)." + }, + "twoStepOptions": { + "message": "Two-step login options" + }, + "recoveryCodeDesc": { + "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." + }, + "recoveryCodeTitle": { + "message": "Recovery code" + }, + "authenticatorAppTitle": { + "message": "Authenticator app" + }, + "authenticatorAppDesc": { + "message": "Use an authenticator app (such as Authy or Google Authenticator) to generate time-based verification codes.", + "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." + }, + "yubiKeyTitle": { + "message": "YubiKey OTP Security Key" + }, + "yubiKeyDesc": { + "message": "Use a YubiKey to access your account. Works with YubiKey 4, 4 Nano, 4C, and NEO devices." + }, + "duoDesc": { + "message": "Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.", + "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." + }, + "duoOrganizationDesc": { + "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", + "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." + }, + "webAuthnTitle": { + "message": "FIDO2 WebAuthn" + }, + "webAuthnDesc": { + "message": "Use any WebAuthn compatible security key to access your account." + }, + "emailTitle": { + "message": "Email" + }, + "emailDesc": { + "message": "Verification codes will be emailed to you." + }, + "selfHostedEnvironment": { + "message": "Self-hosted environment" + }, + "selfHostedEnvironmentFooter": { + "message": "Specify the base URL of your on-premises hosted Bitwarden installation." + }, + "customEnvironment": { + "message": "Custom environment" + }, + "customEnvironmentFooter": { + "message": "For advanced users. You can specify the base URL of each service independently." + }, + "baseUrl": { + "message": "Server URL" + }, + "apiUrl": { + "message": "API Server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, + "enableAutoFillOnPageLoad": { + "message": "Auto-fill on page load" + }, + "enableAutoFillOnPageLoadDesc": { + "message": "If a login form is detected, auto-fill when the web page loads." + }, + "experimentalFeature": { + "message": "This is currently an experimental feature. Use at your own risk." + }, + "defaultAutoFillOnPageLoad": { + "message": "Default autofill setting for login items" + }, + "defaultAutoFillOnPageLoadDesc": { + "message": "You can turn off auto-fill on page load for individual login items from the item's Edit view." + }, + "itemAutoFillOnPageLoad": { + "message": "Auto-fill on page load (if set up in Options)" + }, + "autoFillOnPageLoadUseDefault": { + "message": "Use default setting" + }, + "autoFillOnPageLoadYes": { + "message": "Auto-fill on page load" + }, + "autoFillOnPageLoadNo": { + "message": "Do not auto-fill on page load" + }, + "commandOpenPopup": { + "message": "Open vault popup" + }, + "commandOpenSidebar": { + "message": "Open vault in sidebar" + }, + "commandAutofillDesc": { + "message": "Auto-fill the last used login for the current website" + }, + "commandGeneratePasswordDesc": { + "message": "Generate and copy a new random password to the clipboard" + }, + "commandLockVaultDesc": { + "message": "Lock the vault" + }, + "privateModeWarning": { + "message": "Private mode support is experimental and some features are limited." + }, + "customFields": { + "message": "Custom fields" + }, + "copyValue": { + "message": "Copy value" + }, + "value": { + "message": "Value" + }, + "newCustomField": { + "message": "New custom field" + }, + "dragToSort": { + "message": "Drag to sort" + }, + "cfTypeText": { + "message": "Text" + }, + "cfTypeHidden": { + "message": "Hidden" + }, + "cfTypeBoolean": { + "message": "Boolean" + }, + "cfTypeLinked": { + "message": "Linked", + "description": "This describes a field that is 'linked' (tied) to another field." + }, + "linkedValue": { + "message": "Linked value", + "description": "This describes a value that is 'linked' (tied) to another value." + }, + "popup2faCloseMessage": { + "message": "Clicking outside the popup window to check your email for your verification code will cause this popup to close. Do you want to open this popup in a new window so that it does not close?" + }, + "popupU2fCloseMessage": { + "message": "This browser cannot process U2F requests in this popup window. Do you want to open this popup in a new window so that you can log in using U2F?" + }, + "enableFavicon": { + "message": "Show website icons" + }, + "faviconDesc": { + "message": "Show a recognizable image next to each login." + }, + "enableBadgeCounter": { + "message": "Show badge counter" + }, + "badgeCounterDesc": { + "message": "Indicate how many logins you have for the current web page." + }, + "cardholderName": { + "message": "Cardholder name" + }, + "number": { + "message": "Number" + }, + "brand": { + "message": "Brand" + }, + "expirationMonth": { + "message": "Expiration month" + }, + "expirationYear": { + "message": "Expiration year" + }, + "expiration": { + "message": "Expiration" + }, + "january": { + "message": "January" + }, + "february": { + "message": "February" + }, + "march": { + "message": "March" + }, + "april": { + "message": "April" + }, + "may": { + "message": "May" + }, + "june": { + "message": "June" + }, + "july": { + "message": "July" + }, + "august": { + "message": "August" + }, + "september": { + "message": "September" + }, + "october": { + "message": "October" + }, + "november": { + "message": "November" + }, + "december": { + "message": "December" + }, + "securityCode": { + "message": "Security code" + }, + "ex": { + "message": "ex." + }, + "title": { + "message": "Title" + }, + "mr": { + "message": "Mr" + }, + "mrs": { + "message": "Mrs" + }, + "ms": { + "message": "Ms" + }, + "dr": { + "message": "Dr" + }, + "firstName": { + "message": "First name" + }, + "middleName": { + "message": "Middle name" + }, + "lastName": { + "message": "Last name" + }, + "fullName": { + "message": "Full name" + }, + "identityName": { + "message": "Identity name" + }, + "company": { + "message": "Company" + }, + "ssn": { + "message": "Social Security number" + }, + "passportNumber": { + "message": "Passport number" + }, + "licenseNumber": { + "message": "License number" + }, + "email": { + "message": "Email" + }, + "phone": { + "message": "Phone" + }, + "address": { + "message": "Address" + }, + "address1": { + "message": "Address 1" + }, + "address2": { + "message": "Address 2" + }, + "address3": { + "message": "Address 3" + }, + "cityTown": { + "message": "City / Town" + }, + "stateProvince": { + "message": "State / Province" + }, + "zipPostalCode": { + "message": "Zip / Postal code" + }, + "country": { + "message": "Country" + }, + "type": { + "message": "Type" + }, + "typeLogin": { + "message": "Login" + }, + "typeLogins": { + "message": "Logins" + }, + "typeSecureNote": { + "message": "Secure note" + }, + "typeCard": { + "message": "Card" + }, + "typeIdentity": { + "message": "Identity" + }, + "passwordHistory": { + "message": "Password history" + }, + "back": { + "message": "Back" + }, + "collections": { + "message": "Collections" + }, + "favorites": { + "message": "Favorites" + }, + "popOutNewWindow": { + "message": "Pop out to a new window" + }, + "refresh": { + "message": "Refresh" + }, + "cards": { + "message": "Cards" + }, + "identities": { + "message": "Identities" + }, + "logins": { + "message": "Logins" + }, + "secureNotes": { + "message": "Secure notes" + }, + "clear": { + "message": "Clear", + "description": "To clear something out. example: To clear browser history." + }, + "checkPassword": { + "message": "Check if password has been exposed." + }, + "passwordExposed": { + "message": "This password has been exposed $VALUE$ time(s) in data breaches. You should change it.", + "placeholders": { + "value": { + "content": "$1", + "example": "2" + } + } + }, + "passwordSafe": { + "message": "This password was not found in any known data breaches. It should be safe to use." + }, + "baseDomain": { + "message": "Base domain", + "description": "Domain name. Ex. website.com" + }, + "domainName": { + "message": "Domain name", + "description": "Domain name. Ex. website.com" + }, + "host": { + "message": "Host", + "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." + }, + "exact": { + "message": "Exact" + }, + "startsWith": { + "message": "Starts with" + }, + "regEx": { + "message": "Regular expression", + "description": "A programming term, also known as 'RegEx'." + }, + "matchDetection": { + "message": "Match detection", + "description": "URI match detection for auto-fill." + }, + "defaultMatchDetection": { + "message": "Default match detection", + "description": "Default URI match detection for auto-fill." + }, + "toggleOptions": { + "message": "Toggle options" + }, + "toggleCurrentUris": { + "message": "Toggle current URIs", + "description": "Toggle the display of the URIs of the currently open tabs in the browser." + }, + "currentUri": { + "message": "Current URI", + "description": "The URI of one of the current open tabs in the browser." + }, + "organization": { + "message": "Organization", + "description": "An entity of multiple related people (ex. a team or business organization)." + }, + "types": { + "message": "Types" + }, + "allItems": { + "message": "All items" + }, + "noPasswordsInList": { + "message": "There are no passwords to list." + }, + "remove": { + "message": "Remove" + }, + "default": { + "message": "Default" + }, + "dateUpdated": { + "message": "Updated", + "description": "ex. Date this item was updated" + }, + "dateCreated": { + "message": "Created", + "description": "ex. Date this item was created" + }, + "datePasswordUpdated": { + "message": "Password updated", + "description": "ex. Date this password was updated" + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "noOrganizationsList": { + "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." + }, + "noCollectionsInList": { + "message": "There are no collections to list." + }, + "ownership": { + "message": "Ownership" + }, + "whoOwnsThisItem": { + "message": "Who owns this item?" + }, + "strong": { + "message": "Strong", + "description": "ex. A strong password. Scale: Weak -> Good -> Strong" + }, + "good": { + "message": "Good", + "description": "ex. A good password. Scale: Weak -> Good -> Strong" + }, + "weak": { + "message": "Weak", + "description": "ex. A weak password. Scale: Weak -> Good -> Strong" + }, + "weakMasterPassword": { + "message": "Weak master password" + }, + "weakMasterPasswordDesc": { + "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + }, + "pin": { + "message": "PIN", + "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." + }, + "unlockWithPin": { + "message": "Unlock with PIN" + }, + "setYourPinCode": { + "message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application." + }, + "pinRequired": { + "message": "PIN code is required." + }, + "invalidPin": { + "message": "Invalid PIN code." + }, + "unlockWithBiometrics": { + "message": "Unlock with biometrics" + }, + "awaitDesktop": { + "message": "Awaiting confirmation from desktop" + }, + "awaitDesktopDesc": { + "message": "Please confirm using biometrics in the Bitwarden desktop application to set up biometrics for browser." + }, + "lockWithMasterPassOnRestart": { + "message": "Lock with master password on browser restart" + }, + "selectOneCollection": { + "message": "You must select at least one collection." + }, + "cloneItem": { + "message": "Clone item" + }, + "clone": { + "message": "Clone" + }, + "passwordGeneratorPolicyInEffect": { + "message": "One or more organization policies are affecting your generator settings." + }, + "vaultTimeoutAction": { + "message": "Vault timeout action" + }, + "lock": { + "message": "Lock", + "description": "Verb form: to make secure or inaccesible by" + }, + "trash": { + "message": "Trash", + "description": "Noun: a special folder to hold deleted items" + }, + "searchTrash": { + "message": "Search trash" + }, + "permanentlyDeleteItem": { + "message": "Permanently delete item" + }, + "permanentlyDeleteItemConfirmation": { + "message": "Are you sure you want to permanently delete this item?" + }, + "permanentlyDeletedItem": { + "message": "Item permanently deleted" + }, + "restoreItem": { + "message": "Restore item" + }, + "restoreItemConfirmation": { + "message": "Are you sure you want to restore this item?" + }, + "restoredItem": { + "message": "Item restored" + }, + "vaultTimeoutLogOutConfirmation": { + "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" + }, + "vaultTimeoutLogOutConfirmationTitle": { + "message": "Timeout action confirmation" + }, + "autoFillAndSave": { + "message": "Auto-fill and save" + }, + "autoFillSuccessAndSavedUri": { + "message": "Item auto-filled and URI saved" + }, + "autoFillSuccess": { + "message": "Item auto-filled " + }, + "setMasterPassword": { + "message": "Set master password" + }, + "masterPasswordPolicyInEffect": { + "message": "One or more organization policies require your master password to meet the following requirements:" + }, + "policyInEffectMinComplexity": { + "message": "Minimum complexity score of $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, + "policyInEffectMinLength": { + "message": "Minimum length of $LENGTH$", + "placeholders": { + "length": { + "content": "$1", + "example": "14" + } + } + }, + "policyInEffectUppercase": { + "message": "Contain one or more uppercase characters" + }, + "policyInEffectLowercase": { + "message": "Contain one or more lowercase characters" + }, + "policyInEffectNumbers": { + "message": "Contain one or more numbers" + }, + "policyInEffectSpecial": { + "message": "Contain one or more of the following special characters $CHARS$", + "placeholders": { + "chars": { + "content": "$1", + "example": "!@#$%^&*" + } + } + }, + "masterPasswordPolicyRequirementsNotMet": { + "message": "Your new master password does not meet the policy requirements." + }, + "acceptPolicies": { + "message": "By checking this box you agree to the following:" + }, + "acceptPoliciesRequired": { + "message": "Terms of Service and Privacy Policy have not been acknowledged." + }, + "termsOfService": { + "message": "Terms of Service" + }, + "privacyPolicy": { + "message": "Privacy Policy" + }, + "hintEqualsPassword": { + "message": "Your password hint cannot be the same as your password." + }, + "ok": { + "message": "Ok" + }, + "desktopSyncVerificationTitle": { + "message": "Desktop sync verification" + }, + "desktopIntegrationVerificationText": { + "message": "Please verify that the desktop application shows this fingerprint: " + }, + "desktopIntegrationDisabledTitle": { + "message": "Browser integration is not set up" + }, + "desktopIntegrationDisabledDesc": { + "message": "Browser integration is not set up in the Bitwarden desktop application. Please set it up in the settings within the desktop application." + }, + "startDesktopTitle": { + "message": "Start the Bitwarden desktop application" + }, + "startDesktopDesc": { + "message": "The Bitwarden desktop application needs to be started before unlock with biometrics can be used." + }, + "errorEnableBiometricTitle": { + "message": "Unable to set up biometrics" + }, + "errorEnableBiometricDesc": { + "message": "Action was canceled by the desktop application" + }, + "nativeMessagingInvalidEncryptionDesc": { + "message": "Desktop application invalidated the secure communication channel. Please retry this operation" + }, + "nativeMessagingInvalidEncryptionTitle": { + "message": "Desktop communication interrupted" + }, + "nativeMessagingWrongUserDesc": { + "message": "The desktop application is logged into a different account. Please ensure both applications are logged into the same account." + }, + "nativeMessagingWrongUserTitle": { + "message": "Account missmatch" + }, + "biometricsNotEnabledTitle": { + "message": "Biometrics not set up" + }, + "biometricsNotEnabledDesc": { + "message": "Browser biometrics requires desktop biometric to be set up in the settings first." + }, + "biometricsNotSupportedTitle": { + "message": "Biometrics not supported" + }, + "biometricsNotSupportedDesc": { + "message": "Browser biometrics is not supported on this device." + }, + "nativeMessaginPermissionErrorTitle": { + "message": "Permission not provided" + }, + "nativeMessaginPermissionErrorDesc": { + "message": "Without permission to communicate with the Bitwarden Desktop Application we cannot provide biometrics in the browser extension. Please try again." + }, + "nativeMessaginPermissionSidebarTitle": { + "message": "Permission request error" + }, + "nativeMessaginPermissionSidebarDesc": { + "message": "This action cannot be done in the sidebar, please retry the action in the popup or popout." + }, + "personalOwnershipSubmitError": { + "message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available collections." + }, + "personalOwnershipPolicyInEffect": { + "message": "An organization policy is affecting your ownership options." + }, + "excludedDomains": { + "message": "Excluded domains" + }, + "excludedDomainsDesc": { + "message": "Bitwarden will not ask to save login details for these domains. You must refresh the page for changes to take effect." + }, + "excludedDomainsInvalidDomain": { + "message": "$DOMAIN$ is not a valid domain", + "placeholders": { + "domain": { + "content": "$1", + "example": "googlecom" + } + } + }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "searchSends": { + "message": "Search Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "addSend": { + "message": "Add Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeText": { + "message": "Text" + }, + "sendTypeFile": { + "message": "File" + }, + "allSends": { + "message": "All Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, + "expired": { + "message": "Expired" + }, + "pendingDeletion": { + "message": "Pending deletion" + }, + "passwordProtected": { + "message": "Password protected" + }, + "copySendLink": { + "message": "Copy Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "removePassword": { + "message": "Remove Password" + }, + "delete": { + "message": "Delete" + }, + "removedPassword": { + "message": "Password removed" + }, + "deletedSend": { + "message": "Send deleted", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendLink": { + "message": "Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "disabled": { + "message": "Disabled" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, + "deleteSend": { + "message": "Delete Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deleteSendConfirmation": { + "message": "Are you sure you want to delete this Send?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editSend": { + "message": "Edit Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeHeader": { + "message": "What type of Send is this?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendNameDesc": { + "message": "A friendly name to describe this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendFileDesc": { + "message": "The file you want to send." + }, + "deletionDate": { + "message": "Deletion date" + }, + "deletionDateDesc": { + "message": "The Send will be permanently deleted on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "expirationDate": { + "message": "Expiration date" + }, + "expirationDateDesc": { + "message": "If set, access to this Send will expire on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "oneDay": { + "message": "1 day" + }, + "days": { + "message": "$DAYS$ days", + "placeholders": { + "days": { + "content": "$1", + "example": "2" + } + } + }, + "custom": { + "message": "Custom" + }, + "maximumAccessCount": { + "message": "Maximum Access Count" + }, + "maximumAccessCountDesc": { + "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendPasswordDesc": { + "message": "Optionally require a password for users to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendNotesDesc": { + "message": "Private notes about this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendDisableDesc": { + "message": "Deactivate this Send so that no one can access it.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendShareDesc": { + "message": "Copy this Send's link to clipboard upon save.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTextDesc": { + "message": "The text you want to send." + }, + "sendHideText": { + "message": "Hide this Send's text by default.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "currentAccessCount": { + "message": "Current access count" + }, + "createSend": { + "message": "New Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "newPassword": { + "message": "New password" + }, + "sendDisabled": { + "message": "Send removed", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendDisabledWarning": { + "message": "Due to an enterprise policy, you are only able to delete an existing Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "createdSend": { + "message": "Send created", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editedSend": { + "message": "Send saved", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendLinuxChromiumFileWarning": { + "message": "In order to choose a file, open the extension in the sidebar (if possible) or pop out to a new window by clicking this banner." + }, + "sendFirefoxFileWarning": { + "message": "In order to choose a file using Firefox, open the extension in the sidebar or pop out to a new window by clicking this banner." + }, + "sendSafariFileWarning": { + "message": "In order to choose a file using Safari, pop out to a new window by clicking this banner." + }, + "sendFileCalloutHeader": { + "message": "Before you start" + }, + "sendFirefoxCustomDatePopoutMessage1": { + "message": "To use a calendar style date picker", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" + }, + "sendFirefoxCustomDatePopoutMessage2": { + "message": "click here", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" + }, + "sendFirefoxCustomDatePopoutMessage3": { + "message": "to pop out your window.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" + }, + "expirationDateIsInvalid": { + "message": "The expiration date provided is not valid." + }, + "deletionDateIsInvalid": { + "message": "The deletion date provided is not valid." + }, + "expirationDateAndTimeRequired": { + "message": "An expiration date and time are required." + }, + "deletionDateAndTimeRequired": { + "message": "A deletion date and time are required." + }, + "dateParsingError": { + "message": "There was an error saving your deletion and expiration dates." + }, + "hideEmail": { + "message": "Hide my email address from recipients." + }, + "sendOptionsPolicyInEffect": { + "message": "One or more organization policies are affecting your Send options." + }, + "passwordPrompt": { + "message": "Master password re-prompt" + }, + "passwordConfirmation": { + "message": "Master password confirmation" + }, + "passwordConfirmationDesc": { + "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + }, + "emailVerificationRequired": { + "message": "Email verification required" + }, + "emailVerificationRequiredDesc": { + "message": "You must verify your email to use this feature. You can verify your email in the web vault." + }, + "updatedMasterPassword": { + "message": "Updated master password" + }, + "updateMasterPassword": { + "message": "Update master password" + }, + "updateMasterPasswordWarning": { + "message": "Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update it now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + }, + "resetPasswordPolicyAutoEnroll": { + "message": "Automatic enrollment" + }, + "resetPasswordAutoEnrollInviteWarning": { + "message": "This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + }, + "selectFolder": { + "message": "Select folder..." + }, + "ssoCompleteRegistration": { + "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." + }, + "hours": { + "message": "Hours" + }, + "minutes": { + "message": "Minutes" + }, + "vaultTimeoutPolicyInEffect": { + "message": "Your organization policies are affecting your vault timeout. Maximum allowed Vault Timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "vaultExportDisabled": { + "message": "Vault export unavailable" + }, + "personalVaultExportPolicyInEffect": { + "message": "One or more organization policies prevents you from exporting your individual vault." + }, + "copyCustomFieldNameInvalidElement": { + "message": "Unable to identify a valid form element. Try inspecting the HTML instead." + }, + "copyCustomFieldNameNotUnique": { + "message": "No unique identifier found." + }, + "convertOrganizationEncryptionDesc": { + "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "leaveOrganization": { + "message": "Leave organization" + }, + "removeMasterPassword": { + "message": "Remove master password" + }, + "removedMasterPassword": { + "message": "Master password removed" + }, + "leaveOrganizationConfirmation": { + "message": "Are you sure you want to leave this organization?" + }, + "leftOrganization": { + "message": "You have left the organization." + }, + "toggleCharacterCount": { + "message": "Toggle character count" + }, + "sessionTimeout": { + "message": "Your session has timed out. Please go back and try logging in again." + }, + "exportingPersonalVaultTitle": { + "message": "Exporting individual vault" + }, + "exportingPersonalVaultDescription": { + "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "error": { + "message": "Error" + }, + "regenerateUsername": { + "message": "Regenerate username" + }, + "generateUsername": { + "message": "Generate username" + }, + "usernameType": { + "message": "Username type" + }, + "plusAddressedEmail": { + "message": "Plus addressed email", + "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" + }, + "plusAddressedEmailDesc": { + "message": "Use your email provider's sub-addressing capabilities." + }, + "catchallEmail": { + "message": "Catch-all email" + }, + "catchallEmailDesc": { + "message": "Use your domain's configured catch-all inbox." + }, + "random": { + "message": "Random" + }, + "randomWord": { + "message": "Random word" + }, + "websiteName": { + "message": "Website name" + }, + "whatWouldYouLikeToGenerate": { + "message": "What would you like to generate?" + }, + "passwordType": { + "message": "Password type" + }, + "service": { + "message": "Service" + }, + "forwardedEmail": { + "message": "Forwarded email alias" + }, + "forwardedEmailDesc": { + "message": "Generate an email alias with an external forwarding service." + }, + "hostname": { + "message": "Hostname", + "description": "Part of a URL." + }, + "apiAccessToken": { + "message": "API Access Token" + }, + "apiKey": { + "message": "API Key" + }, + "ssoKeyConnectorError": { + "message": "Key connector error: make sure key connector is available and working correctly." + }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, + "organizationIsDisabled": { + "message": "Organization suspended." + }, + "disabledOrganizationFilterError": { + "message": "Items in suspended Organizations cannot be accessed. Contact your Organization owner for assistance." + }, + "cardBrandMir": { + "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" + }, + "serverVersion": { + "message": "Server version" + }, + "selfHosted": { + "message": "Self-hosted" + }, + "thirdParty": { + "message": "Third-party" + }, + "thirdPartyServerMessage": { + "message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", + "placeholders": { + "servername": { + "content": "$1", + "example": "ThirdPartyServerName" + } + } + }, + "lastSeenOn": { + "message": "last seen on: $DATE$", + "placeholders": { + "date": { + "content": "$1", + "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/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index fa4aed52cda..a1525ed64b0 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -193,7 +193,7 @@ "message": "Er zijn geen mappen om weer te geven." }, "helpFeedback": { - "message": "Hulp en reacties" + "message": "Help & reacties" }, "sync": { "message": "Synchroniseren" diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 50cfa01b8b4..3b37ab27813 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -642,7 +642,7 @@ "description": "Light color" }, "solarizedDark": { - "message": "Tmavá –⁠ Solarized", + "message": "Solarized –⁠ tmavý", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportVault": { diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index aa7672f69e4..e2c7cd2d2cd 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -3,24 +3,24 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden", + "message": "Bitwarden - Trình quản lý mật khẩu miễn phí", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Một trình quản lý mật khẩu an toàn và miễn phí cho mọi thiết bị của bạn.", + "message": "Trình quản lý mật khẩu an toàn và miễn phí cho mọi thiết bị của bạn.", "description": "Extension description" }, "loginOrCreateNewAccount": { - "message": "Đăng nhập hoặc tạo tài khoản mới để truy cập kho mật khẩu của bạn." + "message": "Đăng nhập hoặc tạo tài khoản mới để truy cập kho lưu trữ của bạn." }, "createAccount": { - "message": "Tạo Tài Khoản" + "message": "Tạo tài khoản" }, "login": { - "message": "Đăng Nhập" + "message": "Đăng nhập" }, "enterpriseSingleSignOn": { - "message": "Đăng nhập theo tài khoản tổ chức" + "message": "Đăng nhập bằng tài khoản tổ chức" }, "cancel": { "message": "Hủy bỏ" @@ -32,16 +32,16 @@ "message": "Gửi" }, "emailAddress": { - "message": "Địa chỉ Email" + "message": "Địa chỉ email" }, "masterPass": { "message": "Mật khẩu chính" }, "masterPassDesc": { - "message": "Mật khẩu chủ là mật khẩu bạn sử dụng để truy cập kho mật khẩu của bạn. Nó rất quan trọng nên bạn không được quên mật khẩu chủ của mình. Không thể khôi phục lại mật khẩu chủ nếu bạn quên nó." + "message": "Mật khẩu chính là mật khẩu bạn dùng để truy cập vào kho lưu trữ của bạn. Mật khẩu này rất quan trọng và bạn đừng nên quên mật khẩu chính này. Bạn sẽ không thể khôi phục mật khẩu chính trong trường hợp bạn quên nó." }, "masterPassHintDesc": { - "message": "Một gợi ý mật khẩu có thể giúp bạn nhớ lại mật khẩu chủ của bạn nếu bạn quên nó." + "message": "Gợi ý mật khẩu chính có thể giúp bạn nhớ lại mật khẩu của mình nếu bạn quên nó." }, "reTypeMasterPass": { "message": "Nhập lại mật khẩu chính" @@ -50,16 +50,16 @@ "message": "Gợi ý mật khẩu chính (tùy chọn)" }, "tab": { - "message": "Thẻ" + "message": "Tab" }, "vault": { - "message": "Vault" + "message": "Kho lưu trữ" }, "myVault": { - "message": "Hầm của tôi" + "message": "Kho lưu trữ của tôi" }, "allVaults": { - "message": "All vaults" + "message": "Tất cả kho lưu trữ" }, "tools": { "message": "Công cụ" @@ -71,16 +71,16 @@ "message": "Thẻ hiện tại" }, "copyPassword": { - "message": "Sao chép Mật khẩu" + "message": "Sao chép mật khẩu" }, "copyNote": { - "message": "Sao chép Ghi chú" + "message": "Sao chép ghi chú" }, "copyUri": { - "message": "Sao chép URL" + "message": "Sao chép URI" }, "copyUsername": { - "message": "Sao chép tên đăng nhập" + "message": "Sao chép tên người dùng" }, "copyNumber": { "message": "Sao chép Số" @@ -101,10 +101,10 @@ "message": "Không có thông tin đăng nhập phù hợp." }, "unlockVaultMenu": { - "message": "Mở khóa bảo mật" + "message": "Mở khoá kho lưu trữ của bạn" }, "loginToVaultMenu": { - "message": "Đăng nhập tài khoản bảo mật" + "message": "Đăng nhập vào kho lưu trữ của bạn" }, "autoFillInfo": { "message": "Không có thông tin đăng nhập nào sẵn có để tự động điền vào tab hiện tại." @@ -119,10 +119,10 @@ "message": "Gợi ý mật khẩu" }, "enterEmailToGetHint": { - "message": "Nhập địa chỉ email tài khoản của bạn để nhận gợi ý mật khẩu chủ." + "message": "Nhập địa chỉ email tài khoản của bạn để nhận gợi ý mật khẩu chính." }, "getMasterPasswordHint": { - "message": "Nhận gợi ý mật khẩu chủ" + "message": "Nhận gợi ý mật khẩu chính" }, "continue": { "message": "Tiếp tục" @@ -140,7 +140,7 @@ "message": "Mã xác nhận" }, "confirmIdentity": { - "message": "Xác nhận nhận dạng của bạn để tiếp tục" + "message": "Xác nhận danh tính của bạn để tiếp tục." }, "account": { "message": "Tài khoản" @@ -157,7 +157,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." }, "twoStepLogin": { - "message": "Xác thực hai lớp" + "message": "Đăng nhập hai bước" }, "logOut": { "message": "Đăng xuất" @@ -172,13 +172,13 @@ "message": "Lưu" }, "move": { - "message": "Chuyển" + "message": "Di chuyển" }, "addFolder": { "message": "Thêm thư mục" }, "name": { - "message": "Tên mục" + "message": "Tên" }, "editFolder": { "message": "Chỉnh sửa thư mục" @@ -193,16 +193,16 @@ "message": "Không có thư mục để liệt kê." }, "helpFeedback": { - "message": "Trợ giúp & Phản hồi" + "message": "Trợ giúp & phản hồi" }, "sync": { "message": "Đồng bộ" }, "syncVaultNow": { - "message": "Đồng bộ Kho mật khẩu ngay" + "message": "Đồng bộ kho lưu trữ ngay" }, "lastSync": { - "message": "Lần đồng bộ gần nhất:" + "message": "Đồng bộ lần cuối:" }, "passGen": { "message": "Tạo mật khẩu" @@ -212,10 +212,10 @@ "description": "Short for 'Password Generator'." }, "passGenInfo": { - "message": "Tự động tạo mật khẩu mạnh mẽ, duy nhất cho đăng nhập của bạn." + "message": "Tự động tạo mật khẩu mạnh mẽ, độc nhất cho đăng nhập của bạn." }, "bitWebVault": { - "message": "Hầm bitwarden trên Web" + "message": "Trang web kho lưu trữ Bitwarden" }, "importItems": { "message": "Nhập mục" @@ -248,7 +248,7 @@ "message": "Ký tự đặc biệt (!@#$%^&*)" }, "numWords": { - "message": "Number of Words" + "message": "Số từ" }, "wordSeparator": { "message": "Word Separator" @@ -270,7 +270,7 @@ "message": "Tránh các ký tự không rõ ràng" }, "searchVault": { - "message": "Tìm kiếm trong kho" + "message": "Tìm kiếm trong kho lưu trữ" }, "edit": { "message": "Sửa" @@ -339,10 +339,10 @@ "message": "Trình duyệt web của bạn không hỗ trợ dễ dàng sao chép bộ nhớ tạm. Bạn có thể sao chép nó theo cách thủ công để thay thế." }, "verifyIdentity": { - "message": "Mã xác minh" + "message": "Xác minh danh tính" }, "yourVaultIsLocked": { - "message": "Kho của bạn đã bị khóa. Xác minh mật khẩu chính của bạn để mở." + "message": "Kho lưu trữ của bạn đã bị khóa. Hãy xác minh danh tính của bạn để mở khoá." }, "unlock": { "message": "Mở khóa" @@ -361,16 +361,16 @@ } }, "invalidMasterPassword": { - "message": "Mật khẩu chủ không hợp lệ" + "message": "Mật khẩu chính không hợp lệ" }, "vaultTimeout": { - "message": "Thời Gian Chờ Của Kho" + "message": "Thời gian chờ của kho lưu trữ" }, "lockNow": { "message": "Khóa ngay" }, "immediately": { - "message": "Tức thì" + "message": "Ngay lập tức" }, "tenSeconds": { "message": "10 giây" @@ -415,31 +415,31 @@ "message": "Bảo mật" }, "errorOccurred": { - "message": "Đã xảy ra lỗi chưa xác định" + "message": "Đã xảy ra lỗi" }, "emailRequired": { - "message": "Địa chỉ email là bắt buộc." + "message": "Yêu cầu địa chỉ email." }, "invalidEmail": { "message": "Địa chỉ email không hợp lệ." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "Yêu cầu mật khẩu chính." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "Yêu cầu nhập lại mật khẩu chính." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "Mật khẩu chính phải có ít nhất 8 kí tự." }, "masterPassDoesntMatch": { - "message": "Xác nhận mật khẩu chủ không khớp." + "message": "Xác nhận mật khẩu chính không khớp." }, "newAccountCreated": { - "message": "Tài khoản của bạn đã được tạo. Bạn có thể đăng nhập bây giờ." + "message": "Tài khoản mới của bạn đã được tạo! Bạn có thể đăng nhập từ bây giờ." }, "masterPassSent": { - "message": "Chúng tôi đã gửi cho bạn email có chứa gợi ý mật khẩu chủ của bạn." + "message": "Chúng tôi đã gửi cho bạn email có chứa gợi ý mật khẩu chính của bạn." }, "verificationCodeRequired": { "message": "Yêu cầu mã xác nhận." @@ -448,7 +448,7 @@ "message": "Mã xác minh không đúng" }, "valueCopied": { - "message": "$VALUE$ đã sao chép", + "message": "Đã sao chép $VALUE$", "description": "Value has been copied to the clipboard.", "placeholders": { "value": { @@ -458,10 +458,10 @@ } }, "autofillError": { - "message": "Tự động điền thông tin đăng nhập trong hoạt động trên trang này. Bạn có thể thực hiện thủ công bằng cách sao chép/dán thông tin đăng nhập." + "message": "Không thể tự động điền mục đã chọn trên trang này. Hãy thực hiện sao chép và dán thông tin một cách thủ công." }, "loggedOut": { - "message": "Đăng xuất" + "message": "Đã đăng xuất" }, "loginExpired": { "message": "Phiên đăng nhập của bạn đã hết hạn." @@ -482,19 +482,19 @@ "message": "Tên là bắt buộc." }, "addedFolder": { - "message": "Thêm thư mục" + "message": "Đã thêm thư mục" }, "changeMasterPass": { "message": "Thay đổi mật khẩu chính" }, "changeMasterPasswordConfirmation": { - "message": "Bạn có thể thay đổi mật khẩu chủ trong trang web Bitwarden. Bạn có muốn truy cập bitwarden.com bây giờ?" + "message": "Bạn có thể thay đổi mật khẩu chính trong trang web kho lưu trữ của Bitwarden. Bạn có muốn truy cập trang web ngay bây giờ không?" }, "twoStepLoginConfirmation": { "message": "Xác thực hai lớp giúp cho tài khoản của bạn an toàn hơn bằng cách yêu cầu bạn xác minh thông tin đăng nhập của bạn bằng một thiết bị khác như khóa bảo mật, ứng dụng xác thực, SMS, cuộc gọi điện thoại hoặc email. Bạn có thể bật xác thực hai lớp trong kho bitwarden nền web. Bạn có muốn ghé thăm trang web bây giờ?" }, "editedFolder": { - "message": "Chỉnh sửa Thư mục" + "message": "Đã lưu thư mục" }, "deleteFolderConfirmation": { "message": "Bạn có chắc chắn muốn xóa thư mục này không?" @@ -518,10 +518,10 @@ "message": "Đã sao chép mật khẩu" }, "uri": { - "message": "Đường dẫn" + "message": "URI" }, "uriPosition": { - "message": "URL $POSITION$", + "message": "URI $POSITION$", "description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.", "placeholders": { "position": { @@ -531,7 +531,7 @@ } }, "newUri": { - "message": "URL Mới" + "message": "URI mới" }, "addedItem": { "message": "Đã thêm mục" @@ -552,10 +552,10 @@ "message": "Bạn có chắc chắn muốn ghi đè mật khẩu hiện tại không?" }, "overwriteUsername": { - "message": "Overwrite username" + "message": "Ghi đè tên người dùng" }, "overwriteUsernameConfirmation": { - "message": "Are you sure you want to overwrite the current username?" + "message": "Bạn có chắc chắn muốn ghi đè tên người dùng hiện tại không?" }, "searchFolder": { "message": "Tìm kiếm thư mục" @@ -564,29 +564,29 @@ "message": "Tìm kiếm bộ sưu tập" }, "searchType": { - "message": "Tìm mục" + "message": "Tìm loại" }, "noneFolder": { "message": "Không có thư mục", "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { - "message": "Ask to add login" + "message": "Hỏi để thêm đăng nhập" }, "addLoginNotificationDesc": { "message": "'Thông báo Thêm đăng nhập' sẽ tự động nhắc bạn lưu các đăng nhập mới vào hầm an toàn của bạn bất cứ khi nào bạn đăng nhập trang web lần đầu tiên." }, "showCardsCurrentTab": { - "message": "Show cards on Tab page" + "message": "Hiển thị thẻ trên trang Tab" }, "showCardsCurrentTabDesc": { - "message": "List card items on the Tab page for easy auto-fill." + "message": "Liệt kê các mục thẻ trên trang Tab để dễ dàng tự động điền." }, "showIdentitiesCurrentTab": { - "message": "Show identities on Tab page" + "message": "Hiển thị danh tính trên trang Tab" }, "showIdentitiesCurrentTabDesc": { - "message": "List identity items on the Tab page for easy auto-fill." + "message": "Liệt kê các mục danh tính trên trang Tab để dễ dàng tự động điền." }, "clearClipboard": { "message": "Dọn dẹp khay nhớ tạm", @@ -603,7 +603,7 @@ "message": "Vâng, Lưu Ngay" }, "enableChangedPasswordNotification": { - "message": "Ask to update existing login" + "message": "Hỏi để cập nhật đăng nhập hiện có" }, "changedPasswordNotificationDesc": { "message": "Ask to update a login's password when a change is detected on a website." @@ -612,7 +612,7 @@ "message": "Bạn có muốn cập nhật mật khẩu này trên Bitwarden không?" }, "notificationChangeSave": { - "message": "Yes, Update Now" + "message": "Cập nhật" }, "enableContextMenuItem": { "message": "Show context menu options" @@ -628,7 +628,7 @@ "message": "Chọn phương thức mặc định để kiểm tra so sánh URI cho các đăng nhập khi xử lí các hành động như là tự động điền." }, "theme": { - "message": "Giao diện" + "message": "Chủ đề" }, "themeDesc": { "message": "Thay đổi màu sắc ứng dụng." @@ -646,7 +646,7 @@ "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportVault": { - "message": "Xuất Kho" + "message": "Xuất kho lưu trữ" }, "fileFormat": { "message": "File Format" @@ -656,7 +656,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "Xác nhận xuất mật khẩu" + "message": "Xác nhận xuất kho lưu trữ" }, "exportWarningDesc": { "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." @@ -668,7 +668,7 @@ "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." }, "exportMasterPassword": { - "message": "Nhập mật khẩu chủ để xuất kho dữ liệu của bạn." + "message": "Nhập mật khẩu chính để xuất kho lưu trữ của bạn." }, "shared": { "message": "Đã chia sẻ" @@ -680,13 +680,13 @@ "message": "Bitwarden allows you to share your vault items with others by using an organization. Would you like to visit the bitwarden.com website to learn more?" }, "moveToOrganization": { - "message": "Chuyển tới Tổ chức" + "message": "Di chuyển đến tổ chức" }, "share": { "message": "Chia sẻ" }, "movedItemToOrg": { - "message": "$ITEMNAME$ chuyển tới $ORGNAME$", + "message": "$ITEMNAME$ đã được di chuyển đến $ORGNAME$", "placeholders": { "itemname": { "content": "$1", @@ -738,7 +738,7 @@ "message": "Tập tin" }, "selectFile": { - "message": "Chọn 1 tập tin." + "message": "Chọn tập tin" }, "maxFileSize": { "message": "Kích thước tối đa của tệp tin là 500MB." @@ -756,7 +756,7 @@ "message": "Quản lý Thành viên" }, "premiumManageAlert": { - "message": "Bạn có thể quản lý các thành viên trong trang web Bitwarden. Bạn có muốn truy cập bitwarden.com bây giờ?" + "message": "Bạn có thể quản lí tư cách thành viên của mình trên trang web kho lưu trữ bitwarden.com. Bạn có muốn truy cập trang web ngay bây giờ không?" }, "premiumRefresh": { "message": "Làm mới thành viên" @@ -771,16 +771,16 @@ "message": "1GB bộ nhớ lưu trữ tập tin được mã hóa." }, "ppremiumSignUpTwoStep": { - "message": "Các tùy chọn xác thực hai lớp bổ sung như YubiKey, FIDO U2F và Duo." + "message": "Tuỳ chọn đăng nhập 2 bước bổ sung như YubiKey, FIDO U2F, và Duo." }, "ppremiumSignUpReports": { "message": "Thanh lọc mật khẩu, kiểm tra an toàn tài khoản và các báo cáo rò rĩ dữ liệu là để giữ cho kho của bạn an toàn." }, "ppremiumSignUpTotp": { - "message": "Mã xác nhận TOTP (2FA) để đăng nhập vào kho mật khẩu của bạn." + "message": "Trình tạo mã xác nhận TOTP (2FA) để đăng nhập vào kho lưu trữ của bạn." }, "ppremiumSignUpSupport": { - "message": "Hỗ trợ khách hàng ưu tiên." + "message": "Ưu tiên hỗ trợ khách hàng." }, "ppremiumSignUpFuture": { "message": "Tất cả các tính năng cao cấp trong tương lai. Nó sẽ sớm xuất hiện!" @@ -795,10 +795,10 @@ "message": "Bạn là một thành viên cao cấp!" }, "premiumCurrentMemberThanks": { - "message": "Cảm ơn bạn đã hỗ trợ Bitwarden." + "message": "Cảm ơn bạn vì đã hỗ trợ Bitwarden." }, "premiumPrice": { - "message": "Tất cả chỉ với $PRICE$ /năm!", + "message": "Tất cả chỉ với $PRICE$/năm!", "placeholders": { "price": { "content": "$1", @@ -810,7 +810,7 @@ "message": "Làm mới hoàn tất" }, "enableAutoTotpCopy": { - "message": "Copy TOTP automatically" + "message": "Tự động sao chép TOTP" }, "disableAutoTotpCopyDesc": { "message": "Nếu đăng nhập của bạn có một khóa xác thực gắn liền với nó, mã xác nhận TOTP sẽ được tự động sao chép vào bộ nhớ tạm của bạn bất cứ khi nào bạn tự động điền thông tin đăng nhập." @@ -828,7 +828,7 @@ "message": "Nhập mã xác nhận 6 chữ số từ ứng dụng xác thực của bạn." }, "enterVerificationCodeEmail": { - "message": "Nhập mã xác nhận 6 chữ số đã được gửi tới $EMAIL$.", + "message": "Nhập mã xác nhận 6 chữ số đã được gửi tới email", "placeholders": { "email": { "content": "$1", @@ -837,7 +837,7 @@ } }, "verificationCodeEmailSent": { - "message": "Email xác minh được gửi tới $EMAIL$.", + "message": "Email xác minh đã được gửi tới $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -852,13 +852,13 @@ "message": "Gửi lại email chứa mã xác nhận" }, "useAnotherTwoStepMethod": { - "message": "Sử dụng phương pháp xác thực hai lớp khác" + "message": "Sử dụng phương pháp đăng nhập 2 bước khác" }, "insertYubiKey": { "message": "Lắp YubiKey vào cổng USB máy tính của bạn, sau đó chạm vào nút trên nó." }, "insertU2f": { - "message": "Lắp khóa bảo mật vào cổng USB của máy tính. Nếu nó có một nút, nhấn vào nó." + "message": "Lắp khóa bảo mật vào cổng USB máy tính của bạn. Nếu nó có một nút, hãy nhấn vào nó." }, "webAuthnNewTab": { "message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab." @@ -876,7 +876,7 @@ "message": "Tài khoản này đã kích hoạt xác thực hai lớp, tuy nhiên, trình duyệt này không hỗ trợ cấu hình dịch vụ xác thực hai lớp đang sử dụng." }, "noTwoStepProviders2": { - "message": "Vui lòng sử dụng trình duyệt web được hỗ trợ (chẳng hạn như Chrome) và/hoặc thêm dịch vụ bổ sung được hỗ trợ tốt hơn trên các trình duyệt web (chẳng hạn như một ứng dụng xác thực)." + "message": "Hãy sử dụng trình duyệt web được hỗ trợ (chẳng hạn như Chrome) và/hoặc thêm dịch vụ bổ sung được hỗ trợ tốt hơn trên các trình duyệt web (chẳng hạn như một ứng dụng xác thực)." }, "twoStepOptions": { "message": "Tùy chọn xác thực hai lớp" @@ -898,10 +898,10 @@ "message": "Khóa bảo mật YubiKey OTP" }, "yubiKeyDesc": { - "message": "Sử dụng YubiKey để truy cập tài khoản của bạn. Làm việc với thiết bị YubiKey 4, 4 Nano, 4C và NEO." + "message": "Sử dụng YubiKey để truy cập tài khoản của bạn. Hoạt động với thiết bị YubiKey 4, 4 Nano, 4C và NEO." }, "duoDesc": { - "message": "Xác minh với Duo Security sử dụng ứng dụng Duo Mobile, SMS, cuộc gọi điện thoại, hoặc khoá bảo mật U2F.", + "message": "Xác minh với Duo Security bằng ứng dụng Duo Mobile, SMS, cuộc gọi điện thoại, hoặc khoá bảo mật U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -930,19 +930,19 @@ "message": "Môi trường tùy chỉnh" }, "customEnvironmentFooter": { - "message": "Đối với người dùng nâng cao. Bạn có thể chỉ định liên kết cơ bản của mỗi dịch vụ một cách độc lập." + "message": "Đối với người dùng nâng cao. Bạn có thể chỉ định URL cơ bản của mỗi dịch vụ một cách độc lập." }, "baseUrl": { - "message": "Địa chỉ máy chủ" + "message": "URL máy chủ" }, "apiUrl": { "message": "Địa chỉ API máy chủ" }, "webVaultUrl": { - "message": "Địa chỉ máy chủ kho web" + "message": "URL máy chủ của trang web kho lưu trữ" }, "identityUrl": { - "message": "Địa chỉ nhận dạng máy chủ" + "message": "URL máy chủ nhận dạng" }, "notificationsUrl": { "message": "Notifications Server URL" @@ -954,10 +954,10 @@ "message": "Địa chỉ môi trường đã được lưu." }, "enableAutoFillOnPageLoad": { - "message": "Bật Tự động điền vào Tải trang" + "message": "Tự động điền khi tải trang" }, "enableAutoFillOnPageLoadDesc": { - "message": "Nếu có một biểu mẫu đăng nhập được phát hiện, thực hiện tự động điền khi trang web tải xong." + "message": "Nếu phát hiện biểu mẫu đăng nhập, thực hiện tự động điền khi trang web tải xong." }, "experimentalFeature": { "message": "Đây là một tính năng thử nghiệm. Sử dụng nó có thể gây ra rủi ro cho bạn." @@ -966,10 +966,10 @@ "message": "Default autofill setting for login items" }, "defaultAutoFillOnPageLoadDesc": { - "message": "You can turn off auto-fill on page load for individual login items from the item's Edit view." + "message": "Bạn có thể tắt tự động điền khi tải trang cho mục đăng nhập riêng lẻ từ chế độ xem Chỉnh sửa mục." }, "itemAutoFillOnPageLoad": { - "message": "Tự động điền khi tải trang (nếu được kích hoạt trong Tùy chọn)" + "message": "Tự động điền khi tải trang (nếu được thiết lập trong Tùy chọn)" }, "autoFillOnPageLoadUseDefault": { "message": "Sử dụng thiết lập mặc định" @@ -990,10 +990,10 @@ "message": "Tự động điền thông tin đăng nhập người dùng cho trang web hiện tại." }, "commandGeneratePasswordDesc": { - "message": "Tạo và sao chép một mật khẩu ngẫu nhiên mới vào bộ nhớ tạm." + "message": "Tạo và sao chép một mật khẩu ngẫu nhiên mới vào khay nhớ tạm" }, "commandLockVaultDesc": { - "message": "Khóa bảo mật" + "message": "Khoá kho lưu trữ" }, "privateModeWarning": { "message": "Hỗ trợ cho chế độ riêng tư đang được thử nghiệm và hạn chế một số tính năng." @@ -1017,7 +1017,7 @@ "message": "Văn bản" }, "cfTypeHidden": { - "message": "Ẩn" + "message": "Đã ẩn đi" }, "cfTypeBoolean": { "message": "Đúng/Sai" @@ -1037,13 +1037,13 @@ "message": "Trình duyệt này không thể xử lý các yêu cầu U2F trong cửa sổ popup này. Bạn có muốn mở popup này trong cửa sổ mới để bạn có thể đăng nhập thông qua U2F?" }, "enableFavicon": { - "message": "Show website icons" + "message": "Hiển thị biểu tượng trang web" }, "faviconDesc": { "message": "Show a recognizable image next to each login." }, "enableBadgeCounter": { - "message": "Show badge counter" + "message": "Hiển thị biểu tượng bộ đếm" }, "badgeCounterDesc": { "message": "Indicate how many logins you have for the current web page." @@ -1178,7 +1178,7 @@ "message": "Mã bưu chính" }, "country": { - "message": "Quốc Gia" + "message": "Quốc gia" }, "type": { "message": "Loại" @@ -1190,7 +1190,7 @@ "message": "Đăng nhập" }, "typeSecureNote": { - "message": "Lưu ý an toàn" + "message": "Ghi chú bảo mật" }, "typeCard": { "message": "Thẻ" @@ -1199,13 +1199,13 @@ "message": "Danh tính" }, "passwordHistory": { - "message": "Lịch sử Mật khẩu" + "message": "Lịch sử mật khẩu" }, "back": { "message": "Quay lại" }, "collections": { - "message": "Các Bộ Sưu Tập" + "message": "Bộ sưu tập" }, "favorites": { "message": "Yêu thích" @@ -1236,7 +1236,7 @@ "message": "Kiểm tra xem mật khẩu có bị lộ không." }, "passwordExposed": { - "message": "Mật khẩu này đã bị lộ $VALUE$ trong các dữ liệu vi phạm. Bạn nên thay đổi nó.", + "message": "Mật khẩu này đã bị lộ $VALUE$ lần trong các báo cáo lộ lọt dữ liệu. Bạn nên thay đổi nó.", "placeholders": { "value": { "content": "$1", @@ -1245,18 +1245,18 @@ } }, "passwordSafe": { - "message": "Mật khẩu này không được tìm thấy trong bất kỳ dữ liệu vi phạm nào được biết đến. Nó an toàn để sử dụng." + "message": "Mật khẩu này không được tìm thấy trong bất kỳ báo cáo lộ lọt dữ liệu nào được biết đến. Bạn có thể tiếp tục sử dụng nó." }, "baseDomain": { "message": "Tên miền cơ sở", "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "Domain name", + "message": "Tên miền", "description": "Domain name. Ex. website.com" }, "host": { - "message": "Máy chủ", + "message": "Máy chủ lưu trữ", "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." }, "exact": { @@ -1293,7 +1293,7 @@ "description": "An entity of multiple related people (ex. a team or business organization)." }, "types": { - "message": "Các loại" + "message": "Loại" }, "allItems": { "message": "Tất cả các mục" @@ -1312,11 +1312,11 @@ "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "Ngày tạo", "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Password Updated", + "message": "Ngày cập nhật mật khẩu", "description": "ex. Date this password was updated" }, "neverLockWarning": { @@ -1357,7 +1357,7 @@ "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { - "message": "Mở khóa với mã PIN" + "message": "Mở khóa bằng mã PIN" }, "setYourPinCode": { "message": "Đặt mã PIN của bạn để mở khóa Bitwarden. Cài đặt mã PIN của bạn sẽ bị xóa nếu bạn hoàn toàn đăng xuất khỏi ứng dụng." @@ -1393,7 +1393,7 @@ "message": "Có một hoặc vài chính sách của tổ chức đang làm ảnh hưởng đến cài đặt tạo mật khẩu của bạn." }, "vaultTimeoutAction": { - "message": "Hành Động Khi Hết Thời Gian Chờ" + "message": "Hành động khi hết thời gian chờ của kho lưu trữ" }, "lock": { "message": "Khóa", @@ -1425,7 +1425,7 @@ "message": "Mục đã được khôi phục" }, "vaultTimeoutLogOutConfirmation": { - "message": "Đăng xuất sẽ xóa tất các truy cập vào kho của bạn và yêu cầu xác thực trực tuyến sau khi khoảng thời gian chờ hết. Bạn có chắc bạn muốn dùng cài đặt này?" + "message": "Việc đăng xuất sẽ loại bỏ tất cả truy cập vào kho lưu trữ của bạn và yêu cầu xác minh trực tuyến sau khi hết giai đoạn thời gian chờ. Bạn có chắc chắn muốn dùng cài đặt này không?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Xác nhận hành động khi hết thời gian chờ" @@ -1434,10 +1434,10 @@ "message": "Tự động điền và Lưu" }, "autoFillSuccessAndSavedUri": { - "message": "Tự động điền và Lưu URI" + "message": "Đã tự động điền mục và lưu URI" }, "autoFillSuccess": { - "message": "Tự động điền" + "message": "Đã tự động điền mục " }, "setMasterPassword": { "message": "Đặt mật khẩu chính" @@ -1575,7 +1575,7 @@ "message": "Bitwarden will not ask to save login details for these domains. You must refresh the page for changes to take effect." }, "excludedDomainsInvalidDomain": { - "message": "$DOMAIN$ is not a valid domain", + "message": "$DOMAIN$ không phải là tên miền hợp lệ", "placeholders": { "domain": { "content": "$1", @@ -1623,13 +1623,13 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { - "message": "Đã xóa mật khẩu" + "message": "Xóa mật khẩu" }, "delete": { "message": "Xóa" }, "removedPassword": { - "message": "Xóa mật khẩu" + "message": "Đã xóa mật khẩu" }, "deletedSend": { "message": "Đã xóa Send", @@ -1643,7 +1643,7 @@ "message": "Đã tắt" }, "removePasswordConfirmation": { - "message": "Bạn có chắc chắn muốn xóa mật khẩu này?" + "message": "Bạn có chắc chắn muốn xóa mật khẩu này không?" }, "deleteSend": { "message": "Xóa Send", @@ -1662,7 +1662,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNameDesc": { - "message": "Một tên gợi nhớ để mô tả về Send này.", + "message": "Một cái tên thân thiện để mô tả về Send này.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFileDesc": { @@ -1686,7 +1686,7 @@ "message": "1 ngày" }, "days": { - "message": "#DAYS# ngày", + "message": "$DAYS$ ngày", "placeholders": { "days": { "content": "$1", @@ -1717,28 +1717,28 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", + "message": "Sao chép liên kết của Send này vào khay nhớ tạm khi lưu.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { "message": "Văn bản bạn muốn gửi." }, "sendHideText": { - "message": "Hide this Send's text by default.", + "message": "Ẩn văn bản của Send này theo mặc định.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { "message": "Current access count" }, "createSend": { - "message": "New Send", + "message": "Send mới", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { "message": "Mật khẩu mới" }, "sendDisabled": { - "message": "Đã tắt gửi", + "message": "Đã loại bỏ Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -1805,7 +1805,7 @@ "message": "Xác nhận mật khẩu chính" }, "passwordConfirmationDesc": { - "message": "Hành động này được bảo vệ. Để tiếp tục, vui lòng nhập lại mật khẩu chính của bạn." + "message": "Hành động này được bảo vệ. Để tiếp tục, hãy nhập lại mật khẩu chính của bạn để xác minh danh tính." }, "emailVerificationRequired": { "message": "Yêu cầu xác nhận danh tính qua email" @@ -1814,7 +1814,7 @@ "message": "Bạn phải xác nhận email để sử dụng tính năng này. Bạn có thể xác minh email trên web." }, "updatedMasterPassword": { - "message": "Mật khẩu chính đã cập nhật" + "message": "Đã cập nhật mật khẩu chính" }, "updateMasterPassword": { "message": "Cập nhật mật khẩu chính" @@ -1829,7 +1829,7 @@ "message": "This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." }, "selectFolder": { - "message": "Chọn thư mục" + "message": "Chọn thư mục..." }, "ssoCompleteRegistration": { "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." @@ -1857,7 +1857,7 @@ "message": "Your vault timeout exceeds the restrictions set by your organization." }, "vaultExportDisabled": { - "message": "Xuất mật khẩu đăng tắt" + "message": "Xuất kho lưu trữ không có sẵn" }, "personalVaultExportPolicyInEffect": { "message": "One or more organization policies prevents you from exporting your individual vault." @@ -1890,7 +1890,7 @@ "message": "Are you sure you want to leave this organization?" }, "leftOrganization": { - "message": "You have left the organization." + "message": "Bạn đã rời khỏi tổ chức." }, "toggleCharacterCount": { "message": "Toggle character count" @@ -1911,47 +1911,47 @@ } }, "error": { - "message": "Error" + "message": "Lỗi" }, "regenerateUsername": { - "message": "Regenerate username" + "message": "Tạo lại tên người dùng" }, "generateUsername": { - "message": "Generate username" + "message": "Tạo tên người dùng" }, "usernameType": { - "message": "Username type" + "message": "Loại tên người dùng" }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "Địa chỉ email có hậu tố", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { "message": "Use your email provider's sub-addressing capabilities." }, "catchallEmail": { - "message": "Catch-all email" + "message": "Email Catch-all" }, "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, "random": { - "message": "Random" + "message": "Ngẫu nhiên" }, "randomWord": { - "message": "Random word" + "message": "Từ ngẫu nhiên" }, "websiteName": { - "message": "Website name" + "message": "Tên website" }, "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" + "message": "Bạn muốn tạo gì?" }, "passwordType": { - "message": "Password type" + "message": "Loại mật khẩu" }, "service": { - "message": "Service" + "message": "Dịch vụ" }, "forwardedEmail": { "message": "Forwarded email alias" @@ -1964,7 +1964,7 @@ "description": "Part of a URL." }, "apiAccessToken": { - "message": "API Access Token" + "message": "Mã thông báo truy cập API" }, "apiKey": { "message": "Khóa API" @@ -2003,13 +2003,13 @@ "message": "để đặt lại cài đặt đã thiết đặt từ trước" }, "serverVersion": { - "message": "Server version" + "message": "Phiên bản máy chủ" }, "selfHosted": { - "message": "Self-hosted" + "message": "Tự lưu trữ" }, "thirdParty": { - "message": "Third-party" + "message": "Bên thứ ba" }, "thirdPartyServerMessage": { "message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", @@ -2021,7 +2021,7 @@ } }, "lastSeenOn": { - "message": "last seen on: $DATE$", + "message": "nhìn thấy lần cuối: $DATE$", "placeholders": { "date": { "content": "$1", @@ -2030,18 +2030,18 @@ } }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "Đăng nhập bằng mật khẩu chính" }, "loggingInAs": { - "message": "Logging in as" + "message": "Đang đăng nhập với tên" }, "notYou": { - "message": "Not you?" + "message": "Không phải bạn sao?" }, "newAroundHere": { - "message": "New around here?" + "message": "Bạn mới tới đây sao?" }, "rememberEmail": { - "message": "Remember email" + "message": "Ghi nhớ email" } } diff --git a/apps/browser/store/locales/bg/copy.resx b/apps/browser/store/locales/bg/copy.resx index 21a01a58e49..29c468f0457 100644 --- a/apps/browser/store/locales/bg/copy.resx +++ b/apps/browser/store/locales/bg/copy.resx @@ -124,31 +124,31 @@ Сигурен и свободен управител на пароли за всички устройства - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + „Bitwarden, Inc.“ е компанията-майка на „8bit Solutions LLC“. -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +ОПРЕДЕЛЕН КАТО НАЙ-ДОБРИЯТ УПРАВИТЕЛ НА ПАРОЛИ ОТ „THE VERGE“, „U.S. NEWS & WORLD REPORT“, „CNET“ И ОЩЕ. -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. +Управлявайте, съхранявайте, защитавайте и споделяйте неограничен брой пароли на неограничен брой устройства от всяка точка на света. Битуорден предоставя решение за управление на паролите с отворен код, от което може да се възползва всеки, било то у дома, на работа или на път. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +Създавайте сигурни, уникални и случайни пароли според изискванията за сигурност на всеки уеб сайт, който посещавате. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +С Изпращанията на Битуорден можете незабавно да предавате шифрована информация под формата на файлове и обикновен текст – директно и с всекиго. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Битуорден предлага планове за екипи и големи фирми, така че служителите в компаниите да могат безопасно да споделят пароли помежду си. -Why Choose Bitwarden: +Защо да изберете Битуорден: -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Шифроване от най-висока класа +Паролите са защитени със сложен шифър „от край до край“ (AES-256 bit, salted hashtag и PBKDF2 SHA-256), така че данните Ви остават да са защитени и неприкосновени. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +Вграден генератор на пароли +Създавайте сигурни, уникални и случайни пароли според изискванията за сигурност на всеки уеб сайт, който посещавате. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Глобални преводи +Битуорден е преведен на 40 езика и този брой не спира да расте, благодарение на нашата глобална общност. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Приложения за всяка система +Защитете и споделяйте поверителните данни от своя трезор в Битуорден от всеки браузър, мобилно устройство или компютър. diff --git a/apps/browser/store/locales/fa/copy.resx b/apps/browser/store/locales/fa/copy.resx index bcb2ff69b30..23cb3f3bf05 100644 --- a/apps/browser/store/locales/fa/copy.resx +++ b/apps/browser/store/locales/fa/copy.resx @@ -121,54 +121,54 @@ Bitwarden - مدیریت کلمه عبور رایگان - یک مدیریت کننده کلمه عبور رایگان برای تمامی دستگاههایتان + یک مدیریت کننده کلمه عبور رایگان برای تمامی دستگاه‌هایتان - Bitwarden, Inc. شرکت مادر 8bit Solutions LLC است. + Bitwarden، Inc. شرکت مادر 8bit Solutions LLC است. -به عنوان بهترین مدیر رمز عبور توسط VERGE، US News & WORLD REPORT، CNET و دیگران انتخاب شد. +به عنوان بهترین مدیر کلمه عبور توسط VERGE، US News & WORLD REPORT، CNET و دیگران انتخاب شد. -گذرواژه‌هایی به تعداد نامحدود را در دستگاه‌های نامحدود از هر جایی مدیریت کنید، ذخیره کنید، ایمن کنید و به اشتراک بگذارید. Bitwarden راه حل های مدیریت رمز عبور منبع باز را به همه ارائه می دهد، چه در خانه، چه در محل کار یا در حال حرکت. +کلمه‌های عبور با تعداد نامحدود را در دستگاه‌های نامحدود از هر کجا مدیریت کنید، ذخیره کنید، ایمن کنید و به اشتراک بگذارید. Bitwarden راه حل های مدیریت رمز عبور منبع باز را به همه ارائه می دهد، چه در خانه، چه در محل کار یا در حال حرکت. -بر اساس الزامات امنیتی برای هر وب سایتی که بازدید می کنید، رمزهای عبور قوی، منحصر به فرد و تصادفی ایجاد کنید. +بر اساس الزامات امنیتی برای هر وب سایتی که بازدید می کنید، کلمه‌های عبور قوی، منحصر به فرد و تصادفی ایجاد کنید. Bitwarden Send به سرعت اطلاعات رمزگذاری شده --- فایل ها و متن ساده - را مستقیماً به هر کسی منتقل می کند. -Bitwarden برنامه‌های Teams و Enterprise را برای شرکت‌ها ارائه می‌دهد تا بتوانید به‌طور ایمن گذرواژه‌ها را با همکاران خود به اشتراک بگذارید. +Bitwarden برنامه‌های Teams و Enterprise را برای شرکت‌ها ارائه می‌دهد تا بتوانید به‌طور ایمن کلمه‌های را با همکاران خود به اشتراک بگذارید. چرا Bitwarden را انتخاب کنید: رمزگذاری در کلاس جهانی -گذرواژه‌ها با رمزگذاری پیشرفته (AES-256 بیت، هش سالت، و PBKDF2 SHA-256) محافظت می‌شوند تا داده‌های شما امن و خصوصی بمانند. +کلمه‌های عبور با رمزگذاری پیشرفته (AES-256 بیت، هشتگ سالت و PBKDF2 SHA-256) محافظت می‌شوند تا داده‌های شما امن و خصوصی بمانند. -تولید کننده رمز عبور داخلی -بر اساس الزامات امنیتی برای هر وب سایتی که بازدید می‌کنید، رمزهای عبور قوی، منحصر به فرد و تصادفی ایجاد کنید. +تولیدکننده کلمه عبور داخلی +بر اساس الزامات امنیتی برای هر وب سایتی که بازدید می کنید، کلمه‌های عبور قوی، منحصر به فرد و تصادفی ایجاد کنید. ترجمه های جهانی ترجمه Bitwarden به 40 زبان وجود دارد و به لطف جامعه جهانی ما در حال رشد است. برنامه های کاربردی چند پلتفرمی -داده های حساس را در Bitwarden Vault خود از هر مرورگر، دستگاه تلفن همراه یا سیستم عامل دسکتاپ و غیره ایمن کنید و به اشتراک بگذارید. +داده‌های حساس را در Bitwarden Vault خود از هر مرورگر، دستگاه تلفن همراه یا سیستم عامل دسکتاپ و غیره ایمن کنید و به اشتراک بگذارید. - یک مدیریت کننده کلمه عبور رایگان برای تمامی دستگاههایتان + یک مدیریت کننده کلمه عبور رایگان برای تمامی دستگاه‌هایتان - همگام سازی و دسترسی به گاوصندوق خود را از دستگاه های مختلف + همگام‌سازی و دسترسی به گاوصندوق خود از دستگاه های مختلف مدیریت تمام اطلاعات ورود و کلمه های عبورتان از یک گاوصندوق امن - پرکردن خودکار معتبر ورودی شما بصورت سریع برای هر وبسایتی که از آن بازدید میکنید + پرکردن خودکار معتبر ورودی شما به‌صورت سریع برای هر وب‌سایتی که از آن بازدید می‌کنید گاوصندوق شما نیز به راحتی از منوی راست کلیک قابل دسترسی است - به صورت خودکار کلمات عبور قوی، تصادفی و امن ایجاد کنید + به صورت خودکار کلمه‌های عبور قوی، تصادفی و امن ایجاد کنید - اطلاعات شما با استفاده از رمزگذاری AES-256 بیتی ایمن مدیریت می شود + اطلاعات شما با استفاده از رمزگذاری AES-256 بیتی ایمن مدیریت می‌شود diff --git a/apps/browser/store/locales/fr/copy.resx b/apps/browser/store/locales/fr/copy.resx index f815a003caa..0de6cd029a8 100644 --- a/apps/browser/store/locales/fr/copy.resx +++ b/apps/browser/store/locales/fr/copy.resx @@ -128,27 +128,27 @@ NOMMÉ MEILLEUR GESTIONNAIRE DE MOTS DE PASSE PAR THE VERGE, U.S. NEWS & WORLD REPORT, CNET, ET PLUS ENCORE. -Gérez, stockez, sécurisez et partagez un nombre illimité de mots de passe sur un nombre illimité d'appareils, où que vous soyez. Bitwarden fournit des solutions de gestion de mots de passe open source à tout le monde, que ce soit à la maison, au travail ou en déplacement. +Gérez, stockez, sécurisez et partagez un nombre illimité de mots de passe sur un nombre illimité d'appareils, où que vous soyez. Bitwarden fournit des solutions de gestion de mots de passe open source à tout le monde, que ce soit chez soi, au travail ou en déplacement. -Générez des mots de passe forts, uniques et aléatoires basés sur des exigences de sécurité pour chaque site web que vous fréquentez. +Générez des mots de passe robustes, uniques et aléatoires basés sur des exigences de sécurité pour chaque site web que vous fréquentez. Bitwarden Send transmet rapidement des informations chiffrées --- fichiers et texte --- directement à quiconque. -Bitwarden propose les plans Teams et Enterprise pour les entreprises afin que vous puissiez partager des mots de passe en toute sécurité avec vos collègues. +Bitwarden propose des plans Teams et Enterprise pour les sociétés afin que vous puissiez partager des mots de passe en toute sécurité avec vos collègues. Pourquoi choisir Bitwarden : Un chiffrement de classe internationale -Les mots de passe sont protégés par un chiffrement avancé de bout en bout (AES-256 bit, salted hashtag, et PBKDF2 SHA-256) afin que vos données restent sécurisées et privées. +Les mots de passe sont protégés par un cryptage avancé de bout en bout (AES-256 bit, hachage salé et PBKDF2 SHA-256) afin que vos données restent sécurisées et privées. Générateur de mots de passe intégré Générez des mots de passe forts, uniques et aléatoires en fonction des exigences de sécurité pour chaque site web que vous fréquentez. -Traductions mondiales -Les traductions de Bitwarden existent dans 40 langues et ne cessent de croître, grâce à notre communauté mondiale. +Traductions internationales +Les traductions de Bitwarden existent dans 40 langues et ne cessent de croître, grâce à notre communauté globale. -Applications multiplateformes -Sécurisez et partagez des données sensibles dans votre coffre-fort Bitwarden à partir de n'importe quel navigateur, appareil mobile ou système d'exploitation de bureau, et plus encore. +Applications multiplateformes +Sécurisez et partagez des données sensibles dans votre coffre Bitwarden à partir de n'importe quel navigateur, appareil mobile ou système d'exploitation de bureau, et plus encore. @@ -158,18 +158,18 @@ Sécurisez et partagez des données sensibles dans votre coffre-fort Bitwarden Synchroniser et accéder à votre coffre depuis plusieurs appareils - Gérer tous vos identifiants depuis un coffre sécurisé + Gérer tous vos identifiants et mots de passe depuis un coffre sécurisé - Remplissage automatique et rapide de vos identifiants sur n'importe quel site que vous visitez + Préremplir automatiquement et rapidement vos identifiants de connexion sur tous les sites web que vous visitez - Votre coffre est également facilement accessible depuis le menu contextuel + Votre coffre est également facilement accessible à partir du menu contextuel - Générer automatiquement des mots de passe forts, aléatoires et sécurisés + Générer automatiquement des mots de passe robustes, aléatoires et sécurisés - Vos informations sont gérées de manière sécurisée grâce à un chiffrement AES-256 bit + Vos informations sont gérées en toute sécurité en utilisant un chiffrement AES-256 bit diff --git a/apps/browser/store/locales/lv/copy.resx b/apps/browser/store/locales/lv/copy.resx index 203ef914839..22fb95f7c7b 100644 --- a/apps/browser/store/locales/lv/copy.resx +++ b/apps/browser/store/locales/lv/copy.resx @@ -124,31 +124,31 @@ Drošs bezmaksas paroļu pārvaldnieks visām Tavām ierīcēm - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Bitwarden, Inc. ir 8bit Solutions LLC mātesuzņēmums. -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +THE VERGE, U.S. NEWS & WORLD REPORT, CNET UN CITI ATZINA PAR LABĀKO PAROĻU PĀRVALDNIEKU. -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. +Pārvaldi, uzglabā, aizsargā un kopīgo neierobežotu skaitu paroļu neierobežotā skaitā ierīču no jebkuras vietas. Bitwarden piedāvā atvērtā koda paroļu pārvaldības risinājumus ikvienam - gan mājās, gan darbā, gan ceļā. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +Ģenerē spēcīgas, unikālas un nejaušas paroles, pamatojoties uz drošības prasībām, katrai bieži apmeklētai vietnei. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +Bitwarden Send ātri pārsūta šifrētu informāciju - failus un atklātu tekstu - tieši jebkuram. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Bitwarden piedāvā Teams un Enterprise plānus uzņēmumiem, lai tu varētu droši kopīgot paroles ar kolēģiem. -Why Choose Bitwarden: +Kāpēc izvēlēties 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. +Pasaules klases šifrēšana +Paroles tiek aizsargātas ar modernu end-to-end šifrēšanu (AES-256 bitu, sālītu šifrēšanu un PBKDF2 SHA-256), lai tavi dati paliktu droši un privāti. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +Iebūvēts paroļu ģenerators +Ģenerē spēcīgas, unikālas un nejaušas paroles, pamatojoties uz drošības prasībām katrai bieži apmeklētai vietnei. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Globālie tulkojumi +Bitwarden tulkojumi ir pieejami 40 valodās, un to skaits turpina pieaugt, pateicoties mūsu globālajai kopienai. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Starpplatformu lietojumprogrammas +Nodrošini un kopīgo sensitīvus datus savā Bitwarden Seifā no jebkuras pārlūkprogrammas, mobilās ierīces vai darbvirsmas operētājsistēmas un daudz ko citu. diff --git a/apps/browser/store/locales/ne/copy.resx b/apps/browser/store/locales/ne/copy.resx new file mode 100644 index 00000000000..191198691d4 --- /dev/null +++ b/apps/browser/store/locales/ne/copy.resx @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bitwarden – Free Password Manager + + + A secure and free password manager for all of your devices + + + Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + +NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. + +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. + +Generate strong, unique, and random passwords based on security requirements for every website you frequent. + +Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. + +Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. + +Why Choose Bitwarden: + +World-Class Encryption +Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. + +Built-in Password Generator +Generate strong, unique, and random passwords based on security requirements for every website you frequent. + +Global Translations +Bitwarden translations exist in 40 languages and are growing, thanks to our global community. + +Cross-Platform Applications +Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. + + + + A secure and free password manager for all of your devices + + + Sync and access your vault from multiple devices + + + Manage all your logins and passwords from a secure vault + + + Quickly auto-fill your login credentials into any website that you visit + + + Your vault is also conveniently accessible from the right-click menu + + + Automatically generate strong, random, and secure passwords + + + Your information is managed securely using AES-256 bit encryption + + diff --git a/apps/browser/store/locales/vi/copy.resx b/apps/browser/store/locales/vi/copy.resx index 52beeca10b7..ed03fc4b0ee 100644 --- a/apps/browser/store/locales/vi/copy.resx +++ b/apps/browser/store/locales/vi/copy.resx @@ -154,16 +154,16 @@ Bảo mật và chia sẻ dữ liệu nhạy cảm trong kho lưu trữ Bitwarde Một trình quản lý mật khẩu an toàn và miễn phí cho mọi thiết bị của bạn - Đồng bộ hóa và truy cập vào kho mật khẩu của bạn từ nhiều thiết bị + Đồng bộ hóa và truy cập vào kho lưu trữ của bạn từ nhiều thiết bị - Quản lý tất cả đăng nhập và mật khẩu của bạn từ một kho mật khẩu an toàn + Quản lý tất cả đăng nhập và mật khẩu của bạn từ một kho lưu trữ an toàn Nhanh chóng tự động điền thông tin đăng nhập của bạn vào bất kỳ trang web nào mà bạn truy cập - Dễ dàng truy cập kho mật khẩu của bạn từ menu chuột phải + Dễ dàng truy cập kho lưu trữ của bạn từ menu chuột phải Tự động tạo mật khẩu mạnh, ngẫu nhiên và an toàn From ecce1a34167204d637b80641672af7baff60e8ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Jan 2023 10:57:49 +0100 Subject: [PATCH 163/205] Autosync the updated translations (#4462) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/vi/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index e2c7cd2d2cd..c084b40ffbd 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden - Trình quản lý mật khẩu miễn phí", + "message": "Bitwarden - Free Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { From 09b46b7bdd450184bd67e7c58e602659fe75eadb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Jan 2023 11:00:38 +0100 Subject: [PATCH 164/205] Autosync the updated translations (#4459) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/bg/messages.json | 8 +- apps/desktop/src/locales/ca/messages.json | 2 +- apps/desktop/src/locales/de/messages.json | 2 +- apps/desktop/src/locales/et/messages.json | 2 +- apps/desktop/src/locales/fa/messages.json | 470 ++--- apps/desktop/src/locales/fr/messages.json | 116 +- apps/desktop/src/locales/hu/messages.json | 54 +- apps/desktop/src/locales/lv/messages.json | 14 +- apps/desktop/src/locales/ne/messages.json | 2065 +++++++++++++++++++++ apps/desktop/src/locales/tr/messages.json | 2 +- 10 files changed, 2400 insertions(+), 335 deletions(-) create mode 100644 apps/desktop/src/locales/ne/messages.json diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 59b9bf56693..1c9a4d62251 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1964,14 +1964,14 @@ "message": "Тип потребителско име" }, "plusAddressedEmail": { - "message": "Плюс адресиран имейл", + "message": "Адрес на е-поща с плюс", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { "message": "Използвайте възможностите за под-адресиране на е-поща на своя доставчик." }, "catchallEmail": { - "message": "Всички имейли" + "message": "Хващаща всичко е-поща" }, "catchallEmailDesc": { "message": "Използвайте конфигурираната входяща кутия за събиране на всичко." @@ -1998,10 +1998,10 @@ "message": "Търсене в моя трезор" }, "forwardedEmail": { - "message": "Псевдоним на препратен имейл" + "message": "Псевдоним на препратена е-поща" }, "forwardedEmailDesc": { - "message": "Генерирайте имейл псевдоним с външна услуга за препращане." + "message": "Създайте псевдоним на е-поща с външна услуга за препращане." }, "hostname": { "message": "Име на сървъра", diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 863093d5a90..3092c52deb3 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -1459,7 +1459,7 @@ "message": "Una o més polítiques d’organització afecten la configuració del generador." }, "vaultTimeoutAction": { - "message": "Acció del temps d'espera de la caixa forta" + "message": "Acció quan acabe el temps d'espera de la caixa forta" }, "vaultTimeoutActionLockDesc": { "message": "Una caixa forta bloquejada requereix que torneu a introduir la contrasenya principal per accedir-ne de nou." diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 2f234b95837..cfa2a9d16e9 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -2059,7 +2059,7 @@ "message": "Mit einem anderen Gerät anmelden" }, "toggleCharacterCount": { - "message": "Toggle character count", + "message": "Zeichenzählung ein-/ausschalten", "description": "'Character count' describes a feature that displays a number next to each character of the password." } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 6ba904dc307..48230bcfbc6 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -2059,7 +2059,7 @@ "message": "Logi sisse läbi teise seadme" }, "toggleCharacterCount": { - "message": "Toggle character count", + "message": "Loenda kirjatähtede hulka", "description": "'Character count' describes a feature that displays a number next to each character of the password." } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 3a7ff3e0e2c..4d20d8e9f41 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -21,7 +21,7 @@ "message": "کارت" }, "typeIdentity": { - "message": "مشخصات" + "message": "هویت" }, "typeSecureNote": { "message": "یادداشت امن" @@ -42,7 +42,7 @@ "message": "اشتراک گذاری شد" }, "share": { - "message": "اشتراک‌گذاری" + "message": "اشتراک گذاری" }, "moveToOrganization": { "message": "انتقال به سازمان" @@ -61,7 +61,7 @@ } }, "moveToOrgDesc": { - "message": "سازمانی را انتخاب کنید که می خواهید این مورد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت مورد را به آن سازمان منتقل می کند. پس از انتقال این مورد، دیگر مالک مستقیم آن نخواهید بود." + "message": "سازمانی را انتخاب کنید که می‌خواهید این مورد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت مورد را به آن سازمان منتقل می‌کند. پس از انتقال این مورد، دیگر مالک مستقیم آن نخواهید بود." }, "attachments": { "message": "پیوست‌ها" @@ -73,10 +73,10 @@ "message": "نام" }, "uri": { - "message": "URI" + "message": "نشانی اینترنتی" }, "uriPosition": { - "message": "URI $POSITION$", + "message": "نشانی اینترنتی $POSITION$", "description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.", "placeholders": { "position": { @@ -86,7 +86,7 @@ } }, "newUri": { - "message": "URI جدید" + "message": "نشانی اینترنتی جدید" }, "username": { "message": "نام کاربری" @@ -101,13 +101,13 @@ "message": "ویرایش مورد" }, "emailAddress": { - "message": "نشانی رایانامه" + "message": "نشانی ایمیل" }, "verificationCodeTotp": { - "message": "کد تایید (TOTP)" + "message": "کد تأیید (TOTP)" }, "website": { - "message": "وب سایت" + "message": "وب‌سایت" }, "notes": { "message": "یادداشت‌ها" @@ -119,7 +119,7 @@ "message": "راه اندازی" }, "copyValue": { - "message": "رونوشت مقدار", + "message": "کپی مقدار", "description": "Copy value to clipboard" }, "minimizeOnCopyToClipboard": { @@ -129,7 +129,7 @@ "message": "پایین کشیدن پنجره موقع کپی کردن اطلاعات یک مورد در کلیپ بورد." }, "toggleVisibility": { - "message": "نمایش" + "message": "قابلیت مشاهده را تغییر دهید" }, "toggleCollapse": { "message": "باز و بسته کردن", @@ -151,22 +151,22 @@ "message": "کد امنیتی" }, "identityName": { - "message": "نام شناسایی" + "message": "نام هویت" }, "company": { "message": "شرکت" }, "ssn": { - "message": "شماره امنیتی اجتماعی" + "message": "کد ملی" }, "passportNumber": { - "message": "شماره پاسپورت" + "message": "شماره گذرنامه" }, "licenseNumber": { - "message": "شماره مجوز" + "message": "شماره گواهی‌نامه" }, "email": { - "message": "رایانامه" + "message": "ایمیل" }, "phone": { "message": "تلفن" @@ -233,10 +233,10 @@ "message": "آقا" }, "mrs": { - "message": "بانو" + "message": "خانم" }, "ms": { - "message": "خانم" + "message": "بانو" }, "dr": { "message": "دکتر" @@ -308,7 +308,7 @@ "message": "ویرایش" }, "authenticatorKeyTotp": { - "message": "کلید تاییدکننده (TOTP)" + "message": "کلید احراز هویت (TOTP)" }, "folder": { "message": "پوشه" @@ -329,14 +329,14 @@ "message": "مخفی" }, "cfTypeBoolean": { - "message": "بولی" + "message": "منطقی" }, "cfTypeLinked": { - "message": "لینک شده", + "message": "پیوند شده", "description": "This describes a field that is 'linked' (related) to another field." }, "linkedValue": { - "message": "ارزش لینک شده", + "message": "مقدار پیوند شده", "description": "This describes a value that is 'linked' (related) to another value." }, "remove": { @@ -346,10 +346,10 @@ "message": "نام ضروری است." }, "addedItem": { - "message": "مورد افزوده شده" + "message": "مورد اضافه شد" }, "editedItem": { - "message": "مورد ویرایش شده" + "message": "مورد ذخیره شد" }, "deleteItem": { "message": "حذف مورد" @@ -361,10 +361,10 @@ "message": "حذف پیوست" }, "deleteItemConfirmation": { - "message": "آیا مطمئن هستید که می‌خواهید این مورد را حذف کنید؟" + "message": "واقعاً می‌خواهید این آیتم را به سطل زباله ارسال کنید؟" }, "deletedItem": { - "message": "مورد حذف شد" + "message": "مورد به زباله‌ها فرستاده شد" }, "overwritePasswordConfirmation": { "message": "آیا از بازنویسی بر روی پسورد فعلی مطمئن هستید؟" @@ -386,13 +386,13 @@ "message": "ويرايش پوشه" }, "regeneratePassword": { - "message": "تولید دوباره کلمه عبور" + "message": "تولید مجدد کلمه عبور" }, "copyPassword": { - "message": "رونوشت کلمه عبور" + "message": "کپی کلمه عبور" }, "copyUri": { - "message": "رونوشت URI" + "message": "کپی نشانی اینترنتی" }, "copyVerificationCodeTotp": { "message": "کپی کد تأیید (TOTP)" @@ -436,7 +436,7 @@ "description": "Minimum Special Characters" }, "ambiguous": { - "message": "از کاراکترهای مبهم اجتناب شود" + "message": "از کاراکترهای مبهم اجتناب کن" }, "searchCollection": { "message": "جستجوی مجموعه" @@ -461,25 +461,25 @@ "message": "آیا از پاک کردن این پیوست مطمئن هستید؟" }, "attachmentSaved": { - "message": "پیوست ذخیره شد." + "message": "پیوست ذخیره شد" }, "file": { "message": "پرونده" }, "selectFile": { - "message": "ﺍﻧﺘﺨﺎﺏ یک ﭘﺮﻭﻧﺪﻩ." + "message": "ﺍﻧﺘﺨﺎﺏ یک ﭘﺮﻭﻧﺪﻩ" }, "maxFileSize": { - "message": "بیشترین حجم فایل ۱۰۰ مگابایت است." + "message": "بیشترین حجم پرونده ۵۰۰ مگابایت است." }, "updateKey": { - "message": "تا زمانی که کد رمزنگاری را بروز نکنید نمی‌توانید از این قابلیت استفاده کنید." + "message": "تا زمانی که کد رمزنگاری را به‌روز نکنید نمی‌توانید از این قابلیت استفاده کنید." }, "editedFolder": { - "message": "پوشه ویرایش شده" + "message": "پوشه ذخیره شد" }, "addedFolder": { - "message": "پوشه اضافه شده" + "message": "پوشه اضافه شد" }, "deleteFolderConfirmation": { "message": "آیا از حذف این پوشه اطمینان دارید؟" @@ -503,40 +503,40 @@ "message": "کلمه عبور اصلی" }, "masterPassDesc": { - "message": "کلمه عبور اصلی کلمه عبوری است که شما برای دسترسی به گاوصندوق خود استفاده می‌کنید. بیاد داشتن کلمه عبور اصلی بسیار اهمیت دارد. اگر آن را فراموش کنید هیچ راهی برای بازگردانی آن وجود ندارد." + "message": "کلمه عبور اصلی، کلمه عبوری است که شما برای دسترسی به گاوصندوق خود استفاده می‌کنید. به یاد داشتن کلمه عبور اصلی بسیار اهمیت دارد. اگر فراموشش کنید هیچ راهی برای بازگردانی آن وجود ندارد." }, "masterPassHintDesc": { - "message": "راهنمای کلمه عبور اصلی می تواند کمک کند تا در صورت فراموشی آن را بیاد بیاورید." + "message": "یادآور کلمه عبور اصلی کمک می‌کند در صورت فراموشی آن را به یاد بیارید." }, "reTypeMasterPass": { "message": "نوشتن دوباره کلمه عبور اصلی" }, "masterPassHint": { - "message": "راهنمای کلمه عبور اصلی (اختیاری)" + "message": "یادآور کلمه عبور اصلی (اختیاری)" }, "settings": { "message": "تنظیمات" }, "passwordHint": { - "message": "راهنمای کلمه عبور" + "message": "یادآور کلمه عبور" }, "enterEmailToGetHint": { - "message": "برای دریافت راهنمای کلمه عبور اصلی خود آدرس رایانامه‌تان را وارد کنید." + "message": "برای دریافت یادآور کلمه عبور اصلی خود نشانی ایمیل‌تان را وارد کنید." }, "getMasterPasswordHint": { - "message": "دریافت راهنمای کلمه عبور اصلی" + "message": "دریافت یادآور کلمه عبور اصلی" }, "emailRequired": { - "message": "نشانی رایانامه ضروری است." + "message": "نشانی ایمیل ضروری است." }, "invalidEmail": { - "message": "نشانی رایانامه نامعتبر است." + "message": "نشانی ایمیل نامعتبر است." }, "masterPasswordRequired": { - "message": "گذرواژه اصلی ضروری است." + "message": "کلمه عبور اصلی ضروری است." }, "confirmMasterPasswordRequired": { - "message": "تایپ مجدد گذرواژه اصلی نیاز است." + "message": "نوشتن مجدد کلمه عبور اصلی ضروری است." }, "masterPasswordMinlength": { "message": "طول کلمه عبور اصلی باید حداقل ۸ کاراکتر باشد." @@ -545,10 +545,10 @@ "message": "کلمه عبور اصلی با تکرار آن مطابقت ندارد." }, "newAccountCreated": { - "message": "حساب جدید شما ساخته شد! حالا می‌توانید وارد شوید." + "message": "حساب کاربری جدید شما ساخته شد! حالا می‌توانید وارد شوید." }, "masterPassSent": { - "message": "ما یک رایانامه همراه با راهنمای کلمه عبور اصلی برایتان ارسال کردیم." + "message": "ما یک ایمیل همراه با یادآور کلمه عبور اصلی برایتان ارسال کردیم." }, "unexpectedError": { "message": "یک خطای غیر منتظره رخ داده است." @@ -560,7 +560,7 @@ "message": "هیچ موردی برای نمایش وجود ندارد." }, "sendVerificationCode": { - "message": "ارسال یک کد تأیید به ایمیل شما" + "message": "یک کد تأیید به ایمیل خود ارسال کنید" }, "sendCode": { "message": "ارسال کد" @@ -569,25 +569,25 @@ "message": "کد ارسال شد" }, "verificationCode": { - "message": "کد تایید" + "message": "کد تأیید" }, "confirmIdentity": { - "message": "برای ادامه هویت خود را تأیید کنید." + "message": "برای ادامه، هویت خود را تأیید کنید." }, "verificationCodeRequired": { - "message": "کد تایید مورد نیاز است." + "message": "کد تأیید مورد نیاز است." }, "invalidVerificationCode": { - "message": "کد تایید نامعتبر" + "message": "کد تأیید نامعتبر است" }, "continue": { "message": "ادامه" }, "enterVerificationCodeApp": { - "message": "کد تایید ۶ رقمی را از برنامه تایید کننده‌تان وارد کنید." + "message": "کد ۶ رقمی تأیید را از برنامه احراز هویت وارد کنید." }, "enterVerificationCodeEmail": { - "message": "کد تایید ۶ رقمی که به $EMAIL$ ارسال شد را وارد کنید.", + "message": "کد ۶ رقمی تأیید را که به $EMAIL$ ایمیل شده را وارد کنید.", "placeholders": { "email": { "content": "$1", @@ -596,7 +596,7 @@ } }, "verificationCodeEmailSent": { - "message": "رایانامه تایید به $EMAIL$ ارسال شد.", + "message": "ایمیل تأیید به $EMAIL$ ارسال شد.", "placeholders": { "email": { "content": "$1", @@ -608,7 +608,7 @@ "message": "مرا به خاطر بسپار" }, "sendVerificationCodeEmailAgain": { - "message": "ارسال دوباره رایانامه کد تایید" + "message": "ارسال دوباره ایمیل کد تأیید" }, "useAnotherTwoStepMethod": { "message": "استفاده از روش ورود دو مرحله‌ای دیگر" @@ -626,24 +626,24 @@ "message": "کد بازیابی" }, "authenticatorAppTitle": { - "message": "برنامه تاییدکننده" + "message": "برنامه احراز هویت" }, "authenticatorAppDesc": { - "message": "از یک برنامه تاییدکننده (همانند Authy یا Google Authenticator) استفاده کنید تا کدهای تایید بر پایه زمان تولید کنید.", + "message": "از یک برنامه احراز هویت (مانند Authy یا Google Authenticator) استفاده کنید تا کدهای تأیید بر پایه زمان تولید کنید.", "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." }, "yubiKeyTitle": { "message": "کلید امنیتی YubiKey OTP" }, "yubiKeyDesc": { - "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. همراه با دستگاه‌های YubiKey 4،4 Nano، NEO کار میکند." + "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. همراه با دستگاه‌های YubiKey 4 ،4 Nano ،NEO کار می‌کند." }, "duoDesc": { - "message": "با Duo Security با استفاده از برنامه تلفن همراه، پیامک، تماس تلفنی، یا کلید امنیتی U2F تایید کنید.", + "message": "با Duo Security با استفاده از برنامه تلفن همراه، پیامک، تماس تلفنی، یا کلید امنیتی U2F تأیید کنید.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "از Duo Security با استفاده از برنامه تلفن همراه، پیامک، تماس تلفنی یا کلید امنیتی U2F برای تایید سازمان خود استفاده کنید.", + "message": "از Duo Security با استفاده از برنامه تلفن همراه، پیامک، تماس تلفنی یا کلید امنیتی U2F برای تأیید سازمان خود استفاده کنید.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "webAuthnTitle": { @@ -653,19 +653,19 @@ "message": "برای دسترسی به حساب خود از هر کلید امنیتی فعال شده WebAuthn استفاده کنید." }, "emailTitle": { - "message": "رایانامه" + "message": "ایمیل" }, "emailDesc": { - "message": "کد تایید برایتان ارسال می‌شود." + "message": "کد تأیید برایتان ارسال می‌شود." }, "loginUnavailable": { - "message": "ورود به سیستم موجود نیست" + "message": "ورود به سیستم در دسترس نیست" }, "noTwoStepProviders": { - "message": "ورود دو مرحله‌ای برای این حساب فعال است، با این حال، هیچ یک از ارائه‌دهندگان دو مرحله‌ای پیکربندی شده توسط این دستگاه پشتیبانی‌نمی شوند." + "message": "ورود دو مرحله‌ای برای این حساب فعال است، با این حال، هیچ یک از ارائه‌دهندگان دو مرحله‌ای پیکربندی شده توسط این دستگاه پشتیبانی نمی‌شوند." }, "noTwoStepProviders2": { - "message": "لطفا ارائه‌دهندگان دیگری را که بهتر در سایر دستگاه‌ها پشتیبانی می‌شوند اضافه کنید (همانند یک برنامه تاییدکننده)." + "message": "لطفاً ارائه‌دهندگان دیگری را که بهتر در سایر دستگاه‌ها پشتیبانی می‌شوند اضافه کنید (مانند یک برنامه احراز هویت)." }, "twoStepOptions": { "message": "گزینه‌های ورود دو مرحله‌ای" @@ -674,22 +674,22 @@ "message": "محیط خود میزبان" }, "selfHostedEnvironmentFooter": { - "message": "آدرس اینترنتی پایه نصب Bitwarden میزبانی شده را مشخص کنید." + "message": "نشانی اینترنتی پایه فرضی نصب Bitwarden میزبانی شده را مشخص کنید." }, "customEnvironment": { "message": "محیط سفارشی" }, "customEnvironmentFooter": { - "message": "برای کاربران پیشرفته. شما می‌توانید آدرس پایه هر سرویس را به صورت مستقل تعیین کنید." + "message": "برای کاربران پیشرفته. شما می‌توانید نشانی پایه هر سرویس را مستقلاً تعیین کنید." }, "baseUrl": { - "message": "نشانی سرور" + "message": "نشانی اینترنتی سرور" }, "apiUrl": { - "message": "نشانی API سرور" + "message": "نشانی سرور API" }, "webVaultUrl": { - "message": "نشانی سرور گاوصندوق وب" + "message": "نشانی اینترنتی سرور گاوصندوق وب" }, "identityUrl": { "message": "نشانی سرور شناسایی" @@ -698,13 +698,13 @@ "message": "نشانی سرور اعلان‌ها" }, "iconsUrl": { - "message": "نشانی سرور آیکون‌ها" + "message": "آدرس سرور آیکون ها" }, "environmentSaved": { - "message": "نشانی‌های اینترنتی محیط ذخیره شدند." + "message": "نشانی‌های اینترنتی محیط ذخیره شد" }, "ok": { - "message": "تایید" + "message": "تأیید" }, "yes": { "message": "بله" @@ -734,13 +734,13 @@ "message": "خروج" }, "addNewLogin": { - "message": "افزودن ورود جدید" + "message": "ورود جدید" }, "addNewItem": { - "message": "افزودن مورد جدید" + "message": "مورد جدید" }, "addNewFolder": { - "message": "افزودن پوشه جدید" + "message": "پوشه جدید" }, "view": { "message": "مشاهده" @@ -773,7 +773,7 @@ "message": "ﻣﺎ ﺭﺍ ﺩﻧﺒﺎﻝ ﮐﻨﻴﺪ" }, "syncVault": { - "message": "همگام سازی گاوصندوق" + "message": "همگام‌سازی گاوصندوق" }, "changeMasterPass": { "message": "تغییر کلمه عبور اصلی" @@ -793,19 +793,19 @@ "message": "برو به گاوصندق وب" }, "getMobileApp": { - "message": "دریافت برنامه تلفن همراه" + "message": "دریافت برنامه‌ی تلفن همراه" }, "getBrowserExtension": { "message": "دریافت افزونه مرورگر" }, "syncingComplete": { - "message": "همگام سازی کامل شد" + "message": "همگام‌سازی کامل شد" }, "syncingFailed": { - "message": "همگام سازی ناموفق بود" + "message": "همگام‌سازی شکست خورد" }, "yourVaultIsLocked": { - "message": "گاوصندوق شما فقل شد. برای ادامه کلمه عبور اصلی را وارد کنید." + "message": "گاوصندوق شما قفل شده است. برای ادامه هویت خود را تأیید کنید." }, "unlock": { "message": "باز کردن قفل" @@ -827,16 +827,16 @@ "message": "کلمه عبور اصلی نامعتبر است" }, "twoStepLoginConfirmation": { - "message": "ورودی دو مرحله‌ای باعث می‌شود که حساب کاربری شما با استفاده از یک دستگاه دیگر مانند کلید امنیتی، برنامه تایید هویت، پیامک، تماس تلفنی و یا رایانامه، اعتبار خود را با ایمنی بیشتر ثابت کند. ورود دو مرحله‌ای می‌تواند در bitwarden.com فعال شود. آیا می‌خواهید از سایت بازدید کنید؟" + "message": "ورود دو مرحله ای باعث می‌شود که حساب کاربری شما با استفاده از یک دستگاه دیگر مانند کلید امنیتی، برنامه احراز هویت، پیامک، تماس تلفنی و یا ایمیل، اعتبار خود را با ایمنی بیشتر اثبات کند. ورود دو مرحله ای می تواند در bitwarden.com فعال شود. آیا می‌خواهید از سایت بازدید کنید؟" }, "twoStepLogin": { - "message": "ورود دو مرحله‌ای" + "message": "ورود دو مرحله ای" }, "vaultTimeout": { "message": "متوقف شدن گاو‌صندوق" }, "vaultTimeoutDesc": { - "message": "انتخاب کنید که گاو‌صندوق شما چه موقع متوقف شود و عملکرد انتخاب شده را انجام دهد." + "message": "انتخاب کنید که گاو‌صندوق شما چه زمانی عمل توقف زمانی گاوصندوق را انجام دهد." }, "immediately": { "message": "بلافاصله" @@ -872,16 +872,16 @@ "message": "۴ ساعت" }, "onIdle": { - "message": "هنگام بیکاری سیستم" + "message": "در سیستم بیکار" }, "onSleep": { - "message": "هنگام خواب سیستم" + "message": "در خواب سیستم" }, "onLocked": { - "message": "هنگام قفل سیستم" + "message": "در قفل سیستم" }, "onRestart": { - "message": "هنگام راه اندازی مجدد" + "message": "در راه اندازی مجدد" }, "never": { "message": "هرگز" @@ -890,54 +890,54 @@ "message": "امنیت" }, "clearClipboard": { - "message": "پاک‌سازی کلیپ‌برد", + "message": "پاکسازی کلیپ بورد", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { - "message": "به صورت خودکار، مقادیر رونوشت شده را از کلیپ‌برد پاک کن.", + "message": "به صورت خودکار، مقادیر کپی شده را از کلیپ بورد پاک کن.", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "enableFavicon": { - "message": "نمایش نمادهای وب سایت" + "message": "نمایش نمادهای وب‌سایت" }, "faviconDesc": { "message": "یک تصویر قابل تشخیص در کنار هر ورود نشان دهید." }, "enableMinToTray": { - "message": "کوچک کردن به نماد Tray" + "message": "کوچک کردن به نماد سینی" }, "enableMinToTrayDesc": { - "message": "هنگام کوچک‌کردن پنچره، یک نماد در قسمت نمادهای سیستم بجای آن نشان بده." + "message": "هنگام کوچک کردن پنچره، یک نماد در قسمت سینی سیستم به‌جای آن نشان بده." }, "enableMinToMenuBar": { - "message": "به نوار منو کوچک کنید" + "message": "به نوار منو کوچک کن" }, "enableMinToMenuBarDesc": { - "message": "هنگام کوچک کردن پنجره، به جای آن یک نماد در نوار منو نشان بده." + "message": "هنگام کوچک کردن پنجره، به‌جای آن یک نماد در نوار منو نشان بده." }, "enableCloseToTray": { - "message": "بستن به نماد Tray" + "message": "بستن به نماد سینی" }, "enableCloseToTrayDesc": { - "message": "هنگام بستن پنچره، یک نماد در قسمت نمادهای سیستم بجای آن نشان بده." + "message": "هنگام بستن پنچره، یک آیکون در قسمت tray به‌جای آن نشان بده." }, "enableCloseToMenuBar": { "message": "بستن به نوار منو" }, "enableCloseToMenuBarDesc": { - "message": "هنگام بستن پنجره، به جای آن یک نماد در نوار منو نشان دهید." + "message": "هنگام بستن پنجره، به جای آن یک نماد در نوار منو نشان بده." }, "enableTray": { - "message": "فعال کردن نماد Tray" + "message": "فعال کردن نماد سینی" }, "enableTrayDesc": { - "message": "همیشه یک نماد در قسمت نمادهای سیستم نشان بده." + "message": "همیشه یک نماد در قسمت سینی سیستم نشان بده." }, "startToTray": { - "message": "در زمان شروع، به نماد Tray برو" + "message": "در زمان شروع، به نماد سینی برو" }, "startToTrayDesc": { - "message": "زمانی که برنامه برای بار اول شروع می‌شود، فقط یک نماد در نمادهای سیستم نشان بده." + "message": "زمانی که برنامه برای بار اول شروع می‌شود، فقط یک نماد در سینی سیستم نشان بده." }, "startToMenuBar": { "message": "شروع به نوار منو" @@ -946,10 +946,10 @@ "message": "زمانی که برنامه برای بار اول شروع می‌شود، فقط یک نماد در نوار منو نشان بده." }, "openAtLogin": { - "message": "هنگام ورود به سیستم به طور خودکار شروع کنید" + "message": "هنگام ورود به سیستم به طور خودکار شروع کن" }, "openAtLoginDesc": { - "message": "برنامه دستکتاپ Bitwarden را به طور خودکار هنگام ورود به سیستم شروع کنید." + "message": "برنامه دسکتاپ Bitwarden را به طور خودکار هنگام ورود به سیستم شروع کن." }, "alwaysShowDock": { "message": "همیشه در داک نشان بده" @@ -958,10 +958,10 @@ "message": "نماد Bitwarden را در داک نمایش بده حتی زمانی که به نوار منو کوچک شود." }, "confirmTrayTitle": { - "message": "غیرفعال کردن tray را تأیید کنید" + "message": "غیرفعال کردن سینی را تأیید کنید" }, "confirmTrayDesc": { - "message": "غیرفعال کردن این تنظیم تمام تنظیمات مربوط به tray را غیرفعال می کند." + "message": "غیرفعال کردن این تنظیم تمام تنظیمات مربوط به سینی را غیرفعال می‌کند." }, "language": { "message": "زبان" @@ -984,11 +984,11 @@ "description": "Light color" }, "copy": { - "message": "رونوشت", + "message": "کپی", "description": "Copy to clipboard" }, "checkForUpdates": { - "message": "بررسی برای بروزرسانی‌ها" + "message": "بررسی برای به‌روزرسانی‌ها…" }, "version": { "message": "نسخه $VERSION_NUM$", @@ -1000,10 +1000,10 @@ } }, "restartToUpdate": { - "message": "برای بروزرسانی راه اندازی مجدد کنید" + "message": "برای به‌روزرسانی، مجدداً راه اندازی کن" }, "restartToUpdateDesc": { - "message": "نسخه $VERSION_NUM$ آماده نصب است. برای تکمیل نصب باید Bitwarden را مجددا راه اندازی کنید. آیا تمایل به راه اندازی مجدد و بروزرسانی دارید؟", + "message": "نسخه $VERSION_NUM$ آماده نصب است. برای تکمیل نصب باید Bitwarden را مجددا راه اندازی کنید. آیا تمایل به راه اندازی مجدد و به‌روزرسانی دارید؟", "placeholders": { "version_num": { "content": "$1", @@ -1012,54 +1012,54 @@ } }, "updateAvailable": { - "message": "بروزرسانی موجود است" + "message": "به‌روزرسانی در دسترس است" }, "updateAvailableDesc": { - "message": "یک بروزرسانی یافت شد. مایل به دانلود و نصب آن هستید؟" + "message": "یک به‌روزرسانی یافت شد. مایل به دانلود و نصب آن هستید؟" }, "restart": { "message": "راه اندازی مجدد" }, "later": { - "message": "بعدا" + "message": "بعداً" }, "noUpdatesAvailable": { - "message": "در حال حاظر هیچ بروزرسانی در دسترس نمی‌باشد. شما در در حال استفاده از آخرین نسخه هستید." + "message": "در حال حاضر هیچ به‌روزرسانی در دسترس نمی‌باشد. شما در حال استفاده از آخرین نسخه هستید." }, "updateError": { - "message": "خطا در بروزرسانی" + "message": "خطا در به‌روز رسانی" }, "unknown": { "message": "ناشناخته" }, "copyUsername": { - "message": "رونوشت نام کاربری" + "message": "کپی نام کاربری" }, "copyNumber": { - "message": "رونوشت شماره", + "message": "کپی شماره", "description": "Copy credit card number" }, "copySecurityCode": { - "message": "رونوشت کد امنیتی", + "message": "کپی کد امنیتی", "description": "Copy credit card security code (CVV)" }, "premiumMembership": { - "message": "عضویت پریمیوم" + "message": "عضویت پرمیوم" }, "premiumManage": { "message": "مدیریت عضویت" }, "premiumManageAlert": { - "message": "شما می‌توانید عضویت خود را در نسخه وب گاوصندوق در bitwarden.com مدیریت کنید. آیا مایل به دیدن وبسایت هستید؟" + "message": "شما می‌توانید عضویت خود را در نسخه وب گاوصندوق در bitwarden.com مدیریت کنید. آیا مایل به دیدن وب‌سایت هستید؟" }, "premiumRefresh": { "message": "نوسازی عضویت" }, "premiumNotCurrentMember": { - "message": "شما در حال حاظر کاربر پریمیوم نیستید." + "message": "شما در حال حاظر کاربر پرمیوم نیستید." }, "premiumSignUpAndGet": { - "message": "ثبت نام برای عضویت پرمیوم و گرفتن:" + "message": "برای عضویت پرمیوم ثبت نام کنید و دریافت کنید:" }, "premiumSignUpStorage": { "message": "۱ گیگابایت فضای ذخیره‌سازی رمزنگاری شده برای پرونده‌های پیوست." @@ -1068,25 +1068,25 @@ "message": "گزینه‌های ورود دو مرحله‌ای اضافی مانند YubiKey, FIDO U2F و Duo." }, "premiumSignUpReports": { - "message": "دانش کلمه عبور، سلامت حساب کاربری و گزارش‌های نقص اطلاعات، برای حفظ امنیت گاوصندوق شما." + "message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." }, "premiumSignUpTotp": { - "message": "تولید کننده کد تایید (2FA) از نوع TOTP برای ورودهای موجود در گاوصندوقتان." + "message": "تولید کننده کد تأیید (2FA) از نوع TOTP برای ورودهای موجود در گاوصندوقتان." }, "premiumSignUpSupport": { - "message": "پشتیبانی از مشتری با اولویت." + "message": "اولویت پشتیبانی از مشتری." }, "premiumSignUpFuture": { - "message": "تمام ویژگی‌های پریمیوم آینده. به زودی بیشتر!" + "message": "تمام ویژگی‌های پرمیوم آینده. به زودی بیشتر!" }, "premiumPurchase": { - "message": "خرید پریمیوم" + "message": "خرید پرمیوم" }, "premiumPurchaseAlert": { - "message": "شما می‌توانید عضویت پریمیوم را از گاوصندوق وب bitwarden.com خریداری کنید. آیا مایلید اکنون از وبسایت بازید کنید؟" + "message": "شما می‌توانید عضویت پرمیوم را از گاوصندوق وب bitwarden.com خریداری کنید. مایلید اکنون از وب‌سایت بازید کنید؟" }, "premiumCurrentMember": { - "message": "شما یک عضو پریمیوم هستید!" + "message": "شما یک عضو پرمیوم هستید!" }, "premiumCurrentMemberThanks": { "message": "برای حمایتتان از Bitwarden سپاسگزاریم." @@ -1111,7 +1111,7 @@ "description": "To clear something out. example: To clear browser history." }, "noPasswordsInList": { - "message": "هیچ کلمه عبوری در لیست وجود ندارد." + "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, "undo": { "message": "بازگرداندن" @@ -1163,7 +1163,7 @@ "message": "درباره Bitwarden" }, "services": { - "message": "خدمات" + "message": "سرویس‌ها" }, "hideBitwarden": { "message": "پنهان‌سازی Bitwarden" @@ -1178,7 +1178,7 @@ "message": "خروج از Bitwarden" }, "valueCopied": { - "message": "$VALUE$ رونوشت شد", + "message": "$VALUE$ کپی شد", "description": "Value has been copied to the clipboard.", "placeholders": { "value": { @@ -1194,7 +1194,7 @@ "message": "پنجره" }, "checkPassword": { - "message": "بررسی اینکه که آیا کلمه عبور افشا شده است." + "message": "بررسی کنید که آیا کلمه عبور افشا شده است." }, "passwordExposed": { "message": "این کلمه عبور $VALUE$ بار در رخنه داده‌ها افشا شده است. باید آن را تغییر دهید.", @@ -1206,7 +1206,7 @@ } }, "passwordSafe": { - "message": "این کلمه عبور در هیچ رخنه داده‌ای شناخته نشده است. باید برای استفاده امن باشد." + "message": "این کلمه عبور در هیچ رخنه داده ای شناخته نشده است. استفاده از آن باید ایمن باشد." }, "baseDomain": { "message": "دامنه پایه", @@ -1231,11 +1231,11 @@ "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "تشخیص تطابق", + "message": "تشخیص مطابقت", "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { - "message": "تشخیص تطابق پیشفرض", + "message": "بررسی مطابقت پیش‌فرض", "description": "Default URI match detection for auto-fill." }, "toggleOptions": { @@ -1246,7 +1246,7 @@ "description": "An entity of multiple related people (ex. a team or business organization)." }, "default": { - "message": "پیش فرض" + "message": "پیش‌فرض" }, "exit": { "message": "خروج" @@ -1256,36 +1256,36 @@ "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": "پنهان‌سازی در قسمت نمادهای سیستم" + "message": "پنهان‌سازی در سینی" }, "alwaysOnTop": { "message": "همشه در بالا", "description": "Application window should always stay on top of other windows" }, "dateUpdated": { - "message": "بروزرسانی شد", + "message": "به‌روزرسانی شد", "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "ایجاد شده", + "message": "ایجاد شد", "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "کلمه عبور بروزرسانی شد", + "message": "کلمه عبور به‌روزرسانی شد", "description": "ex. Date this password was updated" }, "exportVault": { - "message": "خروجی گرفتن از گاوصندوق" + "message": "برون ریزی گاوصندوق" }, "fileFormat": { "message": "فرمت پرونده" }, "hCaptchaUrl": { - "message": "آدرس اینترنتی hCaptcha", + "message": "نشانی اینترنتی hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" }, "loadAccessibilityCookie": { - "message": "بارگیری کوکی دسترس‌پذیری" + "message": "بارگیری کوکی دسترسی" }, "registerAccessibilityUser": { "message": "ثبت نام به عنوان کاربر قابلیت دسترسی در", @@ -1295,21 +1295,21 @@ "message": "لینک ارسال شده به ایمیل خود را در زیر کپی و پیست کنید" }, "enterhCaptchaUrl": { - "message": "برای بارگیری کوکی دسترسی برای hCaptcha، URL را وارد کنید", + "message": "برای بارگیری کوکی دسترسی کپچا نشانی اینترنتی را وارد کنید", "description": "hCaptcha is the name of a website, should not be translated" }, "hCaptchaUrlRequired": { - "message": "آدرس اینترنتی hCaptcha مورد نیاز است", + "message": "نشانی اینترنتی کپچا مورد نیاز است", "description": "hCaptcha is the name of a website, should not be translated" }, "invalidUrl": { - "message": "آدرس اینترنتی نامعتبر" + "message": "نشانی اینترنتی نامعتبر" }, "done": { - "message": "انجام شده" + "message": "انجام شد" }, "accessibilityCookieSaved": { - "message": "کوکی دسترس‌پذیری ذخیره شد!" + "message": "کوکی دسترسی ذخیره شد!" }, "noAccessibilityCookieSaved": { "message": "هیچ کوکی دسترسی ذخیره نشد" @@ -1319,22 +1319,22 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "صادرات گاوصندوق را تأیید کنید" + "message": "برون ریزی گاوصندوق را تأیید کنید" }, "exportWarningDesc": { - "message": "این خروجی شامل داده‌های گاوصندوق در یک قالب رمزنگاری نشده است. شما نباید آن را از طریق یک راه ارتباطی ناامن (مثل ایمیل) ذخیره یا ارسال کنید. به محض اینکه استفاده‌تان از آن تمام شد، آن را حذف کنید." + "message": "این برون ریزی شامل داده‌های گاوصندوق در یک قالب رمزنگاری نشده است. شما نباید آن را از طریق یک راه ارتباطی نا امن (مثل ایمیل) ذخیره یا ارسال کنید. به محض اینکه کارتان با آن تمام شد، آن را حذف کنید." }, "encExportKeyWarningDesc": { - "message": "این صادرات با استفاده از کلید رمزگذاری حساب شما ، اطلاعات شما را رمزگذاری می کند. اگر حتی کلید رمزگذاری حساب خود را بچرخانید ، باید دوباره صادر کنید چون قادر به رمزگشایی این پرونده صادراتی نخواهید بود." + "message": "این برون ریزی با استفاده از کلید رمزگذاری حساب شما، اطلاعاتتان را رمزگذاری می کند. اگر زمانی کلید رمزگذاری حساب خود را بچرخانید، باید دوباره خروجی بگیرید، چون قادر به رمزگشایی این پرونده برون ریزی نخواهید بود." }, "encExportAccountWarningDesc": { - "message": "کلیدهای رمزگذاری حساب برای هر حساب کاربری Bitwarden منحصر به فرد است ، بنابراین نمی توانید صادرات رمزگذاری شده را به حساب دیگری وارد کنید." + "message": "کلیدهای رمزگذاری حساب برای هر حساب کاربری Bitwarden منحصر به فرد است، بنابراین نمی‌توانید برون ریزی رمزگذاری شده را به حساب دیگری وارد کنید." }, "noOrganizationsList": { - "message": "شما به هیچ سازمانی تعلق ندارید. سازمان‌ها به شما اجازه می‌دهند تا داده‌های خود را را با کاربران دیگر به صورت امن به اشتراک بگذارید." + "message": "شما به هیچ سازمانی تعلق ندارید. سازمان‌ها به شما اجازه می‌دهند تا داده‌های خود را با کاربران دیگر به صورت امن به اشتراک بگذارید." }, "noCollectionsInList": { - "message": "هیچ مجموعه‌ای برای نمایش وجود ندارد." + "message": "هیچ مجموعه ای برای لیست کردن وجود ندارد." }, "ownership": { "message": "مالکیت" @@ -1365,10 +1365,10 @@ "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { - "message": "بازکردن با پین" + "message": "باز کردن با پین" }, "setYourPinCode": { - "message": "کد پین خود را برای باز کردن Bitwarden تنظیم کنید. اگر به طور کامل از برنامه خارج شوید (Log out)، تنظیمات پین شما از بین می‌رود." + "message": "کد پین خود را برای باز کردن Bitwarden تنظیم کنید. اگر به طور کامل از برنامه خارج شوید، تنظیمات پین شما از بین می‌رود." }, "pinRequired": { "message": "کد پین الزامیست." @@ -1380,19 +1380,19 @@ "message": "باز کردن با Windows Hello" }, "windowsHelloConsentMessage": { - "message": "تایید برای Bitwarden." + "message": "تأیید برای Bitwarden." }, "unlockWithTouchId": { - "message": "باز کردن با اثر انگشت" + "message": "باز کردن با Touch ID" }, "touchIdConsentMessage": { - "message": "تایید برای Bitwarden." + "message": "قفل گاوصندوق خود را باز کنید" }, "autoPromptWindowsHello": { "message": "درخواست Windows Hello در هنگام راه اندازی" }, "autoPromptTouchId": { - "message": "درخواست تاچ آیدی در هنگام راه اندازی" + "message": "درخواست Touch ID در هنگام راه اندازی" }, "lockWithMasterPassOnRestart": { "message": "در زمان شروع مجدد، با کلمه عبور اصلی قفل کن" @@ -1404,7 +1404,7 @@ "message": "برای حذف حساب کاربری خود و تمام داده‌های گاوصندوق، به زیر ادامه دهید." }, "deleteAccountWarning": { - "message": "حذف حساب شما دائمی است. نمی توان آن را برگرداند." + "message": "حذف حساب شما دائمی است. نمی‌توان آن را برگرداند." }, "accountDeleted": { "message": "حساب حذف شد" @@ -1447,25 +1447,25 @@ "message": "سیاست حفظ حریم خصوصی" }, "unsavedChangesConfirmation": { - "message": "آیا مطمئن هستید که می‌خواهید خارج شوید؟ اگر الان خارج شوید اطلاعات فعلی ذخیره نخواهند شد" + "message": "آیا مطمئن هستید که می‌خواهید خارج شوید؟ اگر الان خارج شوید اطلاعات فعلی ذخیره نخواهند شد." }, "unsavedChangesTitle": { - "message": "تغییرات ذخیره نشده وجود دارند" + "message": "تغییرات ذخیره نشده" }, "clone": { "message": "شبیه سازی" }, "passwordGeneratorPolicyInEffect": { - "message": "یک یا چند خط مشی سازمان بر تنظیمات تولیدکننده شما تأثیر می گذارد." + "message": "یک یا چند سیاست سازمان بر تنظیمات تولید کننده شما تأثیر می‌گذارد." }, "vaultTimeoutAction": { "message": "عمل متوقف شدن گاو‌صندوق" }, "vaultTimeoutActionLockDesc": { - "message": "یک گاوصندوق قفل شده درخواست وارد کردن مجدد کلمه عبور اصلی را برای دسترسی میدهد." + "message": "یک گاوصندوق قفل شده درخواست وارد کردن مجدد کلمه عبور اصلی را برای دسترسی می‌دهد." }, "vaultTimeoutActionLogOutDesc": { - "message": "یک گاوصندوق خارج شده درخواست احراز هویت مجدد را برای دسترسی آن میدهد." + "message": "یک گاوصندوق خارج شده درخواست احراز هویت مجدد را برای دسترسی آن می‌دهد." }, "lock": { "message": "قفل", @@ -1482,7 +1482,7 @@ "message": "حذف دائمی مورد" }, "permanentlyDeleteItemConfirmation": { - "message": "آیا مطمئن هستید که می خواهید این مورد را برای همیشه حذف کنید؟" + "message": "مطمئن هستید که می‌خواهید این مورد را برای همیشه پاک کنید؟" }, "permanentlyDeletedItem": { "message": "مورد برای همیشه حذف شد" @@ -1491,7 +1491,7 @@ "message": "بازیابی مورد" }, "restoreItemConfirmation": { - "message": "آیا مطمئن هستید می خواهید این مورد را بازیابی کنید؟" + "message": "آیا مطمئن هستید که می‌خواهید این مورد را بازیابی کنید؟" }, "restoredItem": { "message": "مورد بازیابی شد" @@ -1500,28 +1500,28 @@ "message": "حذف دائمی" }, "vaultTimeoutLogOutConfirmation": { - "message": "خروج از سیستم تمام دسترسی ها به گاو‌صندوق شما را از بین می برد و نیاز به احراز هویت آنلاین پس از مدت زمان توقف دارد. آیا مطمئن هستید که می خواهید از این تنظیمات استفاده کنید؟" + "message": "خروج از سیستم، تمام دسترسی ها به گاو‌صندوق شما را از بین می‌برد و نیاز به احراز هویت آنلاین پس از مدت زمان توقف دارد. آیا مطمئن هستید که می‌خواهید از این تنظیمات استفاده کنید؟" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "تایید عمل توقف" + "message": "تأیید عمل توقف" }, "enterpriseSingleSignOn": { - "message": "ورود به سیستم پروژه" + "message": "ورود به سیستم واحد سازمانی" }, "setMasterPassword": { "message": "تنظیم کلمه عبور اصلی" }, "ssoCompleteRegistration": { - "message": "برای تکمیل ورود به سیستم با SSO ، لطفاً یک کلمه عبور اصلی برای دسترسی و محافظت از گاوصندوق خود تنظیم کنید." + "message": "برای پر کردن ورود به سیستم با SSO، لطفاً یک کلمه عبور اصلی برای دسترسی و محافظت از گاوصندوق خود تنظیم کنید." }, "newMasterPass": { "message": "کلمه عبور اصلی جدید" }, "confirmNewMasterPass": { - "message": "تایید کلمه عبور اصلی جدید" + "message": "تأیید کلمه عبور اصلی جدید" }, "masterPasswordPolicyInEffect": { - "message": "یک یا چند سیاست سازمانی برای تأمین شرایط زیر به گذرواژه اصلی شما احتیاج دارد:" + "message": "یک یا چند سیاست سازمانی برای تأمین شرایط زیر به کلمه عبور اصلی شما احتیاج دارد:" }, "policyInEffectMinComplexity": { "message": "حداقل نمره پیچیدگی $SCORE$", @@ -1560,10 +1560,10 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پپیروی نمی کند." + "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پیروی نمی‌کند." }, "acceptPolicies": { - "message": "با علامت زدن این کادر با موارد زیر موافقت می کنید:" + "message": "با علامت زدن این کادر با موارد زیر موافقت می‌کنید:" }, "acceptPoliciesRequired": { "message": "شرایط خدمات و سیاست حفظ حریم خصوصی تأیید نشده است." @@ -1572,7 +1572,7 @@ "message": "فعال کردن ادغام مرورگر" }, "enableBrowserIntegrationDesc": { - "message": "یکپارچه سازی مرورگر برای بیومتریک در مرورگر استفاده می شود." + "message": "یکپارچه سازی مرورگر برای بیومتریک در مرورگر استفاده می‌شود." }, "enableDuckDuckGoBrowserIntegration": { "message": "اجازه ادغام مرورگر DuckDuckGo را بدهید" @@ -1581,16 +1581,16 @@ "message": "هنگام مرور با DuckDuckGo از گاوصندوق Bitwarden خود استفاده کنید." }, "browserIntegrationUnsupportedTitle": { - "message": "ادغام مرورگر پشتیبانی نمی شود" + "message": "ادغام مرورگر پشتیبانی نمی‌شود" }, "browserIntegrationMasOnlyDesc": { - "message": "متأسفانه در حال حاضر ادغام مرورگر فقط در نسخه Mac App Store پشتیبانی می شود." + "message": "متأسفانه در حال حاضر ادغام مرورگر فقط در نسخه Mac App Store پشتیبانی می‌شود." }, "browserIntegrationWindowsStoreDesc": { - "message": "متأسفانه در حال حاضر ادغام مرورگر در نسخه فروشگاه ویندوز پشتیبانی نمی شود." + "message": "متأسفانه در حال حاضر ادغام مرورگر در نسخه فروشگاه ویندوز پشتیبانی نمی‌شود." }, "browserIntegrationLinuxDesc": { - "message": "متأسفانه در حال حاضر ادغام مرورگر در نسخه لینوکس پشتیبانی نمی شود." + "message": "متأسفانه در حال حاضر ادغام مرورگر در نسخه لینوکس پشتیبانی نمی‌شود." }, "enableBrowserIntegrationFingerprint": { "message": "برای ادغام مرورگر نیاز به تأیید است" @@ -1599,7 +1599,7 @@ "message": "هنگام ایجاد پیوند بین دسکتاپ و مرورگر خود، با تأیید اعتبار اثر انگشت، یک لایه امنیتی دیگر را فعال کنید. درصورت فعال بودن، این امر مستلزم مداخله و تأیید کاربر در هر بار برقراری ارتباط است." }, "approve": { - "message": "تایید" + "message": "تأیید" }, "verifyBrowserTitle": { "message": "اتصال مرورگر را تأیید کنید" @@ -1629,26 +1629,26 @@ "message": "بیومتریک مرورگر ابتدا نیاز به فعالسازی بیومتریک دسکتاپ در تنظیمات دارد." }, "personalOwnershipSubmitError": { - "message": "به دلیل خط مشی Enterprise ، برای ذخیره موارد در گاوصندوق شخصی خود محدود شده اید. گزینه مالکیت را به یک سازمان تغییر دهید و مجموعه های موجود را انتخاب کنید." + "message": "به دلیل سیاست پرمیوم، برای ذخیره موارد در گاوصندوق شخصی خود محدود شده اید. گزینه مالکیت را به یک سازمان تغییر دهید و مجموعه های موجود را انتخاب کنید." }, "hintEqualsPassword": { - "message": "نکته کلمه عبور شما نمی تواند همان کلمه عبور شما باشد." + "message": "اشاره به کلمه عبور شما نمی‌تواند همان کلمه عبور شما باشد." }, "personalOwnershipPolicyInEffect": { - "message": "خط مشی سازمانی بر تنظیمات مالکیت شما تأثیر می گذارد." + "message": "سیاست سازمانی بر تنظیمات مالکیت شما تأثیر می‌گذارد." }, "allSends": { "message": "همه ارسال ها", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeFile": { - "message": "فایل" + "message": "پرونده" }, "sendTypeText": { "message": "متن" }, "searchSends": { - "message": "ارسال ها را جستجو کن", + "message": "جستجوی ارسال‌ها", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -1672,7 +1672,7 @@ "message": "تاريخ انقضاء" }, "expirationDateDesc": { - "message": "در صورت تنظیم، دسترسی به این ارسال در تاریخ و ساعت مشخص شده منقضی می شود.", + "message": "در صورت تنظیم، دسترسی به این ارسال در تاریخ و ساعت مشخص شده منقضی می‌شود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { @@ -1680,7 +1680,7 @@ "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "maxAccessCountDesc": { - "message": "در صورت تنظیم، با رسیدن به حداکثر تعداد دسترسی، کاربران دیگر نمی توانند به این ارسال دسترسی پیدا کنند.", + "message": "در صورت تنظیم، با رسیدن به حداکثر تعداد دسترسی، کاربران دیگر نمی‌توانند به این ارسال دسترسی پیدا کنند.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { @@ -1711,15 +1711,15 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "ارسال جدید ساخته شد", + "message": "ارسال اضافه شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "ارسال جدید ویرایش شد", + "message": "ارسال ذخیره شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "ارسال پاک شد", + "message": "ارسال حذف شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { @@ -1730,14 +1730,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "ساختن ارسال", + "message": "ارسال جدید", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { - "message": "متنی که می خواهید ارسال کنید." + "message": "متنی که می‌خواهید ارسال کنید." }, "sendFileDesc": { - "message": "فایلی که می خواهید ارسال کنید." + "message": "پرونده ای که می‌خواهید ارسال کنید." }, "days": { "message": "$DAYS$ روز", @@ -1755,22 +1755,22 @@ "message": "سفارشی" }, "deleteSendConfirmation": { - "message": "آیا مطمئن هستید می خواهید این ارسال را حذف کنید؟", + "message": "آیا مطمئن هستید که می‌خواهید این ارسال را حذف کنید؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkToClipboard": { - "message": "کپی لینک ارسال به حافظه موقت", + "message": "کپی پیوند ارسال به کلیپ بورد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkOnSave": { "message": "این پیوند را برای به اشتراک گذاری ارسال بعد از ارسال کپی کن." }, "sendDisabled": { - "message": "ارسال غیرفعال شد", + "message": "ارسال حذف شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "به دلیل خط مشی سازمانی، شما فقط می توانید ارسال موجود را حذف کنید.", + "message": "به دلیل سیاست سازمانی، شما فقط می‌توانید ارسال موجود را حذف کنید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { @@ -1780,13 +1780,13 @@ "message": "غیرفعال شد" }, "removePassword": { - "message": "حذف رمز عبور" + "message": "حذف کلمه عبور" }, "removedPassword": { - "message": "رمز عبور حذف شد" + "message": "کلمه عبور حذف شد" }, "removePasswordConfirmation": { - "message": "مطمئنید که می‌خواهید رمز عبور حذف شود؟" + "message": "مطمئنید که می‌خواهید کلمه عبور حذف شود؟" }, "maxAccessCountReached": { "message": "به حداکثر تعداد دسترسی رسیده است" @@ -1801,34 +1801,34 @@ "message": "تأیید اعتبار در WebAuthn" }, "hideEmail": { - "message": "آدرس ایمیلم را از گیرندگان مخفی کن." + "message": "نشانی ایمیلم را از گیرندگان مخفی کن." }, "sendOptionsPolicyInEffect": { - "message": "یک یا چند سیاست سازمان بر گزینه های ارسال شما تأثیر می گذارد." + "message": "یک یا چند سیاست سازمان بر گزینه های ارسال شما تأثیر می‌گذارد." }, "emailVerificationRequired": { - "message": "تایید ایمیل لازم است" + "message": "تأیید ایمیل لازم است" }, "emailVerificationRequiredDesc": { "message": "برای استفاده از این ویژگی باید ایمیل خود را تأیید کنید." }, "passwordPrompt": { - "message": "کلمه عبور اصلی دوباره تولید می شود" + "message": "درخواست مجدد کلمه عبور اصلی" }, "passwordConfirmation": { "message": "تأیید کلمه عبور اصلی" }, "passwordConfirmationDesc": { - "message": "این عمل محافظت می شود. برای ادامه، لطفاً کلمه ورود اصلی خود را دوباره وارد کنید تا هویتان را تأیید کنید." + "message": "این عمل محافظت می‌شود. برای ادامه، لطفاً کلمه عبور اصلی خود را دوباره وارد کنید تا هویت‌تان را تأیید کنید." }, "updatedMasterPassword": { - "message": "کلمه عبور اصلی بروز شد" + "message": "کلمه عبور اصلی به‌روز شد" }, "updateMasterPassword": { - "message": "بروزرسانی کلمه عبور اصلی" + "message": "به‌روزرسانی کلمه عبور اصلی" }, "updateMasterPasswordWarning": { - "message": "کلمه عبور اصلی شما اخیراً توسط سرپرست سازمانتان تغییر کرده است. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." + "message": "کلمه عبور اصلی شما اخیراً توسط سرپرست سازمان‌تان تغییر کرده است. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, "hours": { "message": "ساعت" @@ -1837,7 +1837,7 @@ "message": "دقیقه" }, "vaultTimeoutPolicyInEffect": { - "message": "خط مشی های سازمانتان بر مهلت زمانی گاوصندوق شما تأثیر می گذارد. حداکثر زمان مجاز گاوصندوق $HOURS$ ساعت و $MINUTES$ دقیقه است", + "message": "سیاست‌های سازمانتان بر مهلت زمانی گاوصندوق شما تأثیر می‌گذارد. حداکثر زمان مجاز گاوصندوق $HOURS$ ساعت و $MINUTES$ دقیقه است", "placeholders": { "hours": { "content": "$1", @@ -1856,22 +1856,22 @@ "message": "ثبت نام خودکار" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "این سازمان دارای خط مشی سازمانی ای است که به طور خودکار شما را در بازنشانی کلمه عبور ثبت نام می کند. این ثبت نام به مدیران سازمان اجازه می دهد تا کلمه عبور اصلی شما را تغییر دهند." + "message": "این سازمان دارای سیاست سازمانی ای است که به طور خودکار شما را در بازنشانی کلمه عبور ثبت نام می‌کند. این ثبت نام به مدیران سازمان اجازه می‌دهد تا کلمه عبور اصلی شما را تغییر دهند." }, "vaultExportDisabled": { - "message": "صادرات گاوصندوق غیرفعال شده است" + "message": "برون ریزی گاوصندوق غیرفعال شده است" }, "personalVaultExportPolicyInEffect": { - "message": "یک یا چند خط مشی سازمان از صادرات گاوصندوق شخصی شما جلوگیری می کند." + "message": "یک یا چند سیاست سازمان از برون ریزی گاوصندوق شخصی شما جلوگیری می‌کند." }, "addAccount": { "message": "افزودن حساب کاربری" }, "removeMasterPassword": { - "message": "پاک کردن کلمه عبور اصلی" + "message": "حذف کلمه عبور اصلی" }, "removedMasterPassword": { - "message": "کلمه عبور اصلی پاک شد." + "message": "کلمه عبور اصلی حذف شد" }, "convertOrganizationEncryptionDesc": { "message": "$ORGANIZATION$ در حال استفاده از SSO با یک سرور کلید خود میزبان است. برای ورود اعضای این سازمان دیگر نیازی به کلمه عبور اصلی نیست.", @@ -1886,31 +1886,31 @@ "message": "ترک سازمان" }, "leaveOrganizationConfirmation": { - "message": "آيا مطمئن هستيد که می خواهيد سازمان های انتخاب شده را ترک کنيد؟" + "message": "آيا مطمئنید که می‌خواهيد سازمان انتخاب شده را ترک کنيد؟" }, "leftOrganization": { - "message": "شما از سازمان ها خارج شده اید." + "message": "شما از سازمان خارج شده اید." }, "ssoKeyConnectorError": { - "message": "خطای Key Connector: مطمئن شوید که Key Connector در دسترس است و به درستی کار می کند." + "message": "خطای رابط کلید: مطمئن شوید که رابط کلید در دسترس است و به درستی کار می‌کند." }, "lockAllVaults": { "message": "قفل کردن تمام گاوصندوق ها" }, "accountLimitReached": { - "message": "بیش از 5 حساب را نمی توان همزمان وارد کرد." + "message": "بیش از 5 حساب را نمی‌توان همزمان وارد کرد." }, "accountPreferences": { "message": "تنظیمات" }, "appPreferences": { - "message": "تنظیمات اپ (تمام حسابها)" + "message": "تنظیمات برنامه (تمام حساب‌ها)" }, "accountSwitcherLimitReached": { "message": "محدودیت حساب تکمیل شد. برای افزودن حساب دیگر، از یک حساب خارج شوید." }, "settingsTitle": { - "message": "تنظیمات اپ برای $EMAIL$", + "message": "تنظیمات برنامه برای $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -1928,10 +1928,10 @@ "message": "زمان نشست شما به پایان رسید. لطفاً برگردید و دوباره وارد سیستم شوید." }, "exportingPersonalVaultTitle": { - "message": "صادرات گاو‌صندوق شخصی" + "message": "برون ریزی گاو‌صندوق شخصی" }, "exportingPersonalVaultDescription": { - "message": "فقط موارد گاو‌صندوق شخصی مرتبط با $EMAIL$ صادر خواهد شد. موارد گاو‌صندوق سازمان شامل نخواهد شد.", + "message": "فقط موارد گاو‌صندوق شخصی مرتبط با $EMAIL$ برون ریزی خواهد شد. موارد گاو‌صندوق سازمان شامل نخواهد شد.", "placeholders": { "email": { "content": "$1", @@ -1952,7 +1952,7 @@ "message": "چه چیزی دوست دارید تولید کنید؟" }, "passwordType": { - "message": "نوع گذرواژه" + "message": "نوع کلمه عبور" }, "regenerateUsername": { "message": "ایجاد مجدد نام کاربری" @@ -1964,14 +1964,14 @@ "message": "نوع نام کاربری" }, "plusAddressedEmail": { - "message": "به علاوه ایمیل آدرس داده شده", + "message": "به علاوه نشانی ایمیل داده شده", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { "message": "از قابلیت های آدرس دهی فرعی ارائه دهنده ایمیل خود استفاده کنید." }, "catchallEmail": { - "message": "رایانامه همه‌گیر" + "message": "دریافت همه ایمیل‌ها" }, "catchallEmailDesc": { "message": "از صندوق ورودی پیکربندی شده دامنه خود استفاده کنید." @@ -1983,16 +1983,16 @@ "message": "کلمه تصادفی" }, "websiteName": { - "message": "نام وب سایت" + "message": "نام وب‌سایت" }, "service": { - "message": "خدمت" + "message": "سرویس" }, "allVaults": { "message": "تمام گاو‌صندوق‌ها" }, "searchOrganization": { - "message": "سازماندهی جستجو" + "message": "جستجوی سازمان" }, "searchMyVault": { "message": "جستجوی گاوصندوق من" @@ -2001,7 +2001,7 @@ "message": "نام مستعار ایمیل فوروارد شده" }, "forwardedEmailDesc": { - "message": "یک نام مستعار ایمیل با یک سرویس فروارد خارجی ایجاد کنید." + "message": "یک نام مستعار ایمیل با یک سرویس ارسال خارجی ایجاد کنید." }, "hostname": { "message": "نام میزبان", @@ -2023,7 +2023,7 @@ "message": "موارد موجود در سازمان‌های غیرفعال، قابل دسترسی نیستند. برای دریافت کمک با مالک سازمان خود تماس بگیرید." }, "neverLockWarning": { - "message": "آیا جدا میخواهید از گزینه \"هرگز\" استفاده کنید؟ تنظیم کردن کردن گزینه قفل به \"هرگز\" کلیدهای رمزنگاری گاوصندوقتان را بر روی دستگاه شما ذخیره خواهد کرد. اگر از این گزینه استفاده میکنید باید اطمینان داشته باشید که دستگاه شما کاملا محافظت شده است." + "message": "آیا جداً می‌خواهید از گزینه \"هرگز\" استفاده کنید؟ تنظیم کردن گزینه قفل به \"هرگز\" کلیدهای رمزنگاری گاوصندوقتان را بر روی دستگاه شما ذخیره خواهد کرد. اگر از این گزینه استفاده می‌کنید باید اطمینان داشته باشید که دستگاه شما کاملا محافظت شده است." }, "cardBrandMir": { "message": "میر" diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index da7189bdbad..a15eef3ff88 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -175,10 +175,10 @@ "message": "Adresse" }, "premiumRequired": { - "message": "Version Premium requise" + "message": "Premium requis" }, "premiumRequiredDesc": { - "message": "Une adhésion premium est requise pour utiliser cette fonctionnalité." + "message": "Une adhésion Premium est requise pour utiliser cette fonctionnalité." }, "errorOccurred": { "message": "Une erreur est survenue." @@ -500,19 +500,19 @@ "message": "Soumettre" }, "masterPass": { - "message": "Mot de passe maître" + "message": "Mot de passe principal" }, "masterPassDesc": { - "message": "Le mot de passe maître est le mot de passe que vous utilisez pour accéder à votre coffre. Il est très important de ne pas l'oublier. Il n'existe aucun moyen de le récupérer si vous le perdez." + "message": "Le mot de passe principal est le mot de passe que vous utilisez pour accéder à votre coffre. Il est très important de ne pas oublier votre mot de passe principal. Il n'existe aucun moyen de récupérer le mot de passe si vous l'oubliez." }, "masterPassHintDesc": { - "message": "Un indice de mot de passe maître peut vous aider à vous rappeler de votre mot de passe en cas d'oubli." + "message": "Un indice de mot de passe principal peut vous aider à vous souvenir de votre mot de passe si vous l'oubliez." }, "reTypeMasterPass": { - "message": "Saisir à nouveau le mot de passe maître" + "message": "Ressaisir le mot de passe principal" }, "masterPassHint": { - "message": "Indice du mot de passe maître (facultatif)" + "message": "Indice du mot de passe principal (facultatif)" }, "settings": { "message": "Paramètres" @@ -521,10 +521,10 @@ "message": "Indice mot de passe" }, "enterEmailToGetHint": { - "message": "Saisissez l'adresse e-mail de votre compte pour recevoir l'indice de votre mot de passe maître." + "message": "Saisissez l'adresse électronique de votre compte pour recevoir l'indice de votre mot de passe principal." }, "getMasterPasswordHint": { - "message": "Obtenir l'indice du mot de passe maître" + "message": "Obtenir l'indice du mot de passe principal" }, "emailRequired": { "message": "L'adresse e-mail est requise." @@ -533,22 +533,22 @@ "message": "Adresse e-mail invalide." }, "masterPasswordRequired": { - "message": "Le mot de passe maître est requis." + "message": "Le mot de passe principal est requis." }, "confirmMasterPasswordRequired": { - "message": "Le mot de passe maître doit être entré de nouveau." + "message": "Une nouvelle saisie du mot de passe principal est nécessaire." }, "masterPasswordMinlength": { - "message": "Le mot de passe maître doit comporter au moins 8 caractères." + "message": "Le mot de passe principal doit comporter au moins 8 caractères." }, "masterPassDoesntMatch": { - "message": "La confirmation du mot de passe maître ne correspond pas." + "message": "La confirmation du mot de passe principal ne correspond pas." }, "newAccountCreated": { "message": "Votre nouveau compte a été créé ! Vous pouvez maintenant vous authentifier." }, "masterPassSent": { - "message": "Nous vous avons envoyé un e-mail contenant votre indice de mot de passe maître." + "message": "Nous vous avons envoyé un courriel avec votre indice de mot de passe principal." }, "unexpectedError": { "message": "Une erreur inattendue est survenue." @@ -662,13 +662,13 @@ "message": "Identifiant non disponible" }, "noTwoStepProviders": { - "message": "Ce compte dispose d'une authentification à deux étapes, cependant, aucun service d'authentification à deux étapes n'est supporté par ce navigateur web." + "message": "Ce compte a une authentification à deux facteurs configurée, mais aucun des fournisseurs d'authentification à deux facteurs configurés ne sont pris en charge par cet appareil." }, "noTwoStepProviders2": { "message": "Veuillez ajouter des services additionnels qui supportent une utilisation sur plusieurs appareils (comme une application d'authentification)." }, "twoStepOptions": { - "message": "Options d'identification à deux étapes" + "message": "Options d'authentification à deux facteurs" }, "selfHostedEnvironment": { "message": "Environnement auto-hébergé" @@ -731,7 +731,7 @@ "message": "Êtes-vous sûr de vouloir vous déconnecter ?" }, "logOut": { - "message": "Déconnexion" + "message": "Se déconnecter" }, "addNewLogin": { "message": "Ajouter un site" @@ -776,10 +776,10 @@ "message": "Synchroniser le coffre maintenant" }, "changeMasterPass": { - "message": "Modifier le mot de passe maître" + "message": "Changer le mot de passe principal" }, "changeMasterPasswordConfirmation": { - "message": "Vous pouvez modifier votre mot de passe maître depuis le coffre web sur bitwarden.com. Souhaitez-vous visiter le site maintenant ?" + "message": "Vous pouvez changer votre mot de passe principal depuis le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" }, "fingerprintPhrase": { "message": "Phrase d'empreinte", @@ -824,13 +824,13 @@ } }, "invalidMasterPassword": { - "message": "Mot de passe maître invalide" + "message": "Mot de passe principal invalide" }, "twoStepLoginConfirmation": { - "message": "L'identification en deux étapes sécurise plus encore votre compte en vous demandant à chaque identification la saisie d'un code de sécurité obtenu depuis un autre appareil, comme une clef de sécurité, une application d'authentification, un SMS, un appel téléphonique ou encore un courriel. L'identification en deux étapes peut être activée dans le coffre web sur bitwarden.com. Voulez-vous vous rendre sur le site web maintenant ?" + "message": "L'authentification à deux facteurs rend votre compte plus sûr en vous demandant de vérifier votre connexion avec un autre dispositif tel qu'une clé de sécurité, une application d'authentification, un SMS, un appel téléphonique ou un courriel. L'authentification à deux facteurs peut être configurée sur le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" }, "twoStepLogin": { - "message": "Identification à deux facteurs" + "message": "Authentification à deux facteurs" }, "vaultTimeout": { "message": "Délai d'expiration du coffre" @@ -1050,49 +1050,49 @@ "message": "Gérer l'adhésion" }, "premiumManageAlert": { - "message": "Vous pouvez gérer votre adhésion depuis le coffre web sur bitwarden.com. Souhaitez-vous visiter le site web maintenant ?" + "message": "Vous pouvez gérer votre adhésion sur le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" }, "premiumRefresh": { "message": "Actualiser l'adhésion" }, "premiumNotCurrentMember": { - "message": "Vous n'êtes actuellement pas un membre premium." + "message": "Vous n'êtes pas actuellement un membre Premium." }, "premiumSignUpAndGet": { - "message": "Devenez un membre premium et obtenez :" + "message": "Inscrivez-vous pour une adhésion Premium et obtenez :" }, "premiumSignUpStorage": { - "message": "1 Go de stockage de fichiers chiffrés." + "message": "1 Go de stockage chiffré pour les fichiers joints." }, "premiumSignUpTwoStep": { - "message": "Options d'identification en deux étapes additionnelles comme YubiKey, FIDO U2F et Duo." + "message": "Options additionnelles d'identification à deux étapes telles que YubiKey, FIDO U2F et Duo." }, "premiumSignUpReports": { - "message": "Rapports sur l'hygiène des mots de passe, la santé des comptes et les fuites de données pour assurer la sécurité de votre coffre." + "message": "Hygiène du mot de passe, santé du compte et rapports sur les brèches de données pour assurer la sécurité de votre coffre." }, "premiumSignUpTotp": { - "message": "Génération d'un code de vérification TOTP (2FA) pour les identifiants de votre coffre." + "message": "Générateur de code de vérification TOTP (2FA) pour les identifiants dans votre coffre." }, "premiumSignUpSupport": { - "message": "Support client prioritaire." + "message": "Assistance client prioritaire." }, "premiumSignUpFuture": { - "message": "Toutes les futures options premium. D'autres suivront prochainement !" + "message": "Toutes les futures fonctionnalités Premium. Plus à venir prochainement !" }, "premiumPurchase": { "message": "Acheter Premium" }, "premiumPurchaseAlert": { - "message": "Vous pouvez opter pour une adhésion premium depuis le coffre web sur bitwarden.com. Souhaitez-vous visiter le site web maintenant ?" + "message": "Vous pouvez acheter une adhésion Premium sur le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" }, "premiumCurrentMember": { - "message": "Vous êtes un adhérent premium !" + "message": "Vous êtes un membre Premium !" }, "premiumCurrentMemberThanks": { - "message": "Merci de supporter Bitwarden." + "message": "Merci de soutenir Bitwarden." }, "premiumPrice": { - "message": "Tout pour seulement $PRICE$ /an !", + "message": "Tout pour seulement $PRICE$/an !", "placeholders": { "price": { "content": "$1", @@ -1355,10 +1355,10 @@ "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Mot de passe maître faible" + "message": "Mot de passe principal faible" }, "weakMasterPasswordDesc": { - "message": "Le mot de passe maître que vous avez choisi est faible. Vous devriez utiliser un mot de passe (ou une phrase secrète) fort(e) pour protéger correctement votre compte Bitwarden. Êtes-vous sûr de vouloir utiliser ce mot de passe maître ?" + "message": "Le mot de passe principal que vous avez choisi est faible. Vous devriez utiliser un mot de passe principal fort (ou une phrase de passe) pour protéger correctement votre compte Bitwarden. Êtes-vous sûr de vouloir utiliser ce mot de passe principal ?" }, "pin": { "message": "Code PIN", @@ -1395,7 +1395,7 @@ "message": "Demander à Touch ID au démarrage" }, "lockWithMasterPassOnRestart": { - "message": "Verrouiller avec le mot de passe maître lors du redémarrage" + "message": "Verrouiller avec le mot de passe principal au redémarrage" }, "deleteAccount": { "message": "Supprimer le compte" @@ -1428,7 +1428,7 @@ "message": "Vous devez sélectionner au moins une collection." }, "premiumUpdated": { - "message": "Vous venez de passer à un compte Premium." + "message": "Vous avez mis à niveau vers Premium." }, "restore": { "message": "Restaurer" @@ -1462,7 +1462,7 @@ "message": "Action lors de l'expiration du délai du coffre" }, "vaultTimeoutActionLockDesc": { - "message": "Votre mot de passe maître ou une autre méthode de déverrouillage est nécessaire pour accéder de nouveau à votre coffre." + "message": "Un mot de passe principal ou une autre méthode de déverrouillage est nécessaire pour accéder à nouveau à votre coffre." }, "vaultTimeoutActionLogOutDesc": { "message": "Un coffre déconnecté nécessite que vous vous ré-authentifiez pour y accéder de nouveau." @@ -1500,7 +1500,7 @@ "message": "Supprimer définitivement" }, "vaultTimeoutLogOutConfirmation": { - "message": "La déconnexion supprimera tous les accès à votre coffre et nécessite une authentification en ligne après la période d'expiration. Êtes-vous sûr de vouloir utiliser ce paramètre?" + "message": "La déconnexion supprimera tout accès à votre coffre et nécessitera une authentification en ligne après la période d'expiration. Êtes-vous sûr de vouloir utiliser ce paramètre ?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Confirmation de l'action lors de l'expiration du délai" @@ -1509,19 +1509,19 @@ "message": "Portail de connexion unique d'entreprise" }, "setMasterPassword": { - "message": "Définir le mot de passe maître" + "message": "Définir le mot de passe principal" }, "ssoCompleteRegistration": { - "message": "Afin de terminer la connexion avec SSO, veuillez définir un mot de passe maître pour accéder à votre coffre et le protéger." + "message": "Afin de finaliser la connexion avec SSO, veuillez définir un mot de passe principal pour accéder et protéger votre coffre." }, "newMasterPass": { - "message": "Nouveau mot de passe maître" + "message": "Nouveau mot de passe principal" }, "confirmNewMasterPass": { - "message": "Confirmer le nouveau mot de passe maître" + "message": "Confirmer le nouveau mot de passe principal" }, "masterPasswordPolicyInEffect": { - "message": "Une ou plusieurs politiques de l'organisation exigent que votre mot de passe maître réponde aux exigences suivantes :" + "message": "Une ou plusieurs politiques de l'organisation exigent que votre mot de passe principal réponde aux exigences suivantes :" }, "policyInEffectMinComplexity": { "message": "Score de complexité minimum de $SCORE$", @@ -1560,7 +1560,7 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "Votre nouveau mot de passe maître ne répond pas aux exigences de la politique." + "message": "Votre nouveau mot de passe principal ne répond pas aux exigences en matière de politique de sécurité." }, "acceptPolicies": { "message": "En cochant cette case, vous acceptez les éléments suivants :" @@ -1813,22 +1813,22 @@ "message": "Vous devez vérifier votre adresse e-mail pour utiliser cette fonctionnalité." }, "passwordPrompt": { - "message": "Ressaisie du mot de passe maître" + "message": "Ressaisir le mot de passe principal" }, "passwordConfirmation": { - "message": "Confirmation du mot de passe maître" + "message": "Confirmation du mot de passe principal" }, "passwordConfirmationDesc": { - "message": "Cette action est protégée. Pour continuer, veuillez ressaisir votre mot de passe maître pour vérifier votre identité." + "message": "Cette action est protégée. Pour continuer, veuillez saisir à nouveau votre mot de passe principal pour vérifier votre identité." }, "updatedMasterPassword": { - "message": "Mot de passe maître mis à jour" + "message": "Mot de passe principal mis à jour" }, "updateMasterPassword": { - "message": "Mettre à jour le mot de passe maître" + "message": "Mettre à jour le mot de passe principal" }, "updateMasterPasswordWarning": { - "message": "Votre mot de passe maître a récemment été modifié par un administrateur de votre organisation. Pour pouvoir accéder au coffre, vous devez mettre à jour votre mot de passe maître maintenant. Poursuivre vous déconnectera de votre session actuelle, vous obligeant à vous reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives jusqu'à une heure." + "message": "Votre mot de passe principal a été récemment changé par un administrateur de votre organisation. Pour pouvoir accéder au coffre, vous devez le mettre à jour maintenant. En poursuivant, vous serez déconnecté de votre session actuelle et vous devrez vous reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives pendant encore une heure." }, "hours": { "message": "Heures" @@ -1856,7 +1856,7 @@ "message": "Inscription automatique" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Cette organisation a une politique d'entreprise qui vous inscrira automatiquement à la réinitialisation du mot de passe. L'inscription permettra aux administrateurs de l'organisation de changer votre mot de passe maître." + "message": "Cette organisation dispose d'une politique d'entreprise qui vous inscrira automatiquement à la réinitialisation du mot de passe. L'inscription permettra aux administrateurs de l'organisation de changer votre mot de passe principal." }, "vaultExportDisabled": { "message": "Export du coffre désactivé" @@ -1868,13 +1868,13 @@ "message": "Ajouter un compte" }, "removeMasterPassword": { - "message": "Supprimer le mot de passe maître" + "message": "Supprimer le mot de passe principal" }, "removedMasterPassword": { - "message": "Mot de passe maître supprimé." + "message": "Mot de passe principal supprimé" }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ utilise SSO avec un serveur de clés auto-hébergé. Un mot de passe maître n'est plus nécessaire aux membres de cette organisation pour se connecter.", + "message": "$ORGANIZATION$ utilise SSO avec un serveur de clés auto-hébergé. Un mot de passe principal n'est plus nécessaire aux membres de cette organisation pour se connecter.", "placeholders": { "organization": { "content": "$1", @@ -2032,7 +2032,7 @@ "message": "Coffre" }, "loginWithMasterPassword": { - "message": "Connectez-vous avec le mot de passe maître" + "message": "Se connecter avec le mot de passe principal" }, "loggingInAs": { "message": "Connexion en tant que" diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index ed5282d711b..f728b3bbbb6 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -104,7 +104,7 @@ "message": "Email cím" }, "verificationCodeTotp": { - "message": "Ellenőrző kód (egyszeri-idő alapú)" + "message": "Ellenőrző kód (TOTP)" }, "website": { "message": "Webhely" @@ -126,7 +126,7 @@ "message": "Minimalizálás vágólapra másoláskor" }, "minimizeOnCopyToClipboardDesc": { - "message": "Minimalizálás egy elem adatainak vágólapra való másolásakor." + "message": "Minimalizálás asz összes elemadat vágólapra másolásakor." }, "toggleVisibility": { "message": "Láthatóság váltása" @@ -467,7 +467,7 @@ "message": "Fájl" }, "selectFile": { - "message": "Válassz ki egy fájlt." + "message": "Válasszunk egy fájlt." }, "maxFileSize": { "message": "Maximális fájl méret 500 MB." @@ -476,7 +476,7 @@ "message": "Ez a funkció nem használható a titkosítási kulcs frissítéséig." }, "editedFolder": { - "message": "A mappa szerkesztésre került." + "message": "A mappa mentésre került." }, "addedFolder": { "message": "A mappa hozzáadásra került." @@ -734,13 +734,13 @@ "message": "Kijelentkezés" }, "addNewLogin": { - "message": "Új fiók hozzáadása" + "message": "Új bejelentkezés" }, "addNewItem": { - "message": "Új elem hozzáadása" + "message": "Új elem" }, "addNewFolder": { - "message": "Új mappa hozzáadása" + "message": "Új mappa" }, "view": { "message": "Megtekintés" @@ -770,7 +770,7 @@ "message": "Blog" }, "followUs": { - "message": "Kövess minket" + "message": "Követés" }, "syncVault": { "message": "Széf szinkronizálása" @@ -793,7 +793,7 @@ "message": "Ugrás a webes széfhez" }, "getMobileApp": { - "message": "Mobil app letöltése" + "message": "Mobil alkalmazás beszerzése" }, "getBrowserExtension": { "message": "Böngésző bővítmény beszerzése" @@ -833,10 +833,10 @@ "message": "Kétlépcsős bejelentkezés" }, "vaultTimeout": { - "message": "Széf időkorlát" + "message": "Széf időkifutás" }, "vaultTimeoutDesc": { - "message": "Állítsd be a széfedhez egy időkorlátot és egy végrehajtandó műveletet." + "message": "Válasszuk ki, hogy a széfnél mikor legyen időkifutás és a kiválasztott művelet végrehajtása." }, "immediately": { "message": "Azonnal" @@ -872,13 +872,13 @@ "message": "4 óra" }, "onIdle": { - "message": "Tétlenség esetén" + "message": "Rendszer üresjárat esetén" }, "onSleep": { - "message": "Alvó módba lépéskor" + "message": "Rendszer alvó mód esetén" }, "onLocked": { - "message": "A rendszer zárolásakor" + "message": "Rendszer zárolás esetén" }, "onRestart": { "message": "Újraindításkor" @@ -904,7 +904,7 @@ "message": "Felismerhető kép megjelenítése minden bejelentkezés mellett." }, "enableMinToTray": { - "message": "Kicsinyítés tálcára" + "message": "Kicsinyítés tálcaikonná" }, "enableMinToTrayDesc": { "message": "Az ablak minimalizálásakor helyette egy ikon jelenik meg a rendszertálcán." @@ -1056,10 +1056,10 @@ "message": "Előfizetés ellenőrzése" }, "premiumNotCurrentMember": { - "message": "Jelenleg nem vagy prémium tag." + "message": "Jelenleg nincs prémium tagság." }, "premiumSignUpAndGet": { - "message": "Fizess elő prémiumra, hogy különféle előnyöket élvezhess:" + "message": "Regisztráció a prémium tagságra az alábbi funkciókért:" }, "premiumSignUpStorage": { "message": "1 GB titkosított fájlmelléklet tárhely." @@ -1231,7 +1231,7 @@ "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "Találatfelismerés", + "message": "Találat érzékelés", "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { @@ -1503,7 +1503,7 @@ "message": "Kijelentkezve az összes széf elérés eltávolításra kerül és webes hitelesítésre van szükség az időkifutás után. Biztosan szeretnénk használni ezt a beállítást?" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Időkifutáskori művelet megerősítése" + "message": "Időkifutás művelet megerősítés" }, "enterpriseSingleSignOn": { "message": "Vállalati önálló bejelentkezés" @@ -1587,7 +1587,7 @@ "message": "Sajnos a böngésző integrációt egyelőre csak a Mac App Store verzió támogatja." }, "browserIntegrationWindowsStoreDesc": { - "message": "A böngésző integrációt egyelőre csak a Microsoft Store verzió támogatja." + "message": "A böngésző integrációt egyelőre csak a Windows Store verzió támogatja." }, "browserIntegrationLinuxDesc": { "message": "Sajnos a böngésző integrációját a linux verzió jelenleg nem támogatja." @@ -1703,7 +1703,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinkLabel": { - "message": "Hivatkozás küldése", + "message": "Send küldése", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "textHiddenByDefault": { @@ -1711,15 +1711,15 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "A küldés létrejött", + "message": "A Send létrejött.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "A küldés szerkesztésre került", + "message": "A Send mentésre került.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "A küldés törlésre került", + "message": "A Send törlésre került.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { @@ -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": "A küldés létrejött.", + "message": "Új 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": "A hivatkozás másolása a Küldés megosztásához a vágólapra mentéskor." }, "sendDisabled": { - "message": "A küldés kikapcsolásra került", + "message": "A Send eltávolításra került.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -1807,7 +1807,7 @@ "message": "Egy vagy több szervezeti szabály érinti a Send opciókat." }, "emailVerificationRequired": { - "message": "E-mail hitelesítés szükséges" + "message": "Email hitelesítés szükséges" }, "emailVerificationRequiredDesc": { "message": "A funkció használatához ellenőrizni kell az email címet." diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index f82d12c2ae4..f0df7485811 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1569,31 +1569,31 @@ "message": "Nav apstiprināti izmantošanas nosacījumi un privātuma politika." }, "enableBrowserIntegration": { - "message": "Iespējot pārlūka saistīšanu" + "message": "Iespējot sasaistīšanu ar pārlūku" }, "enableBrowserIntegrationDesc": { "message": "Pārlūka saistīšana tiek izmantota pārlūka biometrijas nodrošināšanai." }, "enableDuckDuckGoBrowserIntegration": { - "message": "Atļaut DuckDuckGo pārlūka saistīšanu" + "message": "Atļaut sasaistīšanu ar DuckDuckGo pārlūku" }, "enableDuckDuckGoBrowserIntegrationDesc": { "message": "Izmantot Bitwarden glabātavu, kad pārlūko ar DuckDuckGo." }, "browserIntegrationUnsupportedTitle": { - "message": "Pārlūka saistīšana nav atbalstīta" + "message": "Sasaistīšana ar pārlūku nav atbalstīta" }, "browserIntegrationMasOnlyDesc": { - "message": "Diemžēl pārlūka saistīšana pagaidām ir nodrošināta tikai Mac App Store laidienā." + "message": "Diemžēl sasaistīšāna ar pārlūku pagaidām ir nodrošināta tikai Mac App Store laidienā." }, "browserIntegrationWindowsStoreDesc": { - "message": "Diemžēl pārlūka saistīšana pagaidām nav nodrošināta Windows veikala laidienā." + "message": "Diemžēl sasaistīšana ar pārlūku pagaidām nav nodrošināta Windows veikala laidienā." }, "browserIntegrationLinuxDesc": { - "message": "Diemžēl pārlūka saistīšana Linux laidienā pagaidām nav nodrošināta." + "message": "Diemžēl sasaistīšana ar pārlūku Linux laidienā pagaidām nav nodrošināta." }, "enableBrowserIntegrationFingerprint": { - "message": "Pieprasīt apstiprinājumu pārlūka saistīšanai" + "message": "Pieprasīt apstiprinājumu sasaistīšanai ar pārlūku" }, "enableBrowserIntegrationFingerprintDesc": { "message": "Iespējo papildus drošības slāni, pieprasot atpazīšanas vārdkopas pārbaudi, kad tiek izveidota saikne starp darbvirsmu un pārlūku. Kad iespējots, ir nepieciešama lietotāja mijiedarbīga un apstiprināšana katru reizi, kad tiek izveidots savienojums." diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json new file mode 100644 index 00000000000..57ea12a961d --- /dev/null +++ b/apps/desktop/src/locales/ne/messages.json @@ -0,0 +1,2065 @@ +{ + "bitwarden": { + "message": "Bitwarden" + }, + "filters": { + "message": "Filters" + }, + "allItems": { + "message": "All items" + }, + "favorites": { + "message": "Favorites" + }, + "types": { + "message": "Types" + }, + "typeLogin": { + "message": "Login" + }, + "typeCard": { + "message": "Card" + }, + "typeIdentity": { + "message": "Identity" + }, + "typeSecureNote": { + "message": "Secure note" + }, + "folders": { + "message": "Folders" + }, + "collections": { + "message": "Collections" + }, + "searchVault": { + "message": "Search vault" + }, + "addItem": { + "message": "Add item" + }, + "shared": { + "message": "Shared" + }, + "share": { + "message": "Share" + }, + "moveToOrganization": { + "message": "Move to organization" + }, + "movedItemToOrg": { + "message": "$ITEMNAME$ moved to $ORGNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + }, + "orgname": { + "content": "$2", + "example": "Company Name" + } + } + }, + "moveToOrgDesc": { + "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + }, + "attachments": { + "message": "Attachments" + }, + "viewItem": { + "message": "View item" + }, + "name": { + "message": "Name" + }, + "uri": { + "message": "URI" + }, + "uriPosition": { + "message": "URI $POSITION$", + "description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.", + "placeholders": { + "position": { + "content": "$1", + "example": "2" + } + } + }, + "newUri": { + "message": "New URI" + }, + "username": { + "message": "Username" + }, + "password": { + "message": "Password" + }, + "passphrase": { + "message": "Passphrase" + }, + "editItem": { + "message": "Edit item" + }, + "emailAddress": { + "message": "Email address" + }, + "verificationCodeTotp": { + "message": "Verification code (TOTP)" + }, + "website": { + "message": "Website" + }, + "notes": { + "message": "Notes" + }, + "customFields": { + "message": "Custom fields" + }, + "launch": { + "message": "Launch" + }, + "copyValue": { + "message": "Copy value", + "description": "Copy value to clipboard" + }, + "minimizeOnCopyToClipboard": { + "message": "Minimize when copying to clipboard" + }, + "minimizeOnCopyToClipboardDesc": { + "message": "Minimize application when copying an item's data to the clipboard." + }, + "toggleVisibility": { + "message": "Toggle visibility" + }, + "toggleCollapse": { + "message": "Toggle collapse", + "description": "Toggling an expand/collapse state." + }, + "cardholderName": { + "message": "Cardholder name" + }, + "number": { + "message": "Number" + }, + "brand": { + "message": "Brand" + }, + "expiration": { + "message": "Expiration" + }, + "securityCode": { + "message": "Security code" + }, + "identityName": { + "message": "Identity name" + }, + "company": { + "message": "Company" + }, + "ssn": { + "message": "Social Security number" + }, + "passportNumber": { + "message": "Passport number" + }, + "licenseNumber": { + "message": "License number" + }, + "email": { + "message": "Email" + }, + "phone": { + "message": "Phone" + }, + "address": { + "message": "Address" + }, + "premiumRequired": { + "message": "Premium required" + }, + "premiumRequiredDesc": { + "message": "A Premium membership is required to use this feature." + }, + "errorOccurred": { + "message": "An error has occurred." + }, + "error": { + "message": "Error" + }, + "january": { + "message": "January" + }, + "february": { + "message": "February" + }, + "march": { + "message": "March" + }, + "april": { + "message": "April" + }, + "may": { + "message": "May" + }, + "june": { + "message": "June" + }, + "july": { + "message": "July" + }, + "august": { + "message": "August" + }, + "september": { + "message": "September" + }, + "october": { + "message": "October" + }, + "november": { + "message": "November" + }, + "december": { + "message": "December" + }, + "ex": { + "message": "ex.", + "description": "Short abbreviation for 'example'." + }, + "title": { + "message": "Title" + }, + "mr": { + "message": "Mr" + }, + "mrs": { + "message": "Mrs" + }, + "ms": { + "message": "Ms" + }, + "dr": { + "message": "Dr" + }, + "expirationMonth": { + "message": "Expiration month" + }, + "expirationYear": { + "message": "Expiration year" + }, + "select": { + "message": "Select" + }, + "other": { + "message": "Other" + }, + "generatePassword": { + "message": "Generate password" + }, + "type": { + "message": "Type" + }, + "firstName": { + "message": "First name" + }, + "middleName": { + "message": "Middle name" + }, + "lastName": { + "message": "Last name" + }, + "fullName": { + "message": "Full name" + }, + "address1": { + "message": "Address 1" + }, + "address2": { + "message": "Address 2" + }, + "address3": { + "message": "Address 3" + }, + "cityTown": { + "message": "City / Town" + }, + "stateProvince": { + "message": "State / Province" + }, + "zipPostalCode": { + "message": "Zip / Postal code" + }, + "country": { + "message": "Country" + }, + "save": { + "message": "Save" + }, + "cancel": { + "message": "Cancel" + }, + "delete": { + "message": "Delete" + }, + "favorite": { + "message": "Favorite" + }, + "edit": { + "message": "Edit" + }, + "authenticatorKeyTotp": { + "message": "Authenticator key (TOTP)" + }, + "folder": { + "message": "Folder" + }, + "newCustomField": { + "message": "New custom field" + }, + "value": { + "message": "Value" + }, + "dragToSort": { + "message": "Drag to sort" + }, + "cfTypeText": { + "message": "Text" + }, + "cfTypeHidden": { + "message": "Hidden" + }, + "cfTypeBoolean": { + "message": "Boolean" + }, + "cfTypeLinked": { + "message": "Linked", + "description": "This describes a field that is 'linked' (related) to another field." + }, + "linkedValue": { + "message": "Linked value", + "description": "This describes a value that is 'linked' (related) to another value." + }, + "remove": { + "message": "Remove" + }, + "nameRequired": { + "message": "Name is required." + }, + "addedItem": { + "message": "Item added" + }, + "editedItem": { + "message": "Item saved" + }, + "deleteItem": { + "message": "Delete item" + }, + "deleteFolder": { + "message": "Delete folder" + }, + "deleteAttachment": { + "message": "Delete attachment" + }, + "deleteItemConfirmation": { + "message": "Do you really want to send to the trash?" + }, + "deletedItem": { + "message": "Item sent to trash" + }, + "overwritePasswordConfirmation": { + "message": "Are you sure you want to overwrite the current password?" + }, + "overwriteUsername": { + "message": "Overwrite username" + }, + "overwriteUsernameConfirmation": { + "message": "Are you sure you want to overwrite the current username?" + }, + "noneFolder": { + "message": "No folder", + "description": "This is the folder for uncategorized items" + }, + "addFolder": { + "message": "Add folder" + }, + "editFolder": { + "message": "Edit folder" + }, + "regeneratePassword": { + "message": "Regenerate password" + }, + "copyPassword": { + "message": "Copy password" + }, + "copyUri": { + "message": "Copy URI" + }, + "copyVerificationCodeTotp": { + "message": "Copy verification code (TOTP)" + }, + "length": { + "message": "Length" + }, + "uppercase": { + "message": "Uppercase (A-Z)" + }, + "lowercase": { + "message": "Lowercase (a-z)" + }, + "numbers": { + "message": "Numbers (0-9)" + }, + "specialCharacters": { + "message": "Special characters (!@#$%^&*)" + }, + "numWords": { + "message": "Number of words" + }, + "wordSeparator": { + "message": "Word separator" + }, + "capitalize": { + "message": "Capitalize", + "description": "Make the first letter of a word uppercase." + }, + "includeNumber": { + "message": "Include number" + }, + "close": { + "message": "Close" + }, + "minNumbers": { + "message": "Minimum numbers" + }, + "minSpecial": { + "message": "Minimum special", + "description": "Minimum Special Characters" + }, + "ambiguous": { + "message": "Avoid ambiguous characters" + }, + "searchCollection": { + "message": "Search collection" + }, + "searchFolder": { + "message": "Search folder" + }, + "searchFavorites": { + "message": "Search favorites" + }, + "searchType": { + "message": "Search type", + "description": "Search item type" + }, + "newAttachment": { + "message": "Add new attachment" + }, + "deletedAttachment": { + "message": "Attachment deleted" + }, + "deleteAttachmentConfirmation": { + "message": "Are you sure you want to delete this attachment?" + }, + "attachmentSaved": { + "message": "Attachment saved" + }, + "file": { + "message": "File" + }, + "selectFile": { + "message": "Select a file" + }, + "maxFileSize": { + "message": "Maximum file size is 500 MB." + }, + "updateKey": { + "message": "You cannot use this feature until you update your encryption key." + }, + "editedFolder": { + "message": "Folder saved" + }, + "addedFolder": { + "message": "Folder added" + }, + "deleteFolderConfirmation": { + "message": "Are you sure you want to delete this folder?" + }, + "deletedFolder": { + "message": "Folder deleted" + }, + "loginOrCreateNewAccount": { + "message": "Log in or create a new account to access your secure vault." + }, + "createAccount": { + "message": "Create account" + }, + "logIn": { + "message": "Log in" + }, + "submit": { + "message": "Submit" + }, + "masterPass": { + "message": "Master password" + }, + "masterPassDesc": { + "message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it." + }, + "masterPassHintDesc": { + "message": "A master password hint can help you remember your password if you forget it." + }, + "reTypeMasterPass": { + "message": "Re-type master password" + }, + "masterPassHint": { + "message": "Master password hint (optional)" + }, + "settings": { + "message": "Settings" + }, + "passwordHint": { + "message": "Password hint" + }, + "enterEmailToGetHint": { + "message": "Enter your account email address to receive your master password hint." + }, + "getMasterPasswordHint": { + "message": "Get master password hint" + }, + "emailRequired": { + "message": "Email address is required." + }, + "invalidEmail": { + "message": "Invalid email address." + }, + "masterPasswordRequired": { + "message": "Master password is required." + }, + "confirmMasterPasswordRequired": { + "message": "Master password retype is required." + }, + "masterPasswordMinlength": { + "message": "Master password must be at least 8 characters long." + }, + "masterPassDoesntMatch": { + "message": "Master password confirmation does not match." + }, + "newAccountCreated": { + "message": "Your new account has been created! You may now log in." + }, + "masterPassSent": { + "message": "We've sent you an email with your master password hint." + }, + "unexpectedError": { + "message": "An unexpected error has occurred." + }, + "itemInformation": { + "message": "Item information" + }, + "noItemsInList": { + "message": "There are no items to list." + }, + "sendVerificationCode": { + "message": "Send a verification code to your email" + }, + "sendCode": { + "message": "Send code" + }, + "codeSent": { + "message": "Code sent" + }, + "verificationCode": { + "message": "Verification code" + }, + "confirmIdentity": { + "message": "Confirm your identity to continue." + }, + "verificationCodeRequired": { + "message": "Verification code is required." + }, + "invalidVerificationCode": { + "message": "Invalid verification code" + }, + "continue": { + "message": "Continue" + }, + "enterVerificationCodeApp": { + "message": "Enter the 6 digit verification code from your authenticator app." + }, + "enterVerificationCodeEmail": { + "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", + "placeholders": { + "email": { + "content": "$1", + "example": "example@gmail.com" + } + } + }, + "verificationCodeEmailSent": { + "message": "Verification email sent to $EMAIL$.", + "placeholders": { + "email": { + "content": "$1", + "example": "example@gmail.com" + } + } + }, + "rememberMe": { + "message": "Remember me" + }, + "sendVerificationCodeEmailAgain": { + "message": "Send verification code email again" + }, + "useAnotherTwoStepMethod": { + "message": "Use another two-step login method" + }, + "insertYubiKey": { + "message": "Insert your YubiKey into your computer's USB port, then touch its button." + }, + "insertU2f": { + "message": "Insert your security key into your computer's USB port. If it has a button, touch it." + }, + "recoveryCodeDesc": { + "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers on your account." + }, + "recoveryCodeTitle": { + "message": "Recovery code" + }, + "authenticatorAppTitle": { + "message": "Authenticator app" + }, + "authenticatorAppDesc": { + "message": "Use an authenticator app (such as Authy or Google Authenticator) to generate time-based verification codes.", + "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." + }, + "yubiKeyTitle": { + "message": "YubiKey OTP security key" + }, + "yubiKeyDesc": { + "message": "Use a YubiKey to access your account. Works with YubiKey 4, 4 Nano, 4C, and NEO devices." + }, + "duoDesc": { + "message": "Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.", + "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." + }, + "duoOrganizationDesc": { + "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", + "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." + }, + "webAuthnTitle": { + "message": "FIDO2 WebAuthn" + }, + "webAuthnDesc": { + "message": "Use any WebAuthn compatible security key to access your account." + }, + "emailTitle": { + "message": "Email" + }, + "emailDesc": { + "message": "Verification codes will be emailed to you." + }, + "loginUnavailable": { + "message": "Login unavailable" + }, + "noTwoStepProviders": { + "message": "This account has two-step login set up, however, none of the configured two-step providers are supported by this device." + }, + "noTwoStepProviders2": { + "message": "Please add additional providers that are better supported across devices (such as an authenticator app)." + }, + "twoStepOptions": { + "message": "Two-step login options" + }, + "selfHostedEnvironment": { + "message": "Self-hosted environment" + }, + "selfHostedEnvironmentFooter": { + "message": "Specify the base URL of your on-premises hosted Bitwarden installation." + }, + "customEnvironment": { + "message": "Custom environment" + }, + "customEnvironmentFooter": { + "message": "For advanced users. You can specify the base URL of each service independently." + }, + "baseUrl": { + "message": "Server URL" + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, + "ok": { + "message": "Ok" + }, + "yes": { + "message": "Yes" + }, + "no": { + "message": "No" + }, + "overwritePassword": { + "message": "Overwrite password" + }, + "learnMore": { + "message": "Learn more" + }, + "featureUnavailable": { + "message": "Feature unavailable" + }, + "loggedOut": { + "message": "Logged out" + }, + "loginExpired": { + "message": "Your login session has expired." + }, + "logOutConfirmation": { + "message": "Are you sure you want to log out?" + }, + "logOut": { + "message": "Log out" + }, + "addNewLogin": { + "message": "New login" + }, + "addNewItem": { + "message": "New item" + }, + "addNewFolder": { + "message": "New folder" + }, + "view": { + "message": "View" + }, + "account": { + "message": "Account" + }, + "loading": { + "message": "Loading..." + }, + "lockVault": { + "message": "Lock vault" + }, + "passwordGenerator": { + "message": "Password generator" + }, + "contactUs": { + "message": "Contact us" + }, + "getHelp": { + "message": "Get help" + }, + "fileBugReport": { + "message": "File a bug report" + }, + "blog": { + "message": "Blog" + }, + "followUs": { + "message": "Follow us" + }, + "syncVault": { + "message": "Sync vault" + }, + "changeMasterPass": { + "message": "Change master password" + }, + "changeMasterPasswordConfirmation": { + "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "fingerprintPhrase": { + "message": "Fingerprint phrase", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "yourAccountsFingerprint": { + "message": "Your account's fingerprint phrase", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "goToWebVault": { + "message": "Go to web vault" + }, + "getMobileApp": { + "message": "Get mobile app" + }, + "getBrowserExtension": { + "message": "Get browser extension" + }, + "syncingComplete": { + "message": "Syncing complete" + }, + "syncingFailed": { + "message": "Syncing failed" + }, + "yourVaultIsLocked": { + "message": "Your vault is locked. Verify your identity to continue." + }, + "unlock": { + "message": "Unlock" + }, + "loggedInAsOn": { + "message": "Logged in as $EMAIL$ on $HOSTNAME$.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "hostname": { + "content": "$2", + "example": "bitwarden.com" + } + } + }, + "invalidMasterPassword": { + "message": "Invalid master password" + }, + "twoStepLoginConfirmation": { + "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "twoStepLogin": { + "message": "Two-step login" + }, + "vaultTimeout": { + "message": "Vault timeout" + }, + "vaultTimeoutDesc": { + "message": "Choose when your vault will take the vault timeout action." + }, + "immediately": { + "message": "Immediately" + }, + "tenSeconds": { + "message": "10 seconds" + }, + "twentySeconds": { + "message": "20 seconds" + }, + "thirtySeconds": { + "message": "30 seconds" + }, + "oneMinute": { + "message": "1 minute" + }, + "twoMinutes": { + "message": "2 minutes" + }, + "fiveMinutes": { + "message": "5 minutes" + }, + "fifteenMinutes": { + "message": "15 minutes" + }, + "thirtyMinutes": { + "message": "30 minutes" + }, + "oneHour": { + "message": "1 hour" + }, + "fourHours": { + "message": "4 hours" + }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, + "onLocked": { + "message": "On system lock" + }, + "onRestart": { + "message": "On restart" + }, + "never": { + "message": "Never" + }, + "security": { + "message": "Security" + }, + "clearClipboard": { + "message": "Clear clipboard", + "description": "Clipboard is the operating system thing where you copy/paste data to on your device." + }, + "clearClipboardDesc": { + "message": "Automatically clear copied values from your clipboard.", + "description": "Clipboard is the operating system thing where you copy/paste data to on your device." + }, + "enableFavicon": { + "message": "Show website icons" + }, + "faviconDesc": { + "message": "Show a recognizable image next to each login." + }, + "enableMinToTray": { + "message": "Minimize to tray icon" + }, + "enableMinToTrayDesc": { + "message": "When minimizing the window, show an icon in the system tray instead." + }, + "enableMinToMenuBar": { + "message": "Minimize to menu bar" + }, + "enableMinToMenuBarDesc": { + "message": "When minimizing the window, show an icon in the menu bar instead." + }, + "enableCloseToTray": { + "message": "Close to tray icon" + }, + "enableCloseToTrayDesc": { + "message": "When closing the window, show an icon in the system tray instead." + }, + "enableCloseToMenuBar": { + "message": "Close to menu bar" + }, + "enableCloseToMenuBarDesc": { + "message": "When closing the window, show an icon in the menu bar instead." + }, + "enableTray": { + "message": "Show tray icon" + }, + "enableTrayDesc": { + "message": "Always show an icon in the system tray." + }, + "startToTray": { + "message": "Start to tray icon" + }, + "startToTrayDesc": { + "message": "When the application is first started, only show an icon in the system tray." + }, + "startToMenuBar": { + "message": "Start to menu bar" + }, + "startToMenuBarDesc": { + "message": "When the application is first started, only show an icon in the menu bar." + }, + "openAtLogin": { + "message": "Start automatically on login" + }, + "openAtLoginDesc": { + "message": "Start the Bitwarden desktop application automatically on login." + }, + "alwaysShowDock": { + "message": "Always show in the Dock" + }, + "alwaysShowDockDesc": { + "message": "Show the Bitwarden icon in the Dock even when minimized to the menu bar." + }, + "confirmTrayTitle": { + "message": "Confirm hiding tray" + }, + "confirmTrayDesc": { + "message": "Turning off this setting will also turn off all other tray related settings." + }, + "language": { + "message": "Language" + }, + "languageDesc": { + "message": "Change the language used by the application. Restart is required." + }, + "theme": { + "message": "Theme" + }, + "themeDesc": { + "message": "Change the application's color theme." + }, + "dark": { + "message": "Dark", + "description": "Dark color" + }, + "light": { + "message": "Light", + "description": "Light color" + }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, + "checkForUpdates": { + "message": "Check for updates…" + }, + "version": { + "message": "Version $VERSION_NUM$", + "placeholders": { + "version_num": { + "content": "$1", + "example": "1.2.3" + } + } + }, + "restartToUpdate": { + "message": "Restart to update" + }, + "restartToUpdateDesc": { + "message": "Version $VERSION_NUM$ is ready to install. You must restart the application to complete the installation. Do you want to restart and update now?", + "placeholders": { + "version_num": { + "content": "$1", + "example": "1.2.3" + } + } + }, + "updateAvailable": { + "message": "Update available" + }, + "updateAvailableDesc": { + "message": "An update was found. Do you want to download it now?" + }, + "restart": { + "message": "Restart" + }, + "later": { + "message": "Later" + }, + "noUpdatesAvailable": { + "message": "No updates are currently available. You are using the latest version." + }, + "updateError": { + "message": "Update error" + }, + "unknown": { + "message": "Unknown" + }, + "copyUsername": { + "message": "Copy username" + }, + "copyNumber": { + "message": "Copy number", + "description": "Copy credit card number" + }, + "copySecurityCode": { + "message": "Copy security code", + "description": "Copy credit card security code (CVV)" + }, + "premiumMembership": { + "message": "Premium membership" + }, + "premiumManage": { + "message": "Manage membership" + }, + "premiumManageAlert": { + "message": "You can manage your membership on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "premiumRefresh": { + "message": "Refresh membership" + }, + "premiumNotCurrentMember": { + "message": "You are not currently a Premium member." + }, + "premiumSignUpAndGet": { + "message": "Sign up for a Premium membership and get:" + }, + "premiumSignUpStorage": { + "message": "1 GB encrypted storage for file attachments." + }, + "premiumSignUpTwoStep": { + "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + }, + "premiumSignUpReports": { + "message": "Password hygiene, account health, and data breach reports to keep your vault safe." + }, + "premiumSignUpTotp": { + "message": "TOTP verification code (2FA) generator for logins in your vault." + }, + "premiumSignUpSupport": { + "message": "Priority customer support." + }, + "premiumSignUpFuture": { + "message": "All future premium features. More coming soon!" + }, + "premiumPurchase": { + "message": "Purchase Premium" + }, + "premiumPurchaseAlert": { + "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "premiumCurrentMember": { + "message": "You are a premium member!" + }, + "premiumCurrentMemberThanks": { + "message": "Thank you for supporting Bitwarden." + }, + "premiumPrice": { + "message": "All for just $PRICE$ /year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, + "refreshComplete": { + "message": "Refresh complete" + }, + "passwordHistory": { + "message": "Password history" + }, + "clear": { + "message": "Clear", + "description": "To clear something out. example: To clear browser history." + }, + "noPasswordsInList": { + "message": "There are no passwords to list." + }, + "undo": { + "message": "Undo" + }, + "redo": { + "message": "Redo" + }, + "cut": { + "message": "Cut", + "description": "Cut to clipboard" + }, + "paste": { + "message": "Paste", + "description": "Paste from clipboard" + }, + "selectAll": { + "message": "Select all" + }, + "zoomIn": { + "message": "Zoom in" + }, + "zoomOut": { + "message": "Zoom out" + }, + "resetZoom": { + "message": "Reset zoom" + }, + "toggleFullScreen": { + "message": "Toggle full screen" + }, + "reload": { + "message": "Reload" + }, + "toggleDevTools": { + "message": "Toggle developer tools" + }, + "minimize": { + "message": "Minimize", + "description": "Minimize window" + }, + "zoom": { + "message": "Zoom" + }, + "bringAllToFront": { + "message": "Bring all to front", + "description": "Bring all windows to front (foreground)" + }, + "aboutBitwarden": { + "message": "About Bitwarden" + }, + "services": { + "message": "Services" + }, + "hideBitwarden": { + "message": "Hide Bitwarden" + }, + "hideOthers": { + "message": "Hide others" + }, + "showAll": { + "message": "Show all" + }, + "quitBitwarden": { + "message": "Quit Bitwarden" + }, + "valueCopied": { + "message": "$VALUE$ copied", + "description": "Value has been copied to the clipboard.", + "placeholders": { + "value": { + "content": "$1", + "example": "Password" + } + } + }, + "help": { + "message": "Help" + }, + "window": { + "message": "Window" + }, + "checkPassword": { + "message": "Check if password has been exposed." + }, + "passwordExposed": { + "message": "This password has been exposed $VALUE$ time(s) in data breaches. You should change it.", + "placeholders": { + "value": { + "content": "$1", + "example": "2" + } + } + }, + "passwordSafe": { + "message": "This password was not found in any known data breaches. It should be safe to use." + }, + "baseDomain": { + "message": "Base domain", + "description": "Domain name. Ex. website.com" + }, + "domainName": { + "message": "Domain name", + "description": "Domain name. Ex. website.com" + }, + "host": { + "message": "Host", + "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." + }, + "exact": { + "message": "Exact" + }, + "startsWith": { + "message": "Starts with" + }, + "regEx": { + "message": "Regular expression", + "description": "A programming term, also known as 'RegEx'." + }, + "matchDetection": { + "message": "Match detection", + "description": "URI match detection for auto-fill." + }, + "defaultMatchDetection": { + "message": "Default match detection", + "description": "Default URI match detection for auto-fill." + }, + "toggleOptions": { + "message": "Toggle options" + }, + "organization": { + "message": "Organization", + "description": "An entity of multiple related people (ex. a team or business organization)." + }, + "default": { + "message": "Default" + }, + "exit": { + "message": "Exit" + }, + "showHide": { + "message": "Show / Hide", + "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": "Hide to tray" + }, + "alwaysOnTop": { + "message": "Always on top", + "description": "Application window should always stay on top of other windows" + }, + "dateUpdated": { + "message": "Updated", + "description": "ex. Date this item was updated" + }, + "dateCreated": { + "message": "Created", + "description": "ex. Date this item was created" + }, + "datePasswordUpdated": { + "message": "Password updated", + "description": "ex. Date this password was updated" + }, + "exportVault": { + "message": "Export vault" + }, + "fileFormat": { + "message": "File format" + }, + "hCaptchaUrl": { + "message": "hCaptcha Url", + "description": "hCaptcha is the name of a website, should not be translated" + }, + "loadAccessibilityCookie": { + "message": "Load accessibility cookie" + }, + "registerAccessibilityUser": { + "message": "Register as an accessibility user at", + "description": "ex. Register as an accessibility user at hcaptcha.com" + }, + "copyPasteLink": { + "message": "Copy and paste the link sent to your email below" + }, + "enterhCaptchaUrl": { + "message": "Enter URL to load accessibility cookie for hCaptcha", + "description": "hCaptcha is the name of a website, should not be translated" + }, + "hCaptchaUrlRequired": { + "message": "hCaptcha Url is required", + "description": "hCaptcha is the name of a website, should not be translated" + }, + "invalidUrl": { + "message": "Invalid Url" + }, + "done": { + "message": "Done" + }, + "accessibilityCookieSaved": { + "message": "Accessibility cookie saved!" + }, + "noAccessibilityCookieSaved": { + "message": "No accessibility cookie saved" + }, + "warning": { + "message": "WARNING", + "description": "WARNING (should stay in capitalized letters if the language permits)" + }, + "confirmVaultExport": { + "message": "Confirm vault export" + }, + "exportWarningDesc": { + "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + }, + "encExportKeyWarningDesc": { + "message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." + }, + "encExportAccountWarningDesc": { + "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + }, + "noOrganizationsList": { + "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." + }, + "noCollectionsInList": { + "message": "There are no collections to list." + }, + "ownership": { + "message": "Ownership" + }, + "whoOwnsThisItem": { + "message": "Who owns this item?" + }, + "strong": { + "message": "Strong", + "description": "ex. A strong password. Scale: Weak -> Good -> Strong" + }, + "good": { + "message": "Good", + "description": "ex. A good password. Scale: Weak -> Good -> Strong" + }, + "weak": { + "message": "Weak", + "description": "ex. A weak password. Scale: Weak -> Good -> Strong" + }, + "weakMasterPassword": { + "message": "Weak master password" + }, + "weakMasterPasswordDesc": { + "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + }, + "pin": { + "message": "PIN", + "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." + }, + "unlockWithPin": { + "message": "Unlock with PIN" + }, + "setYourPinCode": { + "message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application." + }, + "pinRequired": { + "message": "PIN code is required." + }, + "invalidPin": { + "message": "Invalid PIN code." + }, + "unlockWithWindowsHello": { + "message": "Unlock with Windows Hello" + }, + "windowsHelloConsentMessage": { + "message": "Verify for Bitwarden." + }, + "unlockWithTouchId": { + "message": "Unlock with Touch ID" + }, + "touchIdConsentMessage": { + "message": "unlock your vault" + }, + "autoPromptWindowsHello": { + "message": "Ask for Windows Hello on launch" + }, + "autoPromptTouchId": { + "message": "Ask for Touch ID on launch" + }, + "lockWithMasterPassOnRestart": { + "message": "Lock with master password on restart" + }, + "deleteAccount": { + "message": "Delete account" + }, + "deleteAccountDesc": { + "message": "Proceed below to delete your account and all vault data." + }, + "deleteAccountWarning": { + "message": "Deleting your account is permanent. It cannot be undone." + }, + "accountDeleted": { + "message": "Account deleted" + }, + "accountDeletedDesc": { + "message": "Your account has been closed and all associated data has been deleted." + }, + "preferences": { + "message": "Preferences" + }, + "enableMenuBar": { + "message": "Show menu bar icon" + }, + "enableMenuBarDesc": { + "message": "Always show an icon in the menu bar." + }, + "hideToMenuBar": { + "message": "Hide to menu bar" + }, + "selectOneCollection": { + "message": "You must select at least one collection." + }, + "premiumUpdated": { + "message": "You've upgraded to Premium." + }, + "restore": { + "message": "Restore" + }, + "premiumManageAlertAppStore": { + "message": "You can manage your subscription from the App Store. Do you want to visit the App Store now?" + }, + "legal": { + "message": "Legal", + "description": "Noun. As in 'legal documents', like our terms of service and privacy policy." + }, + "termsOfService": { + "message": "Terms of Service" + }, + "privacyPolicy": { + "message": "Privacy Policy" + }, + "unsavedChangesConfirmation": { + "message": "Are you sure you want to leave? If you leave now then your current information will not be saved." + }, + "unsavedChangesTitle": { + "message": "Unsaved changes" + }, + "clone": { + "message": "Clone" + }, + "passwordGeneratorPolicyInEffect": { + "message": "One or more organization policies are affecting your generator settings." + }, + "vaultTimeoutAction": { + "message": "Vault timeout action" + }, + "vaultTimeoutActionLockDesc": { + "message": "Master password or other unlock method is required to access your vault again." + }, + "vaultTimeoutActionLogOutDesc": { + "message": "Re-authentication is required to access your vault again." + }, + "lock": { + "message": "Lock", + "description": "Verb form: to make secure or inaccesible by" + }, + "trash": { + "message": "Trash", + "description": "Noun: a special folder to hold deleted items" + }, + "searchTrash": { + "message": "Search trash" + }, + "permanentlyDeleteItem": { + "message": "Permanently delete item" + }, + "permanentlyDeleteItemConfirmation": { + "message": "Are you sure you want to permanently delete this item?" + }, + "permanentlyDeletedItem": { + "message": "Item permanently deleted" + }, + "restoreItem": { + "message": "Restore item" + }, + "restoreItemConfirmation": { + "message": "Are you sure you want to restore this item?" + }, + "restoredItem": { + "message": "Item restored" + }, + "permanentlyDelete": { + "message": "Permanently delete" + }, + "vaultTimeoutLogOutConfirmation": { + "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" + }, + "vaultTimeoutLogOutConfirmationTitle": { + "message": "Timeout action confirmation" + }, + "enterpriseSingleSignOn": { + "message": "Enterprise single sign-on" + }, + "setMasterPassword": { + "message": "Set master password" + }, + "ssoCompleteRegistration": { + "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." + }, + "newMasterPass": { + "message": "New master password" + }, + "confirmNewMasterPass": { + "message": "Confirm new master password" + }, + "masterPasswordPolicyInEffect": { + "message": "One or more organization policies require your master password to meet the following requirements:" + }, + "policyInEffectMinComplexity": { + "message": "Minimum complexity score of $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, + "policyInEffectMinLength": { + "message": "Minimum length of $LENGTH$", + "placeholders": { + "length": { + "content": "$1", + "example": "14" + } + } + }, + "policyInEffectUppercase": { + "message": "Contain one or more uppercase characters" + }, + "policyInEffectLowercase": { + "message": "Contain one or more lowercase characters" + }, + "policyInEffectNumbers": { + "message": "Contain one or more numbers" + }, + "policyInEffectSpecial": { + "message": "Contain one or more of the following special characters $CHARS$", + "placeholders": { + "chars": { + "content": "$1", + "example": "!@#$%^&*" + } + } + }, + "masterPasswordPolicyRequirementsNotMet": { + "message": "Your new master password does not meet the policy requirements." + }, + "acceptPolicies": { + "message": "By checking this box you agree to the following:" + }, + "acceptPoliciesRequired": { + "message": "Terms of Service and Privacy Policy have not been acknowledged." + }, + "enableBrowserIntegration": { + "message": "Allow browser integration" + }, + "enableBrowserIntegrationDesc": { + "message": "Used for biometrics in browser." + }, + "enableDuckDuckGoBrowserIntegration": { + "message": "Allow DuckDuckGo browser integration" + }, + "enableDuckDuckGoBrowserIntegrationDesc": { + "message": "Use your Bitwarden vault when browsing with DuckDuckGo." + }, + "browserIntegrationUnsupportedTitle": { + "message": "Browser integration not supported" + }, + "browserIntegrationMasOnlyDesc": { + "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." + }, + "browserIntegrationWindowsStoreDesc": { + "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." + }, + "browserIntegrationLinuxDesc": { + "message": "Unfortunately browser integration is currently not supported in the linux version." + }, + "enableBrowserIntegrationFingerprint": { + "message": "Require verification for browser integration" + }, + "enableBrowserIntegrationFingerprintDesc": { + "message": "Add an additional layer of security by requiring fingerprint phrase confirmation when establishing a link between your desktop and browser. This requires user action and verification each time a connection is created." + }, + "approve": { + "message": "Approve" + }, + "verifyBrowserTitle": { + "message": "Verify browser connection" + }, + "verifyBrowserDesc": { + "message": "Please ensure the shown fingerprint is identical to the fingerprint showed in the browser extension." + }, + "verifyNativeMessagingConnectionTitle": { + "message": "$APPID$ wants to connect to Bitwarden", + "placeholders": { + "appid": { + "content": "$1", + "example": "My App" + } + } + }, + "verifyNativeMessagingConnectionDesc": { + "message": "Would you like to approve this request?" + }, + "verifyNativeMessagingConnectionWarning": { + "message": "If you did not initiate this request, do not approve it." + }, + "biometricsNotEnabledTitle": { + "message": "Biometrics not set up" + }, + "biometricsNotEnabledDesc": { + "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." + }, + "personalOwnershipSubmitError": { + "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." + }, + "hintEqualsPassword": { + "message": "Your password hint cannot be the same as your password." + }, + "personalOwnershipPolicyInEffect": { + "message": "An organization policy is affecting your ownership options." + }, + "allSends": { + "message": "All Sends", + "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeFile": { + "message": "File" + }, + "sendTypeText": { + "message": "Text" + }, + "searchSends": { + "message": "Search Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editSend": { + "message": "Edit Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "myVault": { + "message": "My vault" + }, + "text": { + "message": "Text" + }, + "deletionDate": { + "message": "Deletion date" + }, + "deletionDateDesc": { + "message": "The Send will be permanently deleted on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "expirationDate": { + "message": "Expiration date" + }, + "expirationDateDesc": { + "message": "If set, access to this Send will expire on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "maxAccessCount": { + "message": "Maximum access count", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, + "maxAccessCountDesc": { + "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "currentAccessCount": { + "message": "Current access count" + }, + "disableSend": { + "message": "Deactivate this Send so that no one can access it.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendPasswordDesc": { + "message": "Optionally require a password for users to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendNotesDesc": { + "message": "Private notes about this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendLink": { + "message": "Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendLinkLabel": { + "message": "Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "textHiddenByDefault": { + "message": "When accessing the Send, hide the text by default", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "createdSend": { + "message": "Send added", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editedSend": { + "message": "Send saved", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deletedSend": { + "message": "Send deleted", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "newPassword": { + "message": "New password" + }, + "whatTypeOfSend": { + "message": "What type of Send is this?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "createSend": { + "message": "New Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTextDesc": { + "message": "The text you want to send." + }, + "sendFileDesc": { + "message": "The file you want to send." + }, + "days": { + "message": "$DAYS$ days", + "placeholders": { + "days": { + "content": "$1", + "example": "1" + } + } + }, + "oneDay": { + "message": "1 day" + }, + "custom": { + "message": "Custom" + }, + "deleteSendConfirmation": { + "message": "Are you sure you want to delete this Send?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "copySendLinkToClipboard": { + "message": "Copy Send link to clipboard", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "copySendLinkOnSave": { + "message": "Copy the link to share this Send to my clipboard upon save." + }, + "sendDisabled": { + "message": "Send removed", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendDisabledWarning": { + "message": "Due to an enterprise policy, you are only able to delete an existing Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "copyLink": { + "message": "Copy link" + }, + "disabled": { + "message": "Disabled" + }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, + "maxAccessCountReached": { + "message": "Max access count reached" + }, + "expired": { + "message": "Expired" + }, + "pendingDeletion": { + "message": "Pending deletion" + }, + "webAuthnAuthenticate": { + "message": "Authenticate WebAuthn" + }, + "hideEmail": { + "message": "Hide my email address from recipients." + }, + "sendOptionsPolicyInEffect": { + "message": "One or more organization policies are affecting your Send options." + }, + "emailVerificationRequired": { + "message": "Email verification required" + }, + "emailVerificationRequiredDesc": { + "message": "You must verify your email to use this feature." + }, + "passwordPrompt": { + "message": "Master password re-prompt" + }, + "passwordConfirmation": { + "message": "Master password confirmation" + }, + "passwordConfirmationDesc": { + "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + }, + "updatedMasterPassword": { + "message": "Updated master password" + }, + "updateMasterPassword": { + "message": "Update master password" + }, + "updateMasterPasswordWarning": { + "message": "Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update it now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + }, + "hours": { + "message": "Hours" + }, + "minutes": { + "message": "Minutes" + }, + "vaultTimeoutPolicyInEffect": { + "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "resetPasswordPolicyAutoEnroll": { + "message": "Automatic enrollment" + }, + "resetPasswordAutoEnrollInviteWarning": { + "message": "This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + }, + "vaultExportDisabled": { + "message": "Vault export removed" + }, + "personalVaultExportPolicyInEffect": { + "message": "One or more organization policies prevents you from exporting your personal vault." + }, + "addAccount": { + "message": "Add account" + }, + "removeMasterPassword": { + "message": "Remove master password" + }, + "removedMasterPassword": { + "message": "Master password removed" + }, + "convertOrganizationEncryptionDesc": { + "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "leaveOrganization": { + "message": "Leave organization" + }, + "leaveOrganizationConfirmation": { + "message": "Are you sure you want to leave this organization?" + }, + "leftOrganization": { + "message": "You have left the organization." + }, + "ssoKeyConnectorError": { + "message": "Key connector error: make sure key connector is available and working correctly." + }, + "lockAllVaults": { + "message": "Lock all vaults" + }, + "accountLimitReached": { + "message": "No more than 5 accounts may be logged in at the same time." + }, + "accountPreferences": { + "message": "Preferences" + }, + "appPreferences": { + "message": "App settings (all accounts)" + }, + "accountSwitcherLimitReached": { + "message": "Account limit reached. Log out of an account to add another." + }, + "settingsTitle": { + "message": "App settings for $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "jdoe@example.com" + } + } + }, + "switchAccount": { + "message": "Switch account" + }, + "options": { + "message": "Options" + }, + "sessionTimeout": { + "message": "Your session has timed out. Please go back and try logging in again." + }, + "exportingPersonalVaultTitle": { + "message": "Exporting individual vault" + }, + "exportingPersonalVaultDescription": { + "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "locked": { + "message": "Locked" + }, + "unlocked": { + "message": "Unlocked" + }, + "generator": { + "message": "Generator" + }, + "whatWouldYouLikeToGenerate": { + "message": "What would you like to generate?" + }, + "passwordType": { + "message": "Password type" + }, + "regenerateUsername": { + "message": "Regenerate username" + }, + "generateUsername": { + "message": "Generate username" + }, + "usernameType": { + "message": "Username type" + }, + "plusAddressedEmail": { + "message": "Plus addressed email", + "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" + }, + "plusAddressedEmailDesc": { + "message": "Use your email provider's sub-addressing capabilities." + }, + "catchallEmail": { + "message": "Catch-all email" + }, + "catchallEmailDesc": { + "message": "Use your domain's configured catch-all inbox." + }, + "random": { + "message": "Random" + }, + "randomWord": { + "message": "Random word" + }, + "websiteName": { + "message": "Website name" + }, + "service": { + "message": "Service" + }, + "allVaults": { + "message": "All vaults" + }, + "searchOrganization": { + "message": "Search organization" + }, + "searchMyVault": { + "message": "Search my vault" + }, + "forwardedEmail": { + "message": "Forwarded email alias" + }, + "forwardedEmailDesc": { + "message": "Generate an email alias with an external forwarding service." + }, + "hostname": { + "message": "Hostname", + "description": "Part of a URL." + }, + "apiAccessToken": { + "message": "API Access Token" + }, + "apiKey": { + "message": "API key" + }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, + "organizationIsDisabled": { + "message": "Organization suspended" + }, + "disabledOrganizationFilterError": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "cardBrandMir": { + "message": "Mir" + }, + "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" + }, + "toggleCharacterCount": { + "message": "Toggle character count", + "description": "'Character count' describes a feature that displays a number next to each character of the password." + } +} diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 9ec147800bc..267fdf4b438 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -2059,7 +2059,7 @@ "message": "Başka bir cihazla giriş yap" }, "toggleCharacterCount": { - "message": "Karakter sayısını değiştir", + "message": "Karakter sayacını aç/kapat", "description": "'Character count' describes a feature that displays a number next to each character of the password." } } From dfd2a6f1056a7d3f3f91abdf71e3e9507ae728f1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Jan 2023 11:33:13 +0100 Subject: [PATCH 165/205] Autosync the updated translations (#4461) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 16 + apps/web/src/locales/ar/messages.json | 24 +- apps/web/src/locales/az/messages.json | 22 +- apps/web/src/locales/be/messages.json | 22 +- apps/web/src/locales/bg/messages.json | 22 +- apps/web/src/locales/bn/messages.json | 16 + apps/web/src/locales/bs/messages.json | 16 + apps/web/src/locales/ca/messages.json | 26 +- apps/web/src/locales/cs/messages.json | 36 +- apps/web/src/locales/da/messages.json | 22 +- apps/web/src/locales/de/messages.json | 18 +- apps/web/src/locales/el/messages.json | 18 +- apps/web/src/locales/en_GB/messages.json | 18 +- apps/web/src/locales/en_IN/messages.json | 18 +- apps/web/src/locales/eo/messages.json | 16 + apps/web/src/locales/es/messages.json | 32 +- apps/web/src/locales/et/messages.json | 16 + apps/web/src/locales/eu/messages.json | 16 + apps/web/src/locales/fa/messages.json | 2952 +++++------ apps/web/src/locales/fi/messages.json | 36 +- apps/web/src/locales/fil/messages.json | 16 + apps/web/src/locales/fr/messages.json | 304 +- apps/web/src/locales/he/messages.json | 18 +- apps/web/src/locales/hi/messages.json | 16 + apps/web/src/locales/hr/messages.json | 18 +- apps/web/src/locales/hu/messages.json | 96 +- apps/web/src/locales/id/messages.json | 128 +- apps/web/src/locales/it/messages.json | 18 +- apps/web/src/locales/ja/messages.json | 56 +- apps/web/src/locales/ka/messages.json | 16 + apps/web/src/locales/km/messages.json | 16 + apps/web/src/locales/kn/messages.json | 16 + apps/web/src/locales/ko/messages.json | 16 + apps/web/src/locales/lv/messages.json | 56 +- apps/web/src/locales/ml/messages.json | 18 +- apps/web/src/locales/nb/messages.json | 16 + apps/web/src/locales/ne/messages.json | 5892 ++++++++++++++++++++++ apps/web/src/locales/nl/messages.json | 44 +- apps/web/src/locales/nn/messages.json | 16 + apps/web/src/locales/pl/messages.json | 16 + apps/web/src/locales/pt_BR/messages.json | 18 +- apps/web/src/locales/pt_PT/messages.json | 18 +- apps/web/src/locales/ro/messages.json | 16 + apps/web/src/locales/ru/messages.json | 26 +- apps/web/src/locales/si/messages.json | 16 + apps/web/src/locales/sk/messages.json | 22 +- apps/web/src/locales/sl/messages.json | 16 + apps/web/src/locales/sr/messages.json | 16 + apps/web/src/locales/sr_CS/messages.json | 16 + apps/web/src/locales/sv/messages.json | 18 +- apps/web/src/locales/te/messages.json | 16 + apps/web/src/locales/th/messages.json | 16 + apps/web/src/locales/tr/messages.json | 22 +- apps/web/src/locales/uk/messages.json | 16 + apps/web/src/locales/vi/messages.json | 42 +- apps/web/src/locales/zh_CN/messages.json | 22 +- apps/web/src/locales/zh_TW/messages.json | 16 + 57 files changed, 8637 insertions(+), 1849 deletions(-) create mode 100644 apps/web/src/locales/ne/messages.json diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 6e4e3ac2029..2489fad610b 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Toevoegings" }, diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index cedda563116..32038152a23 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -398,7 +398,7 @@ } }, "copyValue": { - "message": "Copy value", + "message": "نسخ القيمة", "description": "Copy value to clipboard" }, "copyPassword": { @@ -660,7 +660,7 @@ "message": "تم إنشاء حساب جديد لك! بإمكانك الآن تسجيل الدخول." }, "trialAccountCreated": { - "message": "Account created successfully." + "message": "تم إنشاء الحساب بنجاح." }, "masterPassSent": { "message": "We've sent you an email with your master password hint." @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, @@ -2426,7 +2442,7 @@ "message": "This user can access only the selected collections." }, "search": { - "message": "Search" + "message": "بحث" }, "invited": { "message": "Invited" @@ -3125,7 +3141,7 @@ "message": "Account holder name" }, "bankAccountType": { - "message": "Account type" + "message": "نوع الحساب" }, "bankAccountTypeCompany": { "message": "Company (business)" diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index ff5cee669f3..3dd932819e6 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Sadəcə $PRICE$ qiymətinə illik premium-a abunə olun və ya Bitwarden Ailələr planı ilə $FAMILYPLANUSERCOUNT$ istifadəçiləri üçün premium hesablar və ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "limitsiz ailə paylaşımı əldə edin." + }, "addons": { "message": "Əlavələr" }, @@ -5562,13 +5578,13 @@ "message": "\"Sirr\"ləri sil" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Sirrin əlaqələndiriləcəyi layihələri seçin. Yalnız bu layihələrə müraciəti olan təşkilat istifadəçiləri sirri görə biləcək." }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "Layihələri yazın və ya seçin" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "Layihə yazın və ya seçin" }, "project": { "message": "Layihə" diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 61a4eca954c..37e7c317462 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Перайдзіце на прэміяльны тарыфны план усяго за $PRICE$/год або атрымайце прэміяльныя ўліковыя запісы для $FAMILYPLANUSERCOUNT$ карыстальнікаў і неабмежаванае сямейнае абагуленне з ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Тарыфны план Bitwarden Families." + }, "addons": { "message": "Дадаткі" }, @@ -5562,13 +5578,13 @@ "message": "Выдаліць сакрэты" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Выберыце праекты з якімі будзе звязаны сакрэт. Толькі карыстальнікі арганізацыі з доступам да гэтых праектаў змогуць бачыць сакрэт." }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "Увядзіце або выберыце праекты" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "Увядзіце або выберыце праект" }, "project": { "message": "Праект" diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 40b994dc131..087bafdb7d2 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Преминете към платен план само за $PRICE$ /година, или към групов план за $FAMILYPLANUSERCOUNT$ потребители и неограничено семейно споделяне със ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Семейния план на Битуорден." + }, "addons": { "message": "Добавки" }, @@ -5562,13 +5578,13 @@ "message": "Изтриване на тайните" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Изберете проектите, с които ще бъде свързана тази тайна. Само потребителите в организацията, които имат достъп до тези проекти, ще могат да виждат тайната." }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "Пишете тук или изберете проекти" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "Пишете тук или изберете проект" }, "project": { "message": "Проект" diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index cc7422ea781..2a46e4b6a0b 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 17f3bba7255..fdc96828cc6 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index a2b3778d8fa..1a2dc7eee01 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1221,7 +1221,7 @@ "message": "Preferències" }, "preferencesDesc": { - "message": "Personalitzeu la vostra experiència de caixa forta web." + "message": "Personalitzeu l'experiència de la caixa forta web." }, "preferencesUpdated": { "message": "Preferències guardades" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Hazte Premium por sólo $PRICE$ /año, u obtén cuentas Premium para usuarios $FAMILYPLANUSERCOUNT$ y uso compartido familiar ilimitado con un ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Plan Familias Bitwarden." + }, "addons": { "message": "Complements" }, @@ -3564,7 +3580,7 @@ "message": "Preferència d'usuari" }, "vaultTimeoutAction": { - "message": "Acció del temps d'espera de la caixa forta" + "message": "Acció quan acabe el temps d'espera de la caixa forta" }, "vaultTimeoutActionLockDesc": { "message": "Una caixa forta bloquejada requereix que torne a introduir la contrasenya principal per accedir-ne de nou." @@ -5562,13 +5578,13 @@ "message": "Suprimeix els secrets" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Selecciona los proyectos a los que se asociará el secreto. Sólo los usuarios de la organización con acceso a estos proyectos podrán verlo." }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "Teclea o selecciona proyectos" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "Teclea o selecciona proyecto" }, "project": { "message": "Projecte" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 3048dfd16c8..62198e59c47 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -1553,11 +1553,11 @@ "message": "Hlášení" }, "reportsDesc": { - "message": "Identify and close security gaps in your online accounts by clicking the reports below.", + "message": "Kliknutím na níže uvedené přehledy zjistěte a odstraňte nedostatky v zabezpečení svých online účtů.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "orgsReportsDesc": { - "message": "Identify and close security gaps in your organization's accounts by clicking the reports below.", + "message": "Kliknutím na níže uvedené přehledy zjistěte a odstraňte nedostatky v zabezpečení svých online účtů.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization Vault." }, "unsecuredWebsitesReport": { @@ -1609,7 +1609,7 @@ "message": "Hlášení o úniku hesel" }, "exposedPasswordsReportDesc": { - "message": "Uniklá hesla jsou hesla, která byla odhalena během známých úniků dat, zveřejněna nebo prodána hackery na dark webu." + "message": "Hesla odhalená při úniku dat jsou pro útočníky snadným cílem. Změňte tato hesla, abyste předešli případným průnikům." }, "exposedPasswordsFound": { "message": "Nalezena uniklá hesla" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Doplňky" }, @@ -2291,7 +2307,7 @@ "message": "Ročně" }, "annual": { - "message": "Annual" + "message": "Roční" }, "basePrice": { "message": "Základní cena" @@ -2321,7 +2337,7 @@ "message": "Nápověda" }, "getApps": { - "message": "Stáhnout aplikaci" + "message": "Získání aplikací" }, "loggedInAs": { "message": "Přihlášen jako" @@ -4936,7 +4952,7 @@ "message": "Sponsorship created" }, "emailSent": { - "message": "Email sent" + "message": "Email odeslán" }, "revokeSponsorshipConfirmation": { "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" @@ -4960,7 +4976,7 @@ "message": "Code sent" }, "verificationCode": { - "message": "Verification code" + "message": "Ověřovací kód" }, "confirmIdentity": { "message": "Confirm your identity to continue." @@ -5125,7 +5141,7 @@ "message": "Billing sync token" }, "active": { - "message": "Active" + "message": "Aktivní" }, "inactive": { "message": "Inactive" @@ -5237,10 +5253,10 @@ "message": "Generator" }, "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" + "message": "Co chcete vygenerovat?" }, "passwordType": { - "message": "Password type" + "message": "Typ hesla" }, "regenerateUsername": { "message": "Regenerate username" diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index da2d2010c14..fff4319b07a 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Køb Premium for kun $PRICE$ /år, eller køb Premium-konti til $FAMILYPLANUSERCOUNT$ brugere og ubegrænset familiedeling med et ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families-abonnement." + }, "addons": { "message": "Tilføjelser" }, @@ -5562,13 +5578,13 @@ "message": "Slet hemmeligheder" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Vælg projekter, som hemmeligheden vil blive tilknyttet. Den vil kun kunne ses af organisationsbrugere med adgang til disse projekter." }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "Angiv eller vælg Projekter" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "Angiv eller vælg Projekt" }, "project": { "message": "Projekt" diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 5fe1792a8e9..f069c32f20c 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -1609,7 +1609,7 @@ "message": "Bericht über kompromittierte Passwörter" }, "exposedPasswordsReportDesc": { - "message": "Kompromittierte Passwörter sind Passwörter, die in bekannten Datendiebstählen entdeckt und veröffentlicht wurden oder von Hackern im Dark Web verkauft wurden." + "message": "Durch Datendiebstahl aufgedeckte Passwörter sind einfache Ziele für Angreifer. Ändere diese Passwörter, um mögliche Einbrüche zu verhindern." }, "exposedPasswordsFound": { "message": "Es wurden kompromittierte Passwörter gefunden" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Wechsle zu Premium für nur $PRICE$ /Jahr oder erhalte Premium-Konten für $FAMILYPLANUSERCOUNT$ Benutzer und eine unbegrenzte Familienfreigabe mit einem ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Familien-Abo." + }, "addons": { "message": "Erweiterungen" }, diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 132cc37319c..08bccb52bd6 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -1609,7 +1609,7 @@ "message": "Αναφορά Κωδικών που έχουν Εκτεθεί" }, "exposedPasswordsReportDesc": { - "message": "Οι εκτεθειμένοι κωδικοί, είναι κωδικοί που έχουν εμφανιστεί σε γνωστές διαρροές δεδομένων, και που κυκλοφόρησαν δημόσια ή πουλήθηκαν στο dark web από χάκερς." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "Βρέθηκαν Εκτεθειμένοι Κωδικοί" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Πρόσθετα" }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 7162f34cd4c..bdf946f6609 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Add-ons" }, @@ -5562,7 +5578,7 @@ "message": "Delete Secrets" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Select projects that the secret will be associated with. Only organisation users with access to these projects will be able to see the secret." }, "typeOrSelectProjects": { "message": "Type or select Projects" diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 104a33e004d..b1e272ead8d 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -1609,7 +1609,7 @@ "message": "Exposed passwords report" }, "exposedPasswordsReportDesc": { - "message": "Exposed passwords are passwords that have been uncovered in known data breaches that were released publicly or sold on the dark web by hackers." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "Exposed passwords found" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Add-ons" }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 3a1ce78aca2..ca05278c338 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Aldonaĵoj" }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index c25e93f1e11..296b810df9c 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -1609,7 +1609,7 @@ "message": "Infome de contraseñas expuestas" }, "exposedPasswordsReportDesc": { - "message": "Exposed passwords are passwords have been uncovered in known data breaches that were released publicly or sold on the dark web by hackers." + "message": "Las contraseñas expuestas en una brecha de datos son blancos fáciles para los atacantes. Cambia estas contraseñas para evitar futuras brechas." }, "exposedPasswordsFound": { "message": "Contraseñas expuestas encontradas" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Pasate a premium por solo $PRICE$ /año, o consigue cuentas premium para $FAMILYPLANUSERCOUNT$ usuarios y una familia ilimitada compartiendo con un ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Plan familiar Bitwarden." + }, "addons": { "message": "Complementos" }, @@ -3462,7 +3478,7 @@ "description": "'OAuth 2.0' is a programming protocol. It should probably not be translated." }, "viewApiKey": { - "message": "Ver Clave API" + "message": "Ver clave API" }, "rotateApiKey": { "message": "Regenerar clave API" @@ -5494,25 +5510,25 @@ "message": "Naranja" }, "lavender": { - "message": "Lavender" + "message": "Lavanda" }, "yellow": { - "message": "Yellow" + "message": "Amarillo" }, "indigo": { "message": "Indigo" }, "teal": { - "message": "Teal" + "message": "Verde azulado" }, "salmon": { - "message": "Salmon" + "message": "Salmón" }, "pink": { - "message": "Pink" + "message": "Rosa" }, "customColor": { - "message": "Custom Color" + "message": "Color personalizado" }, "multiSelectPlaceholder": { "message": "-- Escriba para filtrar --" diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 0fcc8899d15..1cf29fe5035 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Lisad" }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 64ba1a0c4c3..b23d133af67 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Gehigarriak" }, diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index b05c694f0c5..937131a7d3c 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -10,7 +10,7 @@ } }, "whatTypeOfItem": { - "message": "این چه نوع اطلاعلاتی هست؟" + "message": "این چه نوع موردی است؟" }, "name": { "message": "نام" @@ -35,13 +35,13 @@ "message": "نام کاربری" }, "password": { - "message": "گذرواژه" + "message": "کلمه عبور" }, "newPassword": { - "message": "گذرواژهٔ جدید" + "message": "کلمه عبور جدید" }, "passphrase": { - "message": "کلمه عبور" + "message": "عبارت عبور" }, "notes": { "message": "یادداشت‌ها" @@ -80,7 +80,7 @@ "message": "شماره گواهی‌نامه" }, "email": { - "message": "رایانامه" + "message": "ایمیل" }, "phone": { "message": "تلفن" @@ -104,7 +104,7 @@ "message": "ژوئن" }, "july": { - "message": "ژوئیه" + "message": "جولای" }, "august": { "message": "آگوست‌" @@ -131,7 +131,7 @@ "message": "خانم" }, "ms": { - "message": "خانم" + "message": "بانو" }, "dr": { "message": "دکتر" @@ -143,7 +143,7 @@ "message": "سال انقضاء" }, "authenticatorKeyTotp": { - "message": "کلید احراز هویت کننده (TOTP)" + "message": "کلید احراز هویت (TOTP)" }, "folder": { "message": "پوشه" @@ -209,31 +209,31 @@ "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "تشخیص تطابق", + "message": "تشخیص مطابقت", "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { - "message": "تشخیص تطابق پیشفرض", + "message": "بررسی مطابقت پیش‌فرض", "description": "Default URI match detection for auto-fill." }, "never": { "message": "هرگز" }, "toggleVisibility": { - "message": "تعویض حالت آشکاری" + "message": "قابلیت مشاهده را تغییر دهید" }, "toggleCollapse": { "message": "باز و بسته کردن", "description": "Toggling an expand/collapse state." }, "generatePassword": { - "message": "تولید گذرواژه" + "message": "تولید کلمه عبور" }, "checkPassword": { - "message": "بررسی اینکه که آیا کلمه عبور افشا شده است." + "message": "بررسی کنید که آیا کلمه عبور افشا شده است." }, "passwordExposed": { - "message": "این رمز عبور در رخنه داده‌ها $VALUE$ بار افشا شده است. باید تغییرش دهید.", + "message": "این کلمه عبور $VALUE$ بار در رخنه داده‌ها افشا شده است. باید آن را تغییر دهید.", "placeholders": { "value": { "content": "$1", @@ -242,7 +242,7 @@ } }, "passwordSafe": { - "message": "این کلمه عبور در هیچ رخنه داده شناخته‌شده‌ای پیدا نشد. استفاده از آن، امن است." + "message": "این کلمه عبور در هیچ رخنه داده ای شناخته نشده است. استفاده از آن باید ایمن باشد." }, "save": { "message": "ذخیره" @@ -260,10 +260,10 @@ "message": "حذف" }, "favorite": { - "message": "برگزیده‌" + "message": "مورد علاقه" }, "unfavorite": { - "message": "حذف برگزیده" + "message": "حذف از مورد علاقه" }, "edit": { "message": "ویرایش" @@ -275,7 +275,7 @@ "message": "جستجوی پوشه" }, "searchFavorites": { - "message": "جستجوی برگزیده‌ها" + "message": "جستجوی مورد علاقه‌ها" }, "searchType": { "message": "جستجوی نوع", @@ -285,10 +285,10 @@ "message": "جستجوی گاوصندوق" }, "allItems": { - "message": "همه موارد" + "message": "تمام موارد" }, "favorites": { - "message": "برگزیده‌ها" + "message": "مورد علاقه‌ها" }, "types": { "message": "انواع" @@ -357,7 +357,7 @@ "message": "کشور" }, "shared": { - "message": "به اشتراک گذاشته شد" + "message": "اشتراک گذاری شد" }, "attachments": { "message": "پیوست‌ها" @@ -375,20 +375,20 @@ "message": "مشاهده مورد" }, "ex": { - "message": "مثال", + "message": "مثال.", "description": "Short abbreviation for 'example'." }, "other": { "message": "ساير" }, "share": { - "message": "اشتراک‌گذاری" + "message": "اشتراک گذاری" }, "moveToOrganization": { "message": "انتقال به سازمان" }, "valueCopied": { - "message": "$VALUE$ رونوشت شد", + "message": "$VALUE$ کپی شد", "description": "Value has been copied to the clipboard.", "placeholders": { "value": { @@ -398,27 +398,27 @@ } }, "copyValue": { - "message": "رونوشت مقدار", + "message": "کپی مقدار", "description": "Copy value to clipboard" }, "copyPassword": { - "message": "رونوشت گذرواژه", + "message": "کپی کلمه عبور", "description": "Copy password to clipboard" }, "copyUsername": { - "message": "رونوشت نام کاربری", + "message": "کپی نام کاربری", "description": "Copy username to clipboard" }, "copyNumber": { - "message": "رونوشت شماره", + "message": "کپی شماره", "description": "Copy credit card number" }, "copySecurityCode": { - "message": "رونوشت کد امنیتی", + "message": "کپی کد امنیتی", "description": "Copy credit card security code (CVV)" }, "copyUri": { - "message": "رونوشت نشانی اینترنتی", + "message": "کپی نشانی اینترنتی", "description": "Copy URI to clipboard" }, "me": { @@ -440,7 +440,7 @@ "message": "موارد گاوصندوق" }, "moveSelectedToOrg": { - "message": "انتقال مورد انتخاب‌شده به سازمان" + "message": "انتقال مورد انتخاب شده به سازمان" }, "deleteSelected": { "message": "حذف موارد انتخاب شده" @@ -461,7 +461,7 @@ "message": "افزودن پیوست جدید" }, "deletedAttachment": { - "message": "پیوست حذف شد" + "message": "پیوست حذف شده" }, "deleteAttachmentConfirmation": { "message": "آیا از پاک کردن این پیوست مطمئن هستید؟" @@ -479,10 +479,10 @@ "message": "بیشترین حجم فایل ۵۰۰ مگابایت است." }, "updateKey": { - "message": "تا زمانی که کد رمزنگاری خود را به‌روز نکنید نمی‌توانید از این ویژگی استفاده کنید." + "message": "تا زمانی که کد رمزنگاری را به‌روز نکنید نمی‌توانید از این قابلیت استفاده کنید." }, "addedItem": { - "message": "مورد افزوده شد" + "message": "مورد اضافه شد" }, "editedItem": { "message": "مورد ذخیره شد" @@ -510,175 +510,175 @@ } }, "deleteItem": { - "message": "Delete item" + "message": "حذف مورد" }, "deleteFolder": { - "message": "Delete folder" + "message": "حذف پوشه" }, "deleteAttachment": { - "message": "Delete attachment" + "message": "حذف پیوست" }, "deleteItemConfirmation": { - "message": "Do you really want to send to the trash?" + "message": "واقعاً می‌خواهید این آیتم را به سطل زباله ارسال کنید؟" }, "deletedItem": { - "message": "Item sent to trash" + "message": "مورد به زباله‌ها فرستاده شد" }, "deletedItems": { - "message": "Items sent to trash" + "message": "مورد به زباله‌ها فرستاده شد" }, "movedItems": { - "message": "Items moved" + "message": "مورد منتقل شد" }, "overwritePasswordConfirmation": { - "message": "Are you sure you want to overwrite the current password?" + "message": "آیا از بازنویسی بر روی کلمه عبور فعلی مطمئن هستید؟" }, "editedFolder": { - "message": "Folder saved" + "message": "پوشه ذخیره شد" }, "addedFolder": { - "message": "Folder added" + "message": "پوشه اضافه شد" }, "deleteFolderConfirmation": { - "message": "Are you sure you want to delete this folder?" + "message": "آیا از حذف این پوشه اطمینان دارید؟" }, "deletedFolder": { - "message": "Folder deleted" + "message": "پوشه حذف شد" }, "loggedOut": { - "message": "Logged out" + "message": "خارج شد" }, "loginExpired": { - "message": "Your login session has expired." + "message": "نشست ورود شما منقضی شده است." }, "logOutConfirmation": { - "message": "Are you sure you want to log out?" + "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" }, "logOut": { - "message": "Log out" + "message": "خروج" }, "ok": { - "message": "Ok" + "message": "تأیید" }, "yes": { - "message": "Yes" + "message": "بله" }, "no": { - "message": "No" + "message": "خیر" }, "loginOrCreateNewAccount": { - "message": "Log in or create a new account to access your secure vault." + "message": "وارد شوید یا یک حساب کاربری بسازید تا به گاوصندوق امنتان دسترسی یابید." }, "loginWithDevice": { - "message": "Log in with device" + "message": "ورود با دستگاه" }, "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden mobile app. Need another option?" + "message": "ورود به سیستم با دستگاه باید در تنظیمات برنامه‌ی موبایل Bitwarden تنظیم شود. به گزینه دیگری نیاز دارید؟" }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "با کلمه عبور اصلی وارد شوید" }, "createAccount": { - "message": "Create account" + "message": "ایجاد حساب کاربری" }, "newAroundHere": { - "message": "New around here?" + "message": "اینجا جدیده؟" }, "startTrial": { - "message": "Start trial" + "message": "شروع دوره آزمایشی" }, "logIn": { - "message": "Log in" + "message": "ورود" }, "logInInitiated": { - "message": "Log in initiated" + "message": "ورود به سیستم آغاز شد" }, "submit": { - "message": "Submit" + "message": "ثبت" }, "emailAddressDesc": { - "message": "You'll use your email address to log in." + "message": "برای ورود به سیستم از نشانی ایمیل خودتان استفاده خواهید کرد." }, "yourName": { - "message": "Your name" + "message": "نام شما" }, "yourNameDesc": { - "message": "What should we call you?" + "message": "ما شما را چی صدا بزنیم؟" }, "masterPass": { - "message": "Master password" + "message": "کلمه عبور اصلی" }, "masterPassDesc": { - "message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it." + "message": "کلمه عبور اصلی، کلمه عبوری است که شما برای دسترسی به گاوصندوق خود استفاده می‌کنید. به یاد داشتن کلمه عبور اصلی بسیار اهمیت دارد. اگر فراموشش کنید هیچ راهی برای بازگردانی آن وجود ندارد." }, "masterPassImportant": { - "message": "Master passwords cannot be recovered if you forget it!" + "message": "کلمه‌های عبور اصلی در صورت فراموشی قابل بازیابی نیستند!" }, "masterPassHintDesc": { - "message": "A master password hint can help you remember your password if you forget it." + "message": "یادآور کلمه عبور اصلی کمک می‌کند در صورت فراموشی آن را به یاد بیارید." }, "reTypeMasterPass": { - "message": "Re-type master password" + "message": "نوشتن دوباره کلمه عبور اصلی" }, "masterPassHint": { - "message": "Master password hint (optional)" + "message": "یادآور کلمه عبور اصلی (اختیاری)" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "یادآور کلمه عبور اصلی" }, "settings": { - "message": "Settings" + "message": "تنظیمات" }, "passwordHint": { - "message": "Password hint" + "message": "یادآور کلمه عبور" }, "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." + "message": "برای دریافت یادآور کلمه عبور اصلی خود نشانی ایمیل‌تان را وارد کنید." }, "getMasterPasswordHint": { - "message": "Get master password hint" + "message": "دریافت یادآور کلمه عبور اصلی" }, "emailRequired": { - "message": "Email address is required." + "message": "نشانی ایمیل ضروری است." }, "invalidEmail": { - "message": "Invalid email address." + "message": "نشانی ایمیل نامعتبر است." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "کلمه عبور اصلی ضروری است." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "نوشتن مجدد کلمه عبور اصلی ضروری است." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "طول کلمه عبور اصلی باید حداقل ۸ کاراکتر باشد." }, "masterPassDoesntMatch": { - "message": "Master password confirmation does not match." + "message": "کلمه عبور اصلی با تکرار آن مطابقت ندارد." }, "newAccountCreated": { - "message": "Your new account has been created! You may now log in." + "message": "حساب کاربری جدید شما ساخته شد! حالا می‌توانید وارد شوید." }, "trialAccountCreated": { - "message": "Account created successfully." + "message": "حساب کاربری با موفقیت ساخته شد." }, "masterPassSent": { - "message": "We've sent you an email with your master password hint." + "message": "ما یک ایمیل همراه با یادآور کلمه عبور اصلی برایتان ارسال کردیم." }, "unexpectedError": { - "message": "An unexpected error has occurred." + "message": "یک خطای غیر منتظره رخ داده است." }, "emailAddress": { - "message": "Email address" + "message": "نشانی ایمیل" }, "yourVaultIsLocked": { - "message": "Your vault is locked. Verify your master password to continue." + "message": "گاوصندوق شما قفل است. برای ادامه کلمه عبور اصلی خود را وارد کنید." }, "unlock": { - "message": "Unlock" + "message": "باز کردن قفل" }, "loggedInAsEmailOn": { - "message": "Logged in as $EMAIL$ on $HOSTNAME$.", + "message": "وارد شده با $EMAIL$ در $HOSTNAME$.", "placeholders": { "email": { "content": "$1", @@ -691,40 +691,40 @@ } }, "invalidMasterPassword": { - "message": "Invalid master password" + "message": "کلمه عبور اصلی نامعتبر است" }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "کلمه عبور پرونده نامعتبر است، لطفاً از کلمه عبوری که هنگام ایجاد پرونده‌ی برون ریزی وارد کردید استفاده کنید." }, "lockNow": { - "message": "Lock now" + "message": "الان قفل شود" }, "noItemsInList": { - "message": "There are no items to list." + "message": "هیچ موردی برای نمایش وجود ندارد." }, "noCollectionsInList": { - "message": "There are no collections to list." + "message": "هیچ مجموعه ای برای لیست کردن وجود ندارد." }, "noGroupsInList": { - "message": "There are no groups to list." + "message": "هیچ گروهی برای نمایش وجود ندارد." }, "noUsersInList": { - "message": "There are no users to list." + "message": "هیچ کاربری برای نمایش وجود ندارد." }, "noEventsInList": { - "message": "There are no events to list." + "message": "هیچ مناسبتی برای نمایش وجود ندارد." }, "newOrganization": { - "message": "New organization" + "message": "سازمان جدید" }, "noOrganizationsList": { - "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." + "message": "شما به هیچ سازمانی تعلق ندارید. سازمان‌ها به شما اجازه می‌دهند تا داده‌های خود را با کاربران دیگر به صورت امن به اشتراک بگذارید." }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "یک اعلان به دستگاه شما ارسال شده است." }, "versionNumber": { - "message": "Version $VERSION_NUMBER$", + "message": "نسخه $VERSION_NUMBER$", "placeholders": { "version_number": { "content": "$1", @@ -733,10 +733,10 @@ } }, "enterVerificationCodeApp": { - "message": "Enter the 6 digit verification code from your authenticator app." + "message": "کد ۶ رقمی تأیید را از برنامه احراز هویت وارد کنید." }, "enterVerificationCodeEmail": { - "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", + "message": "کد ۶ رقمی تأیید را که به $EMAIL$ ایمیل شده را وارد کنید.", "placeholders": { "email": { "content": "$1", @@ -745,7 +745,7 @@ } }, "verificationCodeEmailSent": { - "message": "Verification email sent to $EMAIL$.", + "message": "ایمیل تأیید به $EMAIL$ ارسال شد.", "placeholders": { "email": { "content": "$1", @@ -754,100 +754,100 @@ } }, "rememberMe": { - "message": "Remember me" + "message": "مرا به خاطر بسپار" }, "sendVerificationCodeEmailAgain": { - "message": "Send verification code email again" + "message": "ارسال دوباره ایمیل کد تأیید" }, "useAnotherTwoStepMethod": { - "message": "Use another two-step login method" + "message": "استفاده از روش ورود دو مرحله‌ای دیگر" }, "insertYubiKey": { - "message": "Insert your YubiKey into your computer's USB port, then touch its button." + "message": "YubiKey خود را وارد پورت USB رایانه کنید، بعد دکمه آن را بفشارید." }, "insertU2f": { - "message": "Insert your security key into your computer's USB port. If it has a button, touch it." + "message": "کلید امنیتی خود را وارد پورت USB رایانه کنید، اگر دکمه ای دارد آن را بفشارید." }, "loginUnavailable": { - "message": "Login unavailable" + "message": "ورود به سیستم در دسترس نیست" }, "noTwoStepProviders": { - "message": "This account has two-step login set up, however, none of the configured two-step providers are supported by this web browser." + "message": "ورود دو مرحله‌ای برای این حساب فعال است، با این حال، هیچ یک از ارائه‌دهندگان دو مرحله‌ای پیکربندی شده توسط این مرورگر وب پشتیبانی نمی‌شوند." }, "noTwoStepProviders2": { - "message": "Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported across web browsers (such as an authenticator app)." + "message": "لطفاً از یک مرورگر وب پشتیبانی شده (مانند کروم) استفاده کنید و یا ارائه دهندگان اضافی را که از مرورگر وب بهتر پشتیانی می‌کنند را اضافه کنید (مانند یک برنامه احراز هویت)." }, "twoStepOptions": { - "message": "Two-step login options" + "message": "گزینه‌های ورود دو مرحله‌ای" }, "recoveryCodeDesc": { - "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." + "message": "دسترسی به تمامی ارائه‌دهندگان دو مرحله‌ای را از دست داده‌اید؟ از کد بازیابی خود برای غیرفعال‌سازی ارائه‌دهندگان دو مرحله‌ای از حسابتان استفاده کنید." }, "recoveryCodeTitle": { - "message": "Recovery code" + "message": "کد بازیابی" }, "authenticatorAppTitle": { - "message": "Authenticator app" + "message": "برنامه احراز هویت" }, "authenticatorAppDesc": { - "message": "Use an authenticator app (such as Authy or Google Authenticator) to generate time-based verification codes.", + "message": "از یک برنامه احراز هویت (مانند Authy یا Google Authenticator) استفاده کنید تا کدهای تأیید بر پایه زمان تولید کنید.", "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." }, "yubiKeyTitle": { - "message": "YubiKey OTP security key" + "message": "کلید امنیتی YubiKey OTP" }, "yubiKeyDesc": { - "message": "Use a YubiKey to access your account. Works with YubiKey 4 series, 5 series, and NEO devices." + "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. با دستگاه های YubiKey سری 4، سری 5 و NEO کار می‌کند." }, "duoDesc": { - "message": "Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.", + "message": "با Duo Security با استفاده از برنامه تلفن همراه، پیامک، تماس تلفنی، یا کلید امنیتی U2F تأیید کنید.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", + "message": "از Duo Security با استفاده از برنامه تلفن همراه، پیامک، تماس تلفنی یا کلید امنیتی U2F برای تأیید سازمان خود استفاده کنید.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "u2fDesc": { - "message": "Use any FIDO U2F compatible security key to access your account." + "message": "از هر کلید امنیتی سازگار با FIDO U2F برای دسترسی به حساب خود استفاده کنید." }, "u2fTitle": { - "message": "FIDO U2F security key" + "message": "کلید امنیتی FIDO U2F" }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Use any WebAuthn compatible security key to access your account." + "message": "برای دسترسی به حساب خود از هر کلید امنیتی فعال شده WebAuthn استفاده کنید." }, "webAuthnMigrated": { - "message": "(Migrated from FIDO)" + "message": "(مهاجرت از FIDO)" }, "emailTitle": { - "message": "Email" + "message": "ایمیل" }, "emailDesc": { - "message": "Verification codes will be emailed to you." + "message": "کد تأیید برایتان ارسال می‌شود." }, "continue": { - "message": "Continue" + "message": "ادامه" }, "organization": { - "message": "Organization" + "message": "سازماندهی" }, "organizations": { - "message": "Organizations" + "message": "سازمان ها" }, "moveToOrgDesc": { - "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + "message": "سازمانی را انتخاب کنید که می‌خواهید این مورد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت مورد را به آن سازمان منتقل می‌کند. پس از انتقال این مورد، دیگر مالک مستقیم آن نخواهید بود." }, "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." + "message": "سازمانی را انتخاب کنید که می‌خواهید این موارد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت موارد را به آن سازمان منتقل می‌کند. پس از انتقال این موارد، دیگر مالک مستقیم آن‌ها نخواهید بود." }, "collectionsDesc": { - "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." + "message": "مجموعه‌هایی را ویرایش کنید که این مورد با آن‌ها به اشتراک گذاشته می‌شود. فقط کاربران سازمانی که به این مجموعه‌ها دسترسی دارند می‌توانند این مورد را ببینند." }, "deleteSelectedItemsDesc": { - "message": "You have selected $COUNT$ item(s) to delete. Are you sure you want to delete all of these items?", + "message": "شما $COUNT$ مورد را برای حذف انتخاب کرده اید. آیا مطمئنید که می‌خواهید تمام این موارد را حذف کنید؟", "placeholders": { "count": { "content": "$1", @@ -856,7 +856,7 @@ } }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to move the $COUNT$ selected item(s) to.", + "message": "پوشه ای را انتخاب کنید که می‌خواهید $COUNT$ مورد انتخاب شده را به آن منتقل کنید.", "placeholders": { "count": { "content": "$1", @@ -865,7 +865,7 @@ } }, "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", + "message": "شما $COUNT$ مورد را انتخاب کرده اید. $MOVEABLE_COUNT$ مورد را می‌توان به یک سازمان منتقل کرد، $NONMOVEABLE_COUNT$ تا را نمی‌تواند.", "placeholders": { "count": { "content": "$1", @@ -882,156 +882,156 @@ } }, "verificationCodeTotp": { - "message": "Verification code (TOTP)" + "message": "کد تأیید (TOTP)" }, "copyVerificationCode": { - "message": "Copy verification code" + "message": "کپی کد تأیید" }, "warning": { - "message": "Warning" + "message": "هشدار" }, "confirmVaultExport": { - "message": "Confirm vault export" + "message": "برون ریزی گاوصندوق را تأیید کنید" }, "exportWarningDesc": { - "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + "message": "این برون ریزی شامل داده‌های گاوصندوق در یک قالب رمزنگاری نشده است. شما نباید آن را از طریق یک راه ارتباطی نا امن (مثل ایمیل) ذخیره یا ارسال کنید. به محض اینکه کارتان با آن تمام شد، آن را حذف کنید." }, "encExportKeyWarningDesc": { - "message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." + "message": "این برون ریزی با استفاده از کلید رمزگذاری حساب شما، اطلاعاتتان را رمزگذاری می کند. اگر زمانی کلید رمزگذاری حساب خود را بچرخانید، باید دوباره خروجی بگیرید، چون قادر به رمزگشایی این پرونده برون ریزی نخواهید بود." }, "encExportAccountWarningDesc": { - "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + "message": "کلیدهای رمزگذاری حساب برای هر حساب کاربری Bitwarden منحصر به فرد است، بنابراین نمی‌توانید برون ریزی رمزگذاری شده را به حساب دیگری وارد کنید." }, "export": { - "message": "Export" + "message": "برون ریزی" }, "exportVault": { - "message": "Export vault" + "message": "برون ریزی گاوصندوق" }, "fileFormat": { - "message": "File format" + "message": "فرمت پرونده" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "این پرونده برون ریزی با رمز محافظت می‌شود و برای رمزگشایی به کلمه عبور پرونده نیاز دارد." }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "این کلمه عبور برای برون ریزی و درون ریزی این پرونده استفاده می‌شود" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "تأیید کلمه عبور اصلی" }, "confirmFormat": { - "message": "Confirm format" + "message": "فرمت را تأیید کنید" }, "filePassword": { - "message": "File password" + "message": "کلمه عبور پرونده" }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "تأیید کلمه عبور پرونده" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "برای رمزگذاری برون ریزی و محدود کردن درون ریزی فقط به حساب فعلی Bitwarden، از کلید رمزگذاری حساب خود که از نام کاربری و کلمه عبور اصلی حساب شما مشتق شده است استفاده کنید." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "یک کلمه عبور برای پرونده به‌منظور رمزگذاری تنظیم کنید تا برون ریزی و درون ریزی آن به هر حساب Bitwarden با استفاده از کلمه عبور رمزگشایی شود." }, "exportTypeHeading": { - "message": "Export type" + "message": "نوع برون ریزی" }, "accountRestricted": { - "message": "Account restricted" + "message": "حساب کاربری محدود شده است" }, "passwordProtected": { - "message": "Password protected" + "message": "محافظت ‌شده با کلمه عبور" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "\"کلمه عبور پرونده\" و \"تأیید کلمه عبور پرونده\" با یکدیگر مطابقت ندارند." }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "درون ریزی گاوصندوق را تأیید کنید" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "این پرونده با کلمه عبور محافظت شده است. لطفاً کلمه عبور پرونده را برای درون ریزی داده‌ها وارد کنید." }, "exportSuccess": { - "message": "Vault data exported" + "message": "داده های گاوصندوق برون ریزی شد" }, "passwordGenerator": { - "message": "Password generator" + "message": "تولید کننده کلمه عبور" }, "minComplexityScore": { - "message": "Minimum complexity score" + "message": "حداقل نمره پیچیدگی" }, "minNumbers": { - "message": "Minimum numbers" + "message": "حداقل اعداد" }, "minSpecial": { - "message": "Minimum special", + "message": "حداقل حرف خاص", "description": "Minimum special characters" }, "ambiguous": { - "message": "Avoid ambiguous characters" + "message": "از کاراکترهای مبهم اجتناب کن" }, "regeneratePassword": { - "message": "Regenerate password" + "message": "تولید مجدد کلمه عبور" }, "length": { - "message": "Length" + "message": "طول" }, "uppercase": { - "message": "Uppercase (A-Z)", + "message": "حروف بزرگ (A-Z)", "description": "Include uppercase letters in the password generator." }, "lowercase": { - "message": "Lowercase (a-z)", + "message": "حروف کوچک (a-z)", "description": "Include lowercase letters in the password generator." }, "numbers": { - "message": "Numbers (0-9)" + "message": "اعداد (‪0-9‬)" }, "specialCharacters": { - "message": "Special characters (!@#$%^&*)" + "message": "نویسه‌های ویژه (!@#$%^&*)" }, "numWords": { - "message": "Number of words" + "message": "تعداد کلمات" }, "wordSeparator": { - "message": "Word separator" + "message": "جداکننده کلمات" }, "capitalize": { - "message": "Capitalize", + "message": "بزرگ کردن", "description": "Make the first letter of a word uppercase." }, "includeNumber": { - "message": "Include number" + "message": "شامل عدد" }, "passwordHistory": { - "message": "Password history" + "message": "تاریخچه کلمه عبور" }, "noPasswordsInList": { - "message": "There are no passwords to list." + "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, "clear": { - "message": "Clear", + "message": "پاک کردن", "description": "To clear something out. Example: To clear browser history." }, "accountUpdated": { - "message": "Account saved" + "message": "حساب کاربری ذخیره شده" }, "changeEmail": { - "message": "Change email" + "message": "تغییر ایمیل" }, "changeEmailTwoFactorWarning": { - "message": "Proceeding will change your account email address. It will not change the email address used for two-step login authentication. You can change this email address in the two-step login settings." + "message": "ادامه دادن، نشانی ایمیل حساب شما را تغییر خواهد داد. اما نشانی ایمیل مورد استفاده برای احراز هویت دو مرحله ای ورود را تغییر نمی‌دهد. می‌توانید این نشانی ایمیل را در تنظیمات ورود به سیستم دو مرحله ای تغییر دهید." }, "newEmail": { - "message": "New email" + "message": "ایمیل جدید" }, "code": { - "message": "Code" + "message": "کد" }, "changeEmailDesc": { - "message": "We have emailed a verification code to $EMAIL$. Please check your email for this code and enter it below to finalize the email address change.", + "message": "ما یک کد تأیید را به $EMAIL$ ایمیل کرده‌ایم. لطفاً ایمیل خود را برای این کد بررسی کنید و آن را در زیر وارد کنید تا تغییر نسانی ایمیل نهایی شود.", "placeholders": { "email": { "content": "$1", @@ -1040,43 +1040,43 @@ } }, "loggedOutWarning": { - "message": "Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "ادامه دادن، شما را از نشست فعلی خود خارج می‌کند و باید دوباره وارد سیستم شوید. جلسات فعال در دستگاه‌های دیگر ممکن است تا یک ساعت فعال بمانند." }, "emailChanged": { - "message": "Email saved" + "message": "ایمیل ذخیره شد" }, "logBackIn": { - "message": "Please log back in." + "message": "لطفاً دوباره وارد شوید." }, "logBackInOthersToo": { - "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." + "message": "لطفاً دوباره وارد شوید. اگر از سایر برنامه‌های Bitwarden استفاده می‌کنید، از سیستم خارج شوید و دوباره به آن‌ها وارد شوید." }, "changeMasterPassword": { - "message": "Change master password" + "message": "تغییر کلمه عبور اصلی" }, "masterPasswordChanged": { - "message": "Master password saved" + "message": "کلمه عبور اصلی ذخیره شد" }, "currentMasterPass": { - "message": "Current master password" + "message": "کلمه عبور اصلی فعلی" }, "newMasterPass": { - "message": "New master password" + "message": "کلمه عبور اصلی جدید" }, "confirmNewMasterPass": { - "message": "Confirm new master password" + "message": "تأیید کلمه عبور اصلی جدید" }, "encKeySettings": { - "message": "Encryption key settings" + "message": "تنظیمات کلید رمزگذاری" }, "kdfAlgorithm": { - "message": "KDF algorithm" + "message": "الگوریتم KDF" }, "kdfIterations": { - "message": "KDF iterations" + "message": "تکرار KDF" }, "kdfIterationsDesc": { - "message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker. We recommend a value of $VALUE$ or more.", + "message": "تکرار KDF بالاتر می‌تواند به محافظت از کلمه عبور اصلی شما در برابر حمله‌ی \"بروت فورث\" توسط مهاجم کمک کند. ما مقدار $VALUE$ یا بیشتر را توصیه می‌کنیم.", "placeholders": { "value": { "content": "$1", @@ -1085,7 +1085,7 @@ } }, "kdfIterationsWarning": { - "message": "Setting your KDF iterations too high could result in poor performance when logging into (and unlocking) Bitwarden on devices with slower CPUs. We recommend that you increase the value in increments of $INCREMENT$ and then test all of your devices.", + "message": "تنظیم تکرارهای KDF شما خیلی زیاد می‌تواند منجر به عملکرد ضعیف هنگام ورود به Bitwarden (و باز کردن قفل) در دستگاه هایی با CPU کندتر شود. توصیه می کنیم که مقدار را با افزایش $INCREMENT$ افزایش دهید و سپس همه دستگاه های خود را آزمایش کنید.", "placeholders": { "increment": { "content": "$1", @@ -1094,85 +1094,85 @@ } }, "changeKdf": { - "message": "Change KDF" + "message": "تغییر KDF" }, "encKeySettingsChanged": { - "message": "Encryption key settings saved" + "message": "تنظیمات کلید رمزگذاری ذخیره شد" }, "dangerZone": { - "message": "Danger zone" + "message": "منطقه‌ی خطر" }, "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" + "message": "مراقب باشید، این اقدامات قابل برگشت نیستند!" }, "deauthorizeSessions": { - "message": "Deauthorize sessions" + "message": "لغو مجوز نشست‌ها" }, "deauthorizeSessionsDesc": { - "message": "Concerned your account is logged in on another device? Proceed below to deauthorize all computers or devices that you have previously used. This security step is recommended if you previously used a public computer or accidentally saved your password on a device that isn't yours. This step will also clear all previously remembered two-step login sessions." + "message": "آیا نگرانید که حساب کاربری شما در دستگاه دیگری وارد شده باشد؟ برای لغو مجوز همه رایانه‌ها یا دستگاه‌هایی که قبلاً استفاده کرده‌اید، در زیر اقدام کنید. اگر قبلاً از یک رایانه عمومی استفاده کرده اید یا به طور تصادفی کلمه عبور خود را در دستگاهی که متعلق به شما نیست ذخیره کرده اید، این مرحله امنیتی توصیه می‌شود. این مرحله همچنین تمام نشست‌های ورود دو مرحله ای را که قبلاً به‌خاطر سپرده اید را پاک می‌کند." }, "deauthorizeSessionsWarning": { - "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." + "message": "ادامه دادن همچنین شما را از نشست فعلی خود خارج می‌کند و باید دوباره وارد سیستم شوید. در صورت راه اندازی مجدداً از شما خواسته می‌شود تا دوباره به سیستم دو مرحله ای بپردازید. جلسات فعال در دستگاه‌های دیگر ممکن است تا یک ساعت فعال بمانند." }, "sessionsDeauthorized": { - "message": "All sessions deauthorized" + "message": "همه نشست‌ها غیرمجاز است" }, "purgeVault": { - "message": "Purge vault" + "message": "پاکسازی گاوصندوق" }, "purgedOrganizationVault": { - "message": "Purged organization vault." + "message": "گاوصندوق سازمان پاکسازی." }, "vaultAccessedByProvider": { - "message": "Vault accessed by Provider." + "message": "گاوصندوق توسط ارائه دهنده قابل دسترسی است." }, "purgeVaultDesc": { - "message": "Proceed below to delete all items and folders in your vault. Items that belong to an organization that you share with will not be deleted." + "message": "برای حذف تمام موارد و پوشه‌های موجود در گاوصندوق خود، از زیر ادامه دهید. مواردی که متعلق به سازمانی است که با آن به اشتراک می‌گذارید، حذف نخواهند شد." }, "purgeOrgVaultDesc": { - "message": "Proceed below to delete all items in the organization's vault." + "message": "برای حذف همه موارد موجود در گاوصندوق سازمان به ادامه مطلب بروید." }, "purgeVaultWarning": { - "message": "Purging your vault is permanent. It cannot be undone." + "message": "پاکسازی گاوصندوق شما دائمی است. نمی‌توان آن را برگرداند." }, "vaultPurged": { - "message": "Vault purged." + "message": "گاوصندوق پاکسازی شد." }, "deleteAccount": { - "message": "Delete account" + "message": "حذف حساب" }, "deleteAccountDesc": { - "message": "Proceed below to delete your account and all vault data." + "message": "برای حذف حساب کاربری خود و تمام داده‌های گاوصندوق، به زیر ادامه دهید." }, "deleteAccountWarning": { - "message": "Deleting your account is permanent. It cannot be undone." + "message": "حذف حساب شما دائمی است. نمی‌توان آن را برگرداند." }, "accountDeleted": { - "message": "Account deleted" + "message": "حساب حذف شد" }, "accountDeletedDesc": { - "message": "Your account has been closed and all associated data has been deleted." + "message": "حساب شما بسته شد و تمام داده های مرتبط حذف شده است." }, "myAccount": { - "message": "My account" + "message": "حساب من" }, "tools": { - "message": "Tools" + "message": "ابزار" }, "importData": { - "message": "Import data" + "message": "درون ریزی داده" }, "importError": { - "message": "Import error" + "message": "خطای درون ریزی" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "مشکلی با داده‌هایی که سعی کردید درون ریزی کنید وجود داشت. لطفاً خطاهای فهرست شده در زیر را در پرونده‌ی منبع خود برطرف کرده و دوباره امتحان کنید." }, "importSuccess": { - "message": "Data successfully imported" + "message": "داده‌ها با موفقیت درون ریزی شد" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "شما در حال درون ریزی داده به $ORGANIZATION$ هستید. داده‌های شما ممکن است با اعضای این سازمان به اشتراک گذاشته شود. آیا می‌خواهید ادامه دهید؟", "placeholders": { "organization": { "content": "$1", @@ -1181,31 +1181,31 @@ } }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "داده‌ها به درستی قالب بندی نشده اند. لطفاً پرونده‌ی درون ریزی خود را بررسی و دوباره امتحان کنید." }, "importNothingError": { - "message": "Nothing was imported." + "message": "چیزی درون ریزی نشد." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "خطا در رمزگشایی پرونده‌ی درون ریزی شده. کلید رمزگذاری شما با کلید رمزگذاری استفاده شده برای درون ریزی داده‌ها مطابقت ندارد." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "فرمت پرونده‌ی درون ریزی را انتخاب کنید" }, "selectImportFile": { - "message": "Select the import file" + "message": "پرونده‌ی درون ریزی را انتخاب کنید" }, "chooseFile": { - "message": "Choose File" + "message": "انتخاب پرونده" }, "noFileChosen": { - "message": "No file chosen" + "message": "هیچ پرونده‌ای انتخاب نشده" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "یا محتویات پرونده‌ی درون ریزی را کپی/پیست کنید" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "دستورالعمل های $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -1215,68 +1215,68 @@ } }, "options": { - "message": "Options" + "message": "گزینه‌ها" }, "preferences": { - "message": "Preferences" + "message": "تنظیمات" }, "preferencesDesc": { - "message": "Customize your web vault experience." + "message": "تجربه‌ی گاوصندوق وب خود را سفارشی کنید." }, "preferencesUpdated": { - "message": "Preferences saved" + "message": "تنظیمات ذخیره شد" }, "language": { - "message": "Language" + "message": "زبان" }, "languageDesc": { - "message": "Change the language used by the web vault." + "message": "زبان مورد استفاده در گاوصندوق وب را تغییر دهید." }, "enableFavicon": { - "message": "Show website icons" + "message": "نمایش نمادهای وب‌سایت" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "یک تصویر قابل تشخیص در کنار هر ورود نشان دهید." }, "enableFullWidth": { - "message": "Display full width layout", + "message": "نمایش طرح با عرض کامل", "description": "Allows scaling the web vault UI's width" }, "enableFullWidthDesc": { - "message": "Allow the web vault to expand the full width of the browser window." + "message": "به گاوصندوق وب اجازه دهید تا تمام عرض پنجره مرورگر را گسترش دهد." }, "default": { - "message": "Default" + "message": "پیش‌فرض" }, "domainRules": { - "message": "Domain rules" + "message": "قوانین دامنه" }, "domainRulesDesc": { - "message": "If you have the same login across multiple different website domains, you can mark the website as \"equivalent\". \"Global\" domains are ones already created for you by Bitwarden." + "message": "اگر یک ورود در چند دامنه‌ی وب‌سایت مختلف دارید، می‌توانید وب‌سایت را به عنوان \"برابر\" علامت گذاری کنید. دامنه‌های «جهانی» دامنه‌هایی هستند که قبلاً توسط Bitwarden برای شما ایجاد شده‌اند." }, "globalEqDomains": { - "message": "Global equivalent domains" + "message": "دامنه‌های برابر جهانی" }, "customEqDomains": { - "message": "Custom equivalent domains" + "message": "دامنه‌های برابر سفارشی" }, "exclude": { - "message": "Exclude" + "message": "استثناء" }, "include": { - "message": "Include" + "message": "شامل" }, "customize": { - "message": "Customize" + "message": "سفارشی" }, "newCustomDomain": { - "message": "New custom domain" + "message": "دامنه شخصی جدید" }, "newCustomDomainDesc": { - "message": "Enter a list of domains separated by commas. Only \"base\" domains are allowed. Do not enter subdomains. For example, enter \"google.com\" instead of \"www.google.com\". You can also enter \"androidapp://package.name\" to associate an android app with other website domains." + "message": "لیستی از دامنه‌ها را وارد کنید که با کاما از هم جدا شده اند. فقط دامنه‌های \"پایه\" مجاز هستند. وارد زیر دامنه‌ها نشوید. برای مثال به جای ‌\"google.com\" ،\"www.google.com\" را وارد کنید. همچنین می‌توانید «androidapp://package.name» را وارد کنید تا یک برنامه اندرویدی را با سایر دامنه‌های وب‌سایت مرتبط کنید." }, "customDomainX": { - "message": "Custom domain $INDEX$", + "message": "دامنه سفارشی $INDEX$", "placeholders": { "index": { "content": "$1", @@ -1285,148 +1285,148 @@ } }, "domainsUpdated": { - "message": "Domains saved" + "message": "دامنه‌ها ذخیره شدند" }, "twoStepLogin": { - "message": "Two-step login" + "message": "ورود دو مرحله ای" }, "twoStepLoginEnforcement": { - "message": "Two-step Login Enforcement" + "message": "اجرای دو مرحله ای ورود به سیستم" }, "twoStepLoginDesc": { - "message": "Secure your account by requiring an additional step when logging in." + "message": "با درخواست یک مرحله اضافی هنگام ورود، حساب خود را ایمن کنید." }, "twoStepLoginOrganizationDescStart": { - "message": "Enforce Bitwarden Two-step Login options for members by using the ", + "message": "گزینه‌های ورود دو مرحله ای Bitwarden را برای اعضا اعمال کنید با استفاده از ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { - "message": "Two-step Login Policy" + "message": "سیاست ورود دو مرحله‌ای" }, "twoStepLoginOrganizationDuoDesc": { - "message": "To enforce Two-step Login through Duo, use the options below." + "message": "برای اعمال ورود دو مرحله‌ای از طریق Duo، از گزینه‌های زیر استفاده کنید." }, "twoStepLoginOrganizationSsoDesc": { - "message": "If you have setup SSO or plan to, Two-step Login may already be enforced through your Identity Provider." + "message": "اگر SSO را راه‌اندازی کرده‌اید یا قصد دارید، ورود دو مرحله‌ای ممکن است از قبل از طریق ارائه‌دهنده هویت شما اعمال شود." }, "twoStepLoginRecoveryWarning": { - "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." + "message": "راه‌اندازی ورود دو مرحله‌ای می‌تواند برای همیشه حساب Bitwarden شما را قفل کند. یک کد بازیابی به شما امکان می‌دهد در صورتی که دیگر نمی‌توانید از ارائه‌دهنده‌ی ورود دو مرحله‌ای معمولی خود استفاده کنید (به عنوان مثال: دستگاه خود را گم می‌کنید) به حساب خود دسترسی پیدا کنید. اگر دسترسی به حساب خود را از دست بدهید، پشتیبانی Bitwarden نمی‌تواند به شما کمک کند. توصیه می‌کنیم کد بازیابی را یادداشت یا چاپ کنید و آن را در مکانی امن نگهداری کنید." }, "viewRecoveryCode": { - "message": "View recovery code" + "message": "نمایش کد بازیابی" }, "providers": { - "message": "Providers", + "message": "ارائه دهندگان", "description": "Two-step login providers such as YubiKey, Duo, Authenticator apps, Email, etc." }, "enable": { - "message": "Turn on" + "message": "روشن" }, "enabled": { - "message": "Turned on" + "message": "روشن شده" }, "restoreAccess": { - "message": "Restore access" + "message": "بازیابی دسترسی" }, "premium": { - "message": "Premium", + "message": "پرمیوم", "description": "Premium membership" }, "premiumMembership": { - "message": "Premium membership" + "message": "عضویت پرمیوم" }, "premiumRequired": { - "message": "Premium required" + "message": "در نسخه پرمیوم کار می‌کند" }, "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." + "message": "برای استفاده از این ویژگی عضویت پرمیوم لازم است." }, "youHavePremiumAccess": { - "message": "You have Premium access" + "message": "شما دسترسی پرمیوم دارید" }, "alreadyPremiumFromOrg": { - "message": "You already have access to Premium features because of an organization you are a member of." + "message": "شما از قبل به دلیل سازمانی که در آن عضو هستید به ویژگی‌های پرمیوم دسترسی دارید." }, "manage": { - "message": "Manage" + "message": "مدیریت" }, "disable": { - "message": "Turn off" + "message": "خاموش کردن" }, "revokeAccess": { - "message": "Revoke access" + "message": "لغو دسترسی" }, "twoStepLoginProviderEnabled": { - "message": "This two-step login provider is active on your account." + "message": "این ارائه دهنده ورود به سیستم دو مرحله ای در حساب شما فعال است." }, "twoStepLoginAuthDesc": { - "message": "Enter your master password to modify two-step login settings." + "message": "کلمه عبور اصلی خود را برای تغییر تنظیمات ورود به سیستم دو مرحله ای وارد کنید." }, "twoStepAuthenticatorDesc": { - "message": "Follow these steps to set up two-step login with an authenticator app:" + "message": "برای راه‌اندازی ورود دو مرحله‌ای با یک برنامه احراز هویت، این مراحل را دنبال کنید:" }, "twoStepAuthenticatorDownloadApp": { - "message": "Download a two-step authenticator app" + "message": "دانلود یک برنامه احراز هویت دو مرحله ای" }, "twoStepAuthenticatorNeedApp": { - "message": "Need a two-step authenticator app? Download one of the following" + "message": "به یک برنامه احراز هویت دو مرحله ای نیاز دارید؟ یکی از موارد زیر را دانلود کنید" }, "iosDevices": { - "message": "iOS devices" + "message": "دستگاه‌های iOS" }, "androidDevices": { - "message": "Android devices" + "message": "دستگاه Android" }, "windowsDevices": { - "message": "Windows devices" + "message": "دستگاه Windows" }, "twoStepAuthenticatorAppsRecommended": { - "message": "These apps are recommended, however, other authenticator apps will also work." + "message": "این برنامه‌ها توصیه می‌شوند، با این حال، سایر برنامه‌های احراز هویت نیز کار خواهند کرد." }, "twoStepAuthenticatorScanCode": { - "message": "Scan this QR code with your authenticator app" + "message": "این کد QR را با برنامه احراز هویت خود اسکن کنید" }, "key": { - "message": "Key" + "message": "کلید" }, "twoStepAuthenticatorEnterCode": { - "message": "Enter the resulting 6 digit verification code from the app" + "message": "کد تأیید 6 رقمی حاصل را از برنامه وارد کنید" }, "twoStepAuthenticatorReaddDesc": { - "message": "In case you need to add it to another device, below is the QR code (or key) required by your authenticator app." + "message": "در صورت نیاز به افزودن آن به دستگاه دیگر، کد QR (یا کلید) مورد نیاز برنامه احراز هویت شما در زیر آمده است." }, "twoStepDisableDesc": { - "message": "Are you sure you want to turn off this two-step login provider?" + "message": "آیا مطمئنید که می‌خواهید این ارائه دهنده ورود به سیستم دو مرحله ای را خاموش کنید؟" }, "twoStepDisabled": { - "message": "Two-step login provider turned off." + "message": "ارائه دهنده ورود به سیستم دو مرحله ای خاموش است." }, "twoFactorYubikeyAdd": { - "message": "Add a new YubiKey to your account" + "message": "یک YubiKey جدید به حساب خود اضافه کنید" }, "twoFactorYubikeyPlugIn": { - "message": "Plug the YubiKey into your computer's USB port." + "message": "YubiKey را به پورت USB رایانه خود وصل کنید." }, "twoFactorYubikeySelectKey": { - "message": "Select the first empty YubiKey input field below." + "message": "اولین فیلد خالی ورودی YubiKey را در زیر انتخاب کنید." }, "twoFactorYubikeyTouchButton": { - "message": "Touch the YubiKey's button." + "message": "دکمه YubiKey را لمس کنید." }, "twoFactorYubikeySaveForm": { - "message": "Save the form." + "message": "فرم را ذخیره کن." }, "twoFactorYubikeyWarning": { - "message": "Due to platform limitations, YubiKeys cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when YubiKeys cannot be used. Supported platforms:" + "message": "به دلیل محدودیت‌های پلتفرم، YubiKeys نمی‌تواند در همه برنامه‌های Bitwarden استفاده شود. باید یک ارائه‌دهنده‌ی ورود دو مرحله‌ای دیگر راه‌اندازی کنید تا زمانی که YubiKeys قابل استفاده نیست، بتوانید به حساب خود دسترسی داشته باشید. پلتفرم های پشتیبانی شده:" }, "twoFactorYubikeySupportUsb": { - "message": "Web vault, desktop application, CLI, and all browser extensions on a device with a USB port that can accept your YubiKey." + "message": "گاوصندوق وب، برنامه دسکتاپ، CLI و همه برنامه‌های افزودنی مرورگر در دستگاهی با پورت USB که می‌تواند YubiKey شما را بپذیرد." }, "twoFactorYubikeySupportMobile": { - "message": "Mobile apps on a device with NFC capabilities or a data port that can accept your YubiKey." + "message": "برنامه‌های تلفن همراه در دستگاهی با قابلیت NFC یا درگاه داده ای که می‌تواند YubiKey شما را بپذیرد." }, "yubikeyX": { - "message": "YubiKey $INDEX$", + "message": "$INDEX$ YubiKey", "placeholders": { "index": { "content": "$1", @@ -1435,7 +1435,7 @@ } }, "u2fkeyX": { - "message": "U2F Key $INDEX$", + "message": "$INDEX$ کلید U2F", "placeholders": { "index": { "content": "$1", @@ -1444,7 +1444,7 @@ } }, "webAuthnkeyX": { - "message": "WebAuthn Key $INDEX$", + "message": "$INDEX$ کلید WebAuthn", "placeholders": { "index": { "content": "$1", @@ -1453,124 +1453,124 @@ } }, "nfcSupport": { - "message": "NFC Support" + "message": "پشتیبانی NFC" }, "twoFactorYubikeySupportsNfc": { - "message": "One of my keys supports NFC." + "message": "یکی از کلیدهای من از NFC پشتیبانی می‌کند." }, "twoFactorYubikeySupportsNfcDesc": { - "message": "If one of your YubiKeys supports NFC (such as a YubiKey NEO), you will be prompted on mobile devices whenever NFC availability is detected." + "message": "اگر یکی از YubiKey های شما از NFC پشتیبانی می‌کند (مانند YubiKey NEO)، هر زمان که در دسترس بودن NFC تشخیص داده شود، در دستگاه‌های تلفن همراه از شما خواسته می‌شود." }, "yubikeysUpdated": { - "message": "YubiKeys updated" + "message": "YubiKeys به‌روزرسانی شد" }, "disableAllKeys": { - "message": "Deactivate all keys" + "message": "غیرفعال کردن تمام کلیدها" }, "twoFactorDuoDesc": { - "message": "Enter the Bitwarden application information from your Duo Admin panel." + "message": "اطلاعات برنامه Bitwarden را از پنل مدیریت Duo خود وارد کنید." }, "twoFactorDuoIntegrationKey": { - "message": "Integration key" + "message": "کلید یکپارچه سازی" }, "twoFactorDuoSecretKey": { - "message": "Secret key" + "message": "کلید مخفی" }, "twoFactorDuoApiHostname": { - "message": "API hostname" + "message": "نام میزبان API" }, "twoFactorEmailDesc": { - "message": "Follow these steps to set up two-step login with email:" + "message": "برای راه‌اندازی ورود دو مرحله‌ای با یک ایمیل، این مراحل را دنبال کنید:" }, "twoFactorEmailEnterEmail": { - "message": "Enter the email that you wish to receive verification codes" + "message": "ایمیلی را که می‌خواهید کدهای تأیید را دریافت کند وارد کنید" }, "twoFactorEmailEnterCode": { - "message": "Enter the resulting 6 digit verification code from the email" + "message": "کد تأیید 6 رقمی حاصل را از ایمیل وارد کنید" }, "sendEmail": { - "message": "Send email" + "message": "ارسال ایمیل" }, "twoFactorU2fAdd": { - "message": "Add a FIDO U2F security key to your account" + "message": "یک کلید امنیتی FIDO U2F به حساب خود اضافه کنید" }, "removeU2fConfirmation": { - "message": "Are you sure you want to remove this security key?" + "message": "مطمئنید که می‌خواهید این کلید امنیتی را حذف کنید؟" }, "twoFactorWebAuthnAdd": { - "message": "Add a WebAuthn security key to your account" + "message": "یک کلید امنیتی WebAuthn به حساب خود اضافه کنید" }, "readKey": { - "message": "Read key" + "message": "خواندن کلید" }, "keyCompromised": { - "message": "Key is compromised." + "message": "کلید به خطر افتاده است." }, "twoFactorU2fGiveName": { - "message": "Give the security key a friendly name to identify it." + "message": "برای شناسایی کلید امنیتی یک نام مناسب انتخاب کنید." }, "twoFactorU2fPlugInReadKey": { - "message": "Plug the security key into your computer's USB port and click the \"Read Key\" button." + "message": "کلید امنیتی را به پورت USB کامپیوتر خود وصل کنید و روی دکمه‌ی \"خواندن کلید\" کلیک کنید." }, "twoFactorU2fTouchButton": { - "message": "If the security key has a button, touch it." + "message": "اگر کلید امنیتی دکمه ای دارد، آن را لمس کنید." }, "twoFactorU2fSaveForm": { - "message": "Save the form." + "message": "فرم را ذخیره کن." }, "twoFactorU2fWarning": { - "message": "Due to platform limitations, FIDO U2F cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when FIDO U2F cannot be used. Supported platforms:" + "message": "به دلیل محدودیت‌های پلتفرم، FIDO U2F نمی‌تواند در همه برنامه‌های Bitwarden استفاده شود. باید یک ارائه‌دهنده‌ی ورود دو مرحله‌ای دیگر راه‌اندازی کنید تا زمانی که FIDO U2F قابل استفاده نیست، بتوانید به حساب خود دسترسی داشته باشید. پلتفرم های پشتیبانی شده:" }, "twoFactorU2fSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a U2F supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "message": "گاوصندوق وب و برنامه‌های افزودنی مرورگر روی دسکتاپ/لپ‌تاپ با مرورگر پشتیبانی‌شده از U2F (Chrome، Opera، Vivaldi یا Firefox با FIDO U2F روشن)." }, "twoFactorU2fWaiting": { - "message": "Waiting for you to touch the button on your security key" + "message": "منتظر لمس دکمه روی کلید امنیتی خود" }, "twoFactorU2fClickSave": { - "message": "Use the \"Save\" button below to activate this security key for two-step login." + "message": "از دکمه‌ی «ذخیره» زیر برای فعال کردن این کلید امنیتی برای ورود دو مرحله‌ای استفاده کنید." }, "twoFactorU2fProblemReadingTryAgain": { - "message": "There was a problem reading the security key. Try again." + "message": "مشکلی در خواندن کلید امنیتی وجود داشت. دوباره امتحان کنید." }, "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" + "message": "به دلیل محدودیت‌های پلتفرم، WebAuthn نمی‌تواند در همه برنامه‌های Bitwarden استفاده شود. باید یک ارائه‌دهنده‌ی ورود دو مرحله‌ای دیگر راه‌اندازی کنید تا زمانی که WebAuthn قابل استفاده نیست، بتوانید به حساب خود دسترسی داشته باشید. پلتفرم های پشتیبانی شده:" }, "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "message": "گاوصندوق وب و برنامه‌های افزودنی مرورگر روی دسکتاپ/لپ‌تاپ با مرورگر پشتیبانی‌شده از WebAuthn (Chrome، Opera، Vivaldi یا Firefox با FIDO U2F روشن)." }, "twoFactorRecoveryYourCode": { - "message": "Your Bitwarden two-step login recovery code" + "message": "کد بازیابی ورود دو مرحله ای Bitwarden شما" }, "twoFactorRecoveryNoCode": { - "message": "You have not set up any two-step login providers yet. After you have set up a two-step login provider you can check back here for your recovery code." + "message": "شما هنوز هیچ ارائه دهنده‌ی ورود دو مرحله ای را راه اندازی نکرده اید. پس از راه‌ اندازی یک ارائه‌دهنده ورود به سیستم دو مرحله‌ای، می‌توانید برای کد بازیابی خود دوباره اینجا را بررسی کنید." }, "printCode": { - "message": "Print code", + "message": "چاپ کد", "description": "Print 2FA recovery code" }, "reports": { - "message": "Reports" + "message": "گزارشات" }, "reportsDesc": { - "message": "Identify and close security gaps in your online accounts by clicking the reports below.", + "message": "با کلیک بر روی گزارش‌های زیر، شکاف‌های امنیتی را در حساب‌های آنلاین خود شناسایی و ببندید.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "orgsReportsDesc": { - "message": "Identify and close security gaps in your organization's accounts by clicking the reports below.", + "message": "با کلیک بر روی گزارش‌های زیر، شکاف‌های امنیتی را در حساب‌های سازمان خود شناسایی و ببندید.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization Vault." }, "unsecuredWebsitesReport": { - "message": "Unsecure websites" + "message": "وب‌سایت های نا امن" }, "unsecuredWebsitesReportDesc": { - "message": "URLs that start with http:// don’t use the best available encryption. Change the login URIs for these accounts to https:// for safer browsing." + "message": "نشانی‌های اینترنتی که با ‌‌//:http شروع می شوند از بهترین رمزگذاری موجود استفاده نمی کنند. برای مرور ایمن‌تر، نشانی‌های اینترنتی ورود به این حساب‌ها را به //:https تغییر دهید." }, "unsecuredWebsitesFound": { - "message": "Unsecured websites found" + "message": "وب‌سایت های نا امن پیدا شد" }, "unsecuredWebsitesFoundDesc": { - "message": "We found $COUNT$ items in your vault with unsecured URIs. You should change their URI scheme to https:// if the website allows it.", + "message": "ما $COUNT$ مورد را با نشانی‌های اینترنتی نا امن در خزانه شما پیدا کردیم. اگر وب‌سایت اجازه می‌دهد، باید طرح نشانی اینترنتی آن‌ها را به //:https‌ تغییر دهید.", "placeholders": { "count": { "content": "$1", @@ -1579,19 +1579,19 @@ } }, "noUnsecuredWebsites": { - "message": "No items in your vault have unsecured URIs." + "message": "هیچ موردی در صندوق شما دارای نشانی اینترنتی نا امن نیست." }, "inactive2faReport": { - "message": "Inactive two-step login" + "message": "ورود دو مرحله ای غیر فعال" }, "inactive2faReportDesc": { - "message": "Two-step login adds a layer of protection to your accounts. Set up two-step login using Bitwarden authenticator for these accounts or use an alternative method." + "message": "ورود دو مرحله ای یک لایه‌ی حفاظتی به حساب‌های شما اضافه می‌کند. با استفاده از احراز کننده‌ی هویت Bitwarden برای این حساب‌ها، ورود دو مرحله ای را تنظیم کنید یا از یک روش جایگزین استفاده کنید." }, "inactive2faFound": { - "message": "Logins without two-step login found" + "message": "ورود‌های بدون ورود دو مرحله ای یافت شد" }, "inactive2faFoundDesc": { - "message": "We found $COUNT$ website(s) in your vault that may not be configured with two-step login (according to 2fa.directory). To further protect these accounts, you should set up two-step login.", + "message": "ما $COUNT$ وب‌سایت را در گاوصندوق شما پیدا کردیم که ممکن است با ورود دو مرحله‌ای پیکربندی نشده باشند (بر اساس 2fa.directory). برای محافظت بیشتر از این حساب‌ها، باید ورود دو مرحله ای را تنظیم کنید.", "placeholders": { "count": { "content": "$1", @@ -1600,22 +1600,22 @@ } }, "noInactive2fa": { - "message": "No websites were found in your vault with a missing two-step login configuration." + "message": "هیچ وب‌سایتی در گاوصندوق شما یافت نشد که پیکربندی ورود دو مرحله‌ای نداشته باشد." }, "instructions": { - "message": "Instructions" + "message": "دستور العمل‌ها" }, "exposedPasswordsReport": { - "message": "Exposed passwords" + "message": "کلمه‌های عبور افشا شده" }, "exposedPasswordsReportDesc": { - "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." + "message": "کلمه‌های عبور افشا شده در نقض داده، اهداف آسانی برای مهاجمان هستند. برای جلوگیری از نفوذ احتمالی، این کلمه‌های عبور را تغییر دهید." }, "exposedPasswordsFound": { - "message": "Exposed passwords found" + "message": "کلمه‌های عبور افشا شده یافت شد" }, "exposedPasswordsFoundDesc": { - "message": "We found $COUNT$ items in your vault that have passwords that were exposed in known data breaches. You should change them to use a new password.", + "message": "ما $COUNT$ موردی را در خزانه شما پیدا کردیم که دارای کلمه‌های عبوری هستند که در نقض‌های اطلاعاتی شناخته شده افشا شده‌اند. شما باید آن‌ها را تغییر دهید تا از یک کلمه عبور جدید استفاده کنید.", "placeholders": { "count": { "content": "$1", @@ -1624,13 +1624,13 @@ } }, "noExposedPasswords": { - "message": "No items in your vault have passwords that have been exposed in known data breaches." + "message": "هیچ موردی در گلوصندوق شما دارای کلمه‌های عبوری نیست که در نقض‌های اطلاعاتی شناخته شده در معرض دید قرار گرفته باشد." }, "checkExposedPasswords": { - "message": "Check exposed passwords" + "message": "کلمه‌های عبور افشا شده را بررسی کنید" }, "exposedXTimes": { - "message": "Exposed $COUNT$ time(s)", + "message": "$COUNT$ بار در معرض نمایش قرار گرفت", "placeholders": { "count": { "content": "$1", @@ -1639,16 +1639,16 @@ } }, "weakPasswordsReport": { - "message": "Weak passwords" + "message": "کلمه عبور ضعیف" }, "weakPasswordsReportDesc": { - "message": "Weak passwords can be easily guessed by attackers. Change these passwords to strong ones using the password generator." + "message": "کلمه‌های عبور ضعیف توسط مهاجمان به راحتی قابل حدس زدن هستند. این کلمه‌های عبور را با استفاده از تولید کننده کلمه عبور به کلمه‌های عبور قوی تغییر دهید." }, "weakPasswordsFound": { - "message": "Weak passwords found" + "message": "کلمه‌های عبور ضعیف پیدا شد" }, "weakPasswordsFoundDesc": { - "message": "We found $COUNT$ items in your vault with passwords that are not strong. You should update them to use stronger passwords.", + "message": "ما $COUNT$ مورد با کلمه‌های عبوری که قوی نیستند در گاوصندوق شما پیدا کردیم. شما باید آن‌ها را به‌روز کنید تا از کلمه‌های عبور قوی تر استفاده کنید.", "placeholders": { "count": { "content": "$1", @@ -1657,19 +1657,19 @@ } }, "noWeakPasswords": { - "message": "No items in your vault have weak passwords." + "message": "هیچ موردی در گاوصندوق شما کلمه‌ی عبور ضعیفی ندارد." }, "reusedPasswordsReport": { - "message": "Reused passwords" + "message": "کلمه‌های عبور مجدد استفاده شده" }, "reusedPasswordsReportDesc": { - "message": "Reusing passwords makes it easier for attackers to break into multiple accounts. Change these passwords so that each is unique." + "message": "استفاده‌ی مجدد از کلمه‌های عبور نفوذ به چندین حساب را برای مهاجمان آسان‌تر می‌کند. این کلمه‌های عبور را طوری تغییر دهید که هر کدام منحصر به فرد باشد." }, "reusedPasswordsFound": { - "message": "Reused passwords found" + "message": "کلمه‌های عبور مجدد استفاده شده یافت شد" }, "reusedPasswordsFoundDesc": { - "message": "We found $COUNT$ passwords that are being reused in your vault. You should change them to a unique value.", + "message": "ما $COUNT$ کلمه عبور پیدا کردیم که در گاوضندوق شما دوباره استفاده می‌شود. شما باید آن‌ها را به یک مقدار منحصر به فرد تغییر دهید.", "placeholders": { "count": { "content": "$1", @@ -1678,10 +1678,10 @@ } }, "noReusedPasswords": { - "message": "No logins in your vault have passwords that are being reused." + "message": "هیچ ورودی در گاوصندوق شما کلمه عبوری ندارد که مجدداً مورد استفاده قرار می‌گیرد." }, "reusedXTimes": { - "message": "Reused $COUNT$ times", + "message": "$COUNT$ بار دوباره استفاده شد", "placeholders": { "count": { "content": "$1", @@ -1690,19 +1690,19 @@ } }, "dataBreachReport": { - "message": "Data breach" + "message": "نقض داده‌ها" }, "breachDesc": { - "message": "Breached accounts can expose your personal information. Secure breached accounts by enabling 2FA or creating a stronger password." + "message": "حساب‌های نقض شده می‌تواند اطلاعات شخصی شما را فاش کند. با فعال کردن 2FA یا ایجاد یک کلمه عبور قوی تر، حساب های نقض شده را ایمن کنید." }, "breachCheckUsernameEmail": { - "message": "Check any usernames or email addresses that you use." + "message": "نام کاربری یا نسانی ایمیلی که استفاده می‌کنید را بررسی کنید." }, "checkBreaches": { - "message": "Check breaches" + "message": "موارد نقض را بررسی کنید" }, "breachUsernameNotFound": { - "message": "$USERNAME$ was not found in any known data breaches.", + "message": "$USERNAME$ در هیچ نقض شناخته شده داده ای یافت نشد.", "placeholders": { "username": { "content": "$1", @@ -1711,11 +1711,11 @@ } }, "goodNews": { - "message": "Good news", + "message": "خبر خوب", "description": "ex. Good News, No Breached Accounts Found!" }, "breachUsernameFound": { - "message": "$USERNAME$ was found in $COUNT$ different data breaches online.", + "message": "$USERNAME$ در $COUNT$ نقض داده‌های مختلف آنلاین پیدا شد.", "placeholders": { "username": { "content": "$1", @@ -1728,93 +1728,93 @@ } }, "breachFound": { - "message": "Breached accounts found" + "message": "حساب‌های نقض شده پیدا شد" }, "compromisedData": { - "message": "Compromised data" + "message": "داده‌های به خطر افتاده" }, "website": { - "message": "Website" + "message": "وب‌سایت" }, "affectedUsers": { - "message": "Affected users" + "message": "کاربران تحت تأثیر" }, "breachOccurred": { - "message": "Breach occurred" + "message": "نقض رخ داد" }, "breachReported": { - "message": "Breach reported" + "message": "نقض گزارش شد" }, "reportError": { - "message": "An error occurred trying to load the report. Try again" + "message": "هنگام بارگیری گزارش خطایی روی داد. دوباره امتحان کنید" }, "billing": { - "message": "Billing" + "message": "صورتحساب" }, "billingPlanLabel": { - "message": "Billing plan" + "message": "طرح صورتحساب" }, "paymentType": { - "message": "Payment type" + "message": "نوع پرداخت" }, "accountCredit": { - "message": "Account credit", + "message": "اعتبار حساب", "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." }, "accountBalance": { - "message": "Account balance", + "message": "مانده حساب", "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." }, "addCredit": { - "message": "Add credit", + "message": "افزودن اعتبار", "description": "Add more credit to your account's balance." }, "amount": { - "message": "Amount", + "message": "مقدار", "description": "Dollar amount, or quantity." }, "creditDelayed": { - "message": "Added credit will appear on your account after the payment has been fully processed. Some payment methods are delayed and can take longer to process than others." + "message": "اعتبار اضافه شده پس از پردازش کامل پرداخت در حساب شما ظاهر می‌شود. برخی از روش‌های پرداخت با تأخیر مواجه می‌شوند و ممکن است پردازش آنها بیشتر از سایر روش‌ها طول بکشد." }, "makeSureEnoughCredit": { - "message": "Please make sure that your account has enough credit available for this purchase. If your account does not have enough credit available, your default payment method on file will be used for the difference. You can add credit to your account from the Billing page." + "message": "لطفاً مطمئن شوید که اعتبار حساب شما برای این خرید کافی است. اگر حساب شما اعتبار کافی ندارد، از روش پرداخت پیش‌فرض موجود در پرونده برای ما به‌ تفاوت استفاده می‌شود. می‌توانید از صفحه صورتحساب اعتبار به حساب خود اضافه کنید." }, "creditAppliedDesc": { - "message": "Your account's credit can be used to make purchases. Any available credit will be automatically applied towards invoices generated for this account." + "message": "اعتبار حساب شما می‌تواند برای خرید استفاده شود. هر اعتبار موجود به طور خودکار برای فاکتورهای ایجاد شده برای این حساب اعمال می‌شود." }, "goPremium": { - "message": "Go Premium", + "message": "عضو پرمیوم شوید", "description": "Another way of saying \"Get a Premium membership\"" }, "premiumUpdated": { - "message": "You've upgraded to Premium." + "message": "شما به پرمیوم ارتقاء یافتید." }, "premiumUpgradeUnlockFeatures": { - "message": "Upgrade your account to a Premium membership and unlock some great additional features." + "message": "حساب خود را به عضویت پرمیوم ارتقا دهید و برخی از ویژگی‌های فوق‌العاده را باز کنید." }, "premiumSignUpStorage": { - "message": "1 GB encrypted storage for file attachments." + "message": "۱ گیگابایت فضای ذخیره‌سازی رمزنگاری شده برای پرونده‌های پیوست." }, "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "message": "گزینه‌های ورود دو مرحله‌ای اضافی مانند YubiKey, FIDO U2F و Duo." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "دسترسی اضطراری" }, "premiumSignUpReports": { - "message": "Password hygiene, account health, and data breach reports to keep your vault safe." + "message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." }, "premiumSignUpTotp": { - "message": "TOTP verification code (2FA) generator for logins in your vault." + "message": "تولید کننده کد تأیید (2FA) از نوع TOTP برای ورودهای موجود در گاوصندوقتان." }, "premiumSignUpSupport": { - "message": "Priority customer support." + "message": "اولویت پشتیبانی از مشتری." }, "premiumSignUpFuture": { - "message": "All future Premium features. More coming soon!" + "message": "تمام ویژگی‌های پرمیوم آینده. به زودی بیشتر!" }, "premiumPrice": { - "message": "All for just $PRICE$ /year!", + "message": "تمامش فقط $PRICE$ در سال!", "placeholders": { "price": { "content": "$1", @@ -1822,14 +1822,30 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "فقط با $PRICE$ در سال کاربر پرمیوم شویید یا برای کاربران $FAMILYPLANUSERCOUNT$ حساب‌های پرمیوم و اشتراک‌گذاری نامحدود خانواده با ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "طرح خانواده Bitwarden." + }, "addons": { - "message": "Addons" + "message": "افزودنی ها" }, "premiumAccess": { - "message": "Premium access" + "message": "دسترسی پرمیوم" }, "premiumAccessDesc": { - "message": "You can add Premium access to all members of your organization for $PRICE$ /$INTERVAL$.", + "message": "می‌توانید دسترسی پرمیوم را با قیمت $PRICE$ /$INTERVAL$ به همه اعضای سازمان خود اضافه کنید.", "placeholders": { "price": { "content": "$1", @@ -1842,13 +1858,13 @@ } }, "additionalStorageGb": { - "message": "Additional storage (GB)" + "message": "ذخیره‌سازی های اضافی (GB)" }, "additionalStorageGbDesc": { - "message": "# of additional GB" + "message": "# گیگابایت اضافی" }, "additionalStorageIntervalDesc": { - "message": "Your plan comes with $SIZE$ of encrypted file storage. You can add additional storage for $PRICE$ per GB /$INTERVAL$.", + "message": "طرح شما با $SIZE$ فضای ذخیره‌سازی پرونده رمزگذاری شده ارائه می‌شود. می‌توانید فضای ذخیره‌سازی اضافی را به قیمت $PRICE$ در هر گیگابایت /$INTERVAL$ اضافه کنید.", "placeholders": { "size": { "content": "$1", @@ -1865,29 +1881,29 @@ } }, "summary": { - "message": "Summary" + "message": "خلاصه" }, "total": { - "message": "Total" + "message": "مجموع" }, "year": { - "message": "year" + "message": "سال" }, "yr": { - "message": "yr" + "message": "سال" }, "month": { - "message": "month" + "message": "ماه" }, "monthAbbr": { - "message": "mo.", + "message": "ماه.", "description": "Short abbreviation for 'month'" }, "paymentChargedAnnually": { - "message": "Your payment method will be charged immediately and then on a recurring basis each year. You may cancel at any time." + "message": "روش پرداخت شما بلافاصله و سپس به صورت مکرر هر سال کسر می‌شود. هر زمان که مایل بودید می‌توانید آن را لغو کنید." }, "paymentCharged": { - "message": "Your payment method will be charged immediately and then on a recurring basis each $INTERVAL$. You may cancel at any time.", + "message": "روش پرداخت شما بلافاصله و سپس به صورت مکرر هر $INTERVAL$ کسر می‌شود. هر زمان که مایل بودید می‌توانید آن را لغو کنید.", "placeholders": { "interval": { "content": "$1", @@ -1896,85 +1912,85 @@ } }, "paymentChargedWithTrial": { - "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." + "message": "طرح شما با یک دوره آزمایشی رایگان ۷ روزه همراه است. تا پایان دوره آزمایشی از روش پرداخت شما هزینه ای کسر نمی‌شود. هر زمان که مایل بودید می‌توانید آن را لغو کنید." }, "paymentInformation": { - "message": "Payment information" + "message": "اطلاعات پرداخت" }, "billingInformation": { - "message": "Billing information" + "message": "اطلاعات صورتحساب" }, "billingTrialSubLabel": { - "message": "Your payment method will not be charged during the 7 day free trial." + "message": "هزینه ای از روش پرداخت شما در طول دوره آزمایشی رایگان ۷ روزه کسر نخواهد شد." }, "creditCard": { - "message": "Credit card" + "message": "کارت اعتباری" }, "paypalClickSubmit": { - "message": "Select the PayPal button to log into your PayPal account, then click the Submit button below to continue." + "message": "دکمه PayPal را برای ورود به حساب PayPal خود انتخاب کنید، سپس برای ادامه روی دکمه Submit در زیر کلیک کنید." }, "cancelSubscription": { - "message": "Cancel subscription" + "message": "لغو اشتراک" }, "subscriptionCanceled": { - "message": "The subscription has been canceled." + "message": "اشتراک شما لغو گردید." }, "pendingCancellation": { - "message": "Pending cancellation" + "message": "در انتظار لغو" }, "subscriptionPendingCanceled": { - "message": "The subscription has been marked for cancellation at the end of the current billing period." + "message": "اشتراک برای لغو در پایان دوره‌ی صورتحساب فعلی علامت‌گذاری شده است." }, "reinstateSubscription": { - "message": "Reinstate subscription" + "message": "اشتراک را دوباره برقرار کنید" }, "reinstateConfirmation": { - "message": "Are you sure you want to remove the pending cancellation request and reinstate your subscription?" + "message": "آیا مطمئنید که می‌خواهید درخواست لغو معلق را حذف کنید و اشتراک خود را دوباره برقرار کنید؟" }, "reinstated": { - "message": "The subscription has been reinstated." + "message": "اشتراک دوباره برقرار شده است." }, "cancelConfirmation": { - "message": "Are you sure you want to cancel? You will lose access to all of this subscription's features at the end of this billing cycle." + "message": "آیا مطمئنید که می‌خواهید لغو کنید؟ در پایان این چرخه صورتحساب، دسترسی به همه ویژگی‌های این اشتراک را از دست خواهید داد." }, "canceledSubscription": { - "message": "Subscription canceled" + "message": "اشتراک لغو شد" }, "neverExpires": { - "message": "Never expires" + "message": "هرگز منقضی نمی‌شود" }, "status": { - "message": "Status" + "message": "وضعیت" }, "nextCharge": { - "message": "Next charge" + "message": "شارژ بعدی" }, "details": { - "message": "Details" + "message": "جزئیات" }, "downloadLicense": { - "message": "Download license" + "message": "دانلود مجوز" }, "updateLicense": { - "message": "Update license" + "message": "به‌روزرسانی مجوز" }, "updatedLicense": { - "message": "Updated license" + "message": "مجوز به‌روزرسانی شد" }, "manageSubscription": { - "message": "Manage subscription" + "message": "مدیریت اشتراک" }, "storage": { - "message": "Storage" + "message": "حافظه" }, "addStorage": { - "message": "Add storage" + "message": "افزودن حافظه" }, "removeStorage": { - "message": "Remove storage" + "message": "حذف حافظه" }, "subscriptionStorage": { - "message": "Your subscription has a total of $MAX_STORAGE$ GB of encrypted file storage. You are currently using $USED_STORAGE$.", + "message": "اشتراک شما در مجموع $MAX_STORAGE$ گیگابایت حافظه‌ی پرونده‌ی رمزگذاری شده دارد. شما در حال حاضر از $USED_STORAGE$ استفاده می‌کنید.", "placeholders": { "max_storage": { "content": "$1", @@ -1987,48 +2003,48 @@ } }, "paymentMethod": { - "message": "Payment method" + "message": "روش پرداخت" }, "noPaymentMethod": { - "message": "No payment method on file." + "message": "هیچ روش پرداختی در پرونده وجود ندارد." }, "addPaymentMethod": { - "message": "Add payment method" + "message": "افزودن روش پرداخت" }, "changePaymentMethod": { - "message": "Change payment method" + "message": "تغییر روش پرداخت" }, "invoices": { - "message": "Invoices" + "message": "صورت حساب‌ها" }, "noInvoices": { - "message": "No invoices." + "message": "صورت حسابی وجود ندارد." }, "paid": { - "message": "Paid", + "message": "پرداخت شد", "description": "Past tense status of an invoice. ex. Paid or unpaid." }, "unpaid": { - "message": "Unpaid", + "message": "پرداخت نشده", "description": "Past tense status of an invoice. ex. Paid or unpaid." }, "transactions": { - "message": "Transactions", + "message": "تراکنش‌ها", "description": "Payment/credit transactions." }, "noTransactions": { - "message": "No transactions." + "message": "هیچ تراکنشی وجود ندارد." }, "chargeNoun": { - "message": "Charge", + "message": "شارژ", "description": "Noun. A charge from a payment method." }, "refundNoun": { - "message": "Refund", + "message": "بازپرداخت", "description": "Noun. A refunded payment that was charged." }, "chargesStatement": { - "message": "Any charges will appear on your statement as $STATEMENT_NAME$.", + "message": "هر هزینه‌ای در صورتحساب شما به عنوان $STATEMENT_NAME$ نشان داده می‌شود.", "placeholders": { "statement_name": { "content": "$1", @@ -2037,19 +2053,19 @@ } }, "gbStorageAdd": { - "message": "GB of storage to add" + "message": "گیگابایت فضای ذخیره سازی برای اضافه کردن" }, "gbStorageRemove": { - "message": "GB of storage to remove" + "message": "گیگابایت فضای ذخیره سازی برای حذف کردن" }, "storageAddNote": { - "message": "Adding storage will result in adjustments to your billing totals and immediately charge your payment method on file. The first charge will be prorated for the remainder of the current billing cycle." + "message": "افزودن فضای ذخیره‌سازی منجر به تغییراتی در مجموع صورتحساب شما می‌شود و بلافاصله از روش پرداخت شما در پرونده کسر می‌شود. اولین هزینه برای باقیمانده‌ی چرخه صورتحساب فعلی به نسبت محاسبه می‌شود." }, "storageRemoveNote": { - "message": "Removing storage will result in adjustments to your billing totals that will be prorated as credits toward your next billing charge." + "message": "حذف فضای ذخیره‌سازی منجر به تغییراتی در مجموع صورتحساب شما می‌شود که به عنوان اعتبار نسبت به هزینه صورتحساب بعدی شما تقسیم می‌شود." }, "adjustedStorage": { - "message": "Adjusted $AMOUNT$ GB of storage.", + "message": "$AMOUNT$ گیگابایت فضای ذخیره سازی تنظیم شده است.", "placeholders": { "amount": { "content": "$1", @@ -2058,19 +2074,19 @@ } }, "contactSupport": { - "message": "Contact customer support" + "message": "با پشتیبانی مشتری تماس بگیرید" }, "updatedPaymentMethod": { - "message": "Updated payment method." + "message": "روش پرداخت به‌روز شده." }, "purchasePremium": { - "message": "Purchase Premium" + "message": "خرید پرمیوم" }, "licenseFile": { - "message": "License file" + "message": "پرونده مجوز" }, "licenseFileDesc": { - "message": "Your license file will be named something like $FILE_NAME$", + "message": "نام پرونده مجوز شما چیزی شبیه $FILE_NAME$ خواهد بود", "placeholders": { "file_name": { "content": "$1", @@ -2079,49 +2095,49 @@ } }, "uploadLicenseFilePremium": { - "message": "To upgrade your account to a Premium membership you need to upload a valid license file." + "message": "برای ارتقاء حساب خود به عضویت پرمیوم، باید یک پرونده‌ی مجوز معتبر آپلود کنید." }, "uploadLicenseFileOrg": { - "message": "To create an on-premises hosted organization you need to upload a valid license file." + "message": "برای ایجاد یک سازمان میزبانی شده در محل، باید یک پرونده‌ی مجوز معتبر آپلود کنید." }, "accountEmailMustBeVerified": { - "message": "Your account's email address must be verified." + "message": "نشانی ایمیل حساب کاربری شما باید تأیید شود." }, "newOrganizationDesc": { - "message": "Organizations allow you to share parts of your vault with others as well as manage related users for a specific entity such as a family, small team, or large company." + "message": "سازمان‌ها به شما این امکان را می‌دهند که بخش هایی از گاوصندوق خود را با دیگران به اشتراک بگذارید و همچنین کاربران مرتبط را برای یک نهاد خاص مانند یک خانواده، تیم کوچک یا شرکت بزرگ مدیریت کنید." }, "generalInformation": { - "message": "General information" + "message": "اطلاعات عمومی" }, "organizationName": { - "message": "Organization name" + "message": "نام سازمان" }, "accountOwnedBusiness": { - "message": "This account is owned by a business." + "message": "این حساب متعلق به یک کسب و کار است." }, "billingEmail": { - "message": "Billing email" + "message": "ایمیل صورتحساب" }, "businessName": { - "message": "Business name" + "message": "نام کسب و کار" }, "chooseYourPlan": { - "message": "Choose your plan" + "message": "طرح خود را انتخاب کنید" }, "users": { - "message": "Users" + "message": "کاربران" }, "userSeats": { - "message": "User seats" + "message": "جایگاه کاربر" }, "additionalUserSeats": { - "message": "Additional user seats" + "message": "جایگاه کاربر اضافی" }, "userSeatsDesc": { - "message": "# of user seats" + "message": "# جایگاه کاربر" }, "userSeatsAdditionalDesc": { - "message": "Your plan comes with $BASE_SEATS$ user seats. You can add additional users for $SEAT_PRICE$ per user /month.", + "message": "طرح شما با $BASE_SEATS$ جایگاه کاربر ارائه می‌شود. می‌توانید کاربران دیگری را با قیمت $SEAT_PRICE$ برای هر کاربر در ماه اضافه کنید.", "placeholders": { "base_seats": { "content": "$1", @@ -2134,14 +2150,14 @@ } }, "userSeatsHowManyDesc": { - "message": "How many user seats do you need? You can also add additional seats later if needed." + "message": "به چند جایگاه کاربر نیاز دارید؟ در صورت نیاز می‌توانید بعداً جایگاه‌های بیشتری اضافه کنید." }, "planNameFree": { - "message": "Free", + "message": "رايگان", "description": "Free as in 'free beer'." }, "planDescFree": { - "message": "For testing or personal users to share with $COUNT$ other user.", + "message": "برای آزمایش یا کاربران شخصی برای اشتراک گذاری با $COUNT$ کاربر دیگر.", "placeholders": { "count": { "content": "$1", @@ -2150,28 +2166,28 @@ } }, "planNameFamilies": { - "message": "Families" + "message": "خانواده" }, "planDescFamilies": { - "message": "For personal use, to share with family & friends." + "message": "برای استفاده شخصی، برای اشتراک گذاری با خانواده و دوستان." }, "planNameTeams": { - "message": "Teams" + "message": "تیم" }, "planDescTeams": { - "message": "For businesses and other team organizations." + "message": "برای مشاغل و سایر سازمان های تیمی." }, "planNameEnterprise": { - "message": "Enterprise" + "message": "سازمانی" }, "planDescEnterprise": { - "message": "For businesses and other large organizations." + "message": "برای مشاغل و سایر سازمان های بزرگ." }, "freeForever": { - "message": "Free forever" + "message": "رایگان برای همیشه" }, "includesXUsers": { - "message": "includes $COUNT$ users", + "message": "شامل $COUNT$ کاربر", "placeholders": { "count": { "content": "$1", @@ -2180,10 +2196,10 @@ } }, "additionalUsers": { - "message": "Additional users" + "message": "کاربران اضافه" }, "costPerUser": { - "message": "$COST$ per user", + "message": "$COST$ برای هر کاربر", "placeholders": { "cost": { "content": "$1", @@ -2192,7 +2208,7 @@ } }, "limitedUsers": { - "message": "Limited to $COUNT$ users (including you)", + "message": "محدود به $COUNT$ کاربر (از جمله شما)", "placeholders": { "count": { "content": "$1", @@ -2201,7 +2217,7 @@ } }, "limitedCollections": { - "message": "Limited to $COUNT$ collections", + "message": "محدود به $COUNT$ مجموعه", "placeholders": { "count": { "content": "$1", @@ -2210,7 +2226,7 @@ } }, "addShareLimitedUsers": { - "message": "Add and share with up to $COUNT$ users", + "message": "اضافه کنید و با حداکثر $COUNT$ کاربر به اشتراک بگذارید", "placeholders": { "count": { "content": "$1", @@ -2219,13 +2235,13 @@ } }, "addShareUnlimitedUsers": { - "message": "Add and share with unlimited users" + "message": "اضافه کردن و اشتراک گذاری با کاربران نامحدود" }, "createUnlimitedCollections": { - "message": "Create unlimited collections" + "message": "ایجاد مجموعه‌های نامحدود" }, "gbEncryptedFileStorage": { - "message": "$SIZE$ encrypted file storage", + "message": "ذخیره سازی پرونده رمزگذاری شده $SIZE$", "placeholders": { "size": { "content": "$1", @@ -2234,28 +2250,28 @@ } }, "onPremHostingOptional": { - "message": "On-premise hosting (optional)" + "message": "میزبانی داخلی (اختیاری)" }, "usersGetPremium": { - "message": "Users get access to Premium features" + "message": "کاربران به ویژگی‌های پرمیوم دسترسی پیدا می‌کنند" }, "controlAccessWithGroups": { - "message": "Control user access with groups" + "message": "کنترل دسترسی کاربر با گروه ها" }, "syncUsersFromDirectory": { - "message": "Sync your users and groups from a directory" + "message": "کاربران و گروه های خود را از یک فهرست همگام‌سازی کنید" }, "trackAuditLogs": { - "message": "Track user actions with audit logs" + "message": "ردیابی اقدامات کاربر با ثبت وقایع مربوط به حسابرسی" }, "enforce2faDuo": { - "message": "Enforce 2FA with Duo" + "message": "2FA را با Duo اجرا کنید" }, "priorityCustomerSupport": { - "message": "Priority customer support" + "message": "اولویت پشتیبانی از مشتری" }, "xDayFreeTrial": { - "message": "$COUNT$ day free trial, cancel anytime", + "message": "$COUNT$ روز آزمایشی رایگان، هر زمان خواستید لغو کنید", "placeholders": { "count": { "content": "$1", @@ -2264,7 +2280,7 @@ } }, "trialThankYou": { - "message": "Thanks for signing up for Bitwarden for $PLAN$!", + "message": "از ثبت نام در Bitwarden برای $PLAN$ سپاسگزاریم!", "placeholders": { "plan": { "content": "$1", @@ -2273,7 +2289,7 @@ } }, "trialPaidInfoMessage": { - "message": "Your $PLAN$ 7 day free trial will be converted to a paid subscription after 7 days.", + "message": "نسخه آزمایشی رایگان ۷ روزه $PLAN$ شما پس از ۷ روز به اشتراک پولی تبدیل می‌شود.", "placeholders": { "plan": { "content": "$1", @@ -2282,133 +2298,133 @@ } }, "trialConfirmationEmail": { - "message": "We've sent a confirmation email to your team's billing email at " + "message": "ما یک ایمیل تأیید به ایمیل صورتحساب تیم شما ارسال کرده‌ایم به نشانی " }, "monthly": { - "message": "Monthly" + "message": "ماهانه" }, "annually": { - "message": "Annually" + "message": "سالانه" }, "annual": { - "message": "Annual" + "message": "سالانه" }, "basePrice": { - "message": "Base price" + "message": "قیمت پایه" }, "organizationCreated": { - "message": "Organization created" + "message": "سازمان ساخته شد" }, "organizationReadyToGo": { - "message": "Your new organization is ready to go!" + "message": "سازمان جدید شما آماده راه اندازی است!" }, "organizationUpgraded": { - "message": "Organization upgraded" + "message": "سازمان ارتقا یافته است" }, "leave": { - "message": "Leave" + "message": "ترک کردن" }, "leaveOrganizationConfirmation": { - "message": "Are you sure you want to leave this organization?" + "message": "آيا مطمئنید که می‌خواهيد سازمان انتخاب شده را ترک کنيد؟" }, "leftOrganization": { - "message": "You left the organization" + "message": "شما از سازمان خارج شده اید" }, "defaultCollection": { - "message": "Default collection" + "message": "مجموعه پیش‌فرض" }, "getHelp": { - "message": "Get help" + "message": "کمک گرفتن" }, "getApps": { - "message": "Get the apps" + "message": "برنامه را دانلود کنید" }, "loggedInAs": { - "message": "Logged in as" + "message": "وارد شده به عنوان" }, "eventLogs": { - "message": "Event logs" + "message": "رویدادهای ورود" }, "people": { - "message": "People" + "message": "مردم" }, "policies": { - "message": "Policies" + "message": "سیاست‌ها" }, "singleSignOn": { - "message": "Single sign-on" + "message": "ورود یکپارچه به سیستم" }, "editPolicy": { - "message": "Edit policy" + "message": "ویرایش سیاست" }, "groups": { - "message": "Groups" + "message": "گروه ها" }, "newGroup": { - "message": "New group" + "message": "گروه جدید" }, "addGroup": { - "message": "Add group" + "message": "افزودن گروه" }, "editGroup": { - "message": "Edit group" + "message": "ویرایش گروه" }, "deleteGroupConfirmation": { - "message": "Are you sure you want to delete this group?" + "message": "مطمئن هستید که می خواهید این گروه را حذف کنید؟" }, "removeUserConfirmation": { - "message": "Are you sure you want to remove this user?" + "message": "آیا مطمئن به حذف این کاربر هستید؟" }, "removeOrgUserConfirmation": { - "message": "When a member is removed, they no longer have access to organization data and this action is irreversible. To add the member back to the organization, they must be invited and onboarded again." + "message": "وقتی عضوی حذف می‌شود، دیگر به داده‌های سازمان دسترسی ندارد و این عمل غیرقابل برگشت است. برای افزودن دوباره عضو به سازمان، باید مجدداً دعوت شود و عضو شود." }, "revokeUserConfirmation": { - "message": "When a member is revoked, they no longer have access to organization data. To quickly restore member access, go to the Revoked tab." + "message": "وقتی عضوی لغو می شود، دیگر به داده های سازمان دسترسی ندارند. برای بازیابی سریع دسترسی اعضا، به برگه \"لغو شد\" بروید." }, "removeUserConfirmationKeyConnector": { - "message": "Warning! This user requires Key Connector to manage their encryption. Removing this user from your organization will permanently deactivate their account. This action cannot be undone. Do you want to proceed?" + "message": "هشدار! این کاربر برای مدیریت رمزگذاری خود به رابط کلید نیاز دارد. با حذف این کاربر از سازمان خود، حساب او برای همیشه غیرفعال می شود. این عمل قابل لغو نیست. آیا می‌خواهید ادامه دهید؟" }, "externalId": { - "message": "External id" + "message": "شناسه خارجی" }, "externalIdDesc": { - "message": "The external id can be used as a reference or to link this resource to an external system such as a user directory." + "message": "شناسه خارجی می‌تواند به عنوان یک مرجع یا برای پیوند دادن این منبع به یک سیستم خارجی مانند فهرست کاربری استفاده شود." }, "accessControl": { - "message": "Access control" + "message": "کنترل دسترسی" }, "groupAccessAllItems": { - "message": "This group can access and modify all items." + "message": "این گروه می‌تواند به همه موارد دسترسی داشته باشد و آن‌ها را تغییر دهد." }, "groupAccessSelectedCollections": { - "message": "This group can access only the selected collections." + "message": "این گروه فقط می‌تواند به مجموعه های انتخاب شده دسترسی داشته باشد." }, "readOnly": { - "message": "Read only" + "message": "فقط خواندنی" }, "newCollection": { - "message": "New collection" + "message": "مجموعه جدید" }, "addCollection": { - "message": "Add collection" + "message": "اضافه کردن مجموعه" }, "editCollection": { - "message": "Edit collection" + "message": "ویرایش مجموعه" }, "deleteCollectionConfirmation": { - "message": "Are you sure you want to delete this collection?" + "message": "آیا مطمئنید که می‌خواهید این مجموعه را حذف کنید؟" }, "editUser": { - "message": "Edit user" + "message": "ویرایش کاربر" }, "inviteUser": { - "message": "Invite user" + "message": "دعوت از کاربر" }, "inviteUserDesc": { - "message": "Invite a new user to your organization by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + "message": "با وارد کردن نشانی ایمیل حساب Bitwarden در زیر، یک کاربر جدید را به سازمان خود دعوت کنید. اگر آنها قبلاً یک حساب Bitwarden نداشته باشند، از آنها خواسته می‌شود یک حساب جدید ایجاد کنند." }, "inviteMultipleEmailDesc": { - "message": "You can invite up to $COUNT$ users at a time by comma separating a list of email addresses.", + "message": "می‌توانید با جدا کردن فهرستی از نشانی‌های ایمیل، حداکثر $COUNT$ کاربر را در یک زمان دعوت کنید.", "placeholders": { "count": { "content": "$1", @@ -2417,121 +2433,121 @@ } }, "userUsingTwoStep": { - "message": "This user is using two-step login to protect their account." + "message": "این کاربر از ورود دو مرحله ای برای محافظت از حساب خود استفاده می‌کند." }, "userAccessAllItems": { - "message": "This user can access and modify all items." + "message": "این کاربر می‌تواند به همه موارد دسترسی داشته باشد و آن‌ها را تغییر دهد." }, "userAccessSelectedCollections": { - "message": "This user can access only the selected collections." + "message": "این کاربر فقط می‌تواند به مجموعه های انتخاب شده دسترسی داشته باشد." }, "search": { - "message": "Search" + "message": "جستجو" }, "invited": { - "message": "Invited" + "message": "دعوت شده" }, "accepted": { - "message": "Accepted" + "message": "پذیرفته شده" }, "confirmed": { - "message": "Confirmed" + "message": "تأیید شده" }, "clientOwnerEmail": { - "message": "Client owner email" + "message": "ایمیل مالک مشتری" }, "owner": { - "message": "Owner" + "message": "مالک" }, "ownerDesc": { - "message": "Manage all aspects of your organization, including billing and subscriptions" + "message": "تمام جنبه‌های سازمانتان، از جمله صورتحساب و اشتراک‌ها را مدیریت کنید" }, "clientOwnerDesc": { - "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." + "message": "این کاربر باید مستقل از ارائه دهنده باشد. اگر ارائه‌دهنده با سازمان ارتباط نداشته باشد، این کاربر مالکیت سازمان را حفظ خواهد کرد." }, "admin": { - "message": "Admin" + "message": "مدیر" }, "adminDesc": { - "message": "Manage organization access, all collections, members, reporting, and security settings" + "message": "دسترسی سازمان، همه مجموعه‌ها، اعضا، گزارش‌ها و تنظیمات امنیتی را مدیریت کنید" }, "user": { - "message": "User" + "message": "کاربر" }, "userDesc": { - "message": "Access and add items to assigned collections" + "message": "به مجموعه های اختصاص داده شده دسترسی داشته باشید و موارد را اضافه کنید" }, "manager": { - "message": "Manager" + "message": "مدیر" }, "managerDesc": { - "message": "Create, delete, and manage access in assigned collections" + "message": "ایجاد، حذف و مدیریت دسترسی در مجموعه های اختصاص داده شده" }, "all": { - "message": "All" + "message": "همه" }, "refresh": { - "message": "Refresh" + "message": "تازه کردن" }, "timestamp": { - "message": "Timestamp" + "message": "برچسب زمان" }, "event": { - "message": "Event" + "message": "رویداد" }, "unknown": { - "message": "Unknown" + "message": "ناشناخته" }, "loadMore": { - "message": "Load more" + "message": "بارگذاری بیشتر" }, "mobile": { - "message": "Mobile", + "message": "تلفن همراه", "description": "Mobile app" }, "extension": { - "message": "Extension", + "message": "افزونه", "description": "Browser extension/addon" }, "desktop": { - "message": "Desktop", + "message": "رومیزی", "description": "Desktop app" }, "webVault": { - "message": "Web vault" + "message": "گاوصندوق وب" }, "loggedIn": { - "message": "Logged in" + "message": "وارد شده" }, "changedPassword": { - "message": "Changed account password" + "message": "کلمه عبور حساب کاربری تغییر کرد" }, "enabledUpdated2fa": { - "message": "Two-step login saved" + "message": "ورود دو مرحله ای ذخیره شد" }, "disabled2fa": { - "message": "Two-step login turned off" + "message": "ورود به سیستم دو مرحله ای خاموش است" }, "recovered2fa": { - "message": "Recovered account from two-step login." + "message": "حساب بازیابی شده از ورود دو مرحله ای." }, "failedLogin": { - "message": "Login attempt failed with incorrect password." + "message": "تلاش ناموفق برای ورود با کلمه عبور اشتباه." }, "failedLogin2fa": { - "message": "Login attempt failed with incorrect two-step login." + "message": "تلاش ناموفق برای ورود با ورود دو مرحله ای اشتباه." }, "exportedVault": { - "message": "Vault exported" + "message": "گاوصندوق برون ریزی شد" }, "exportedOrganizationVault": { - "message": "Exported organization vault." + "message": "گاوصندوق سازمان برون ریزی." }, "editedOrgSettings": { - "message": "Edited organization settings." + "message": "تنظیمات سازمان ویرایش شد." }, "createdItemId": { - "message": "Created item $ID$.", + "message": "مورد $ID$ ایجاد شد.", "placeholders": { "id": { "content": "$1", @@ -2540,7 +2556,7 @@ } }, "editedItemId": { - "message": "Edited item $ID$.", + "message": "مورد ویرایش $ID$.", "placeholders": { "id": { "content": "$1", @@ -2549,7 +2565,7 @@ } }, "deletedItemId": { - "message": "Sent item $ID$ to trash.", + "message": "مورد $ID$ به سطل زباله ارسال شد.", "placeholders": { "id": { "content": "$1", @@ -2558,7 +2574,7 @@ } }, "movedItemIdToOrg": { - "message": "Moved item $ID$ to an organization.", + "message": "مورد $ID$ به یک سازمان منتقل شد.", "placeholders": { "id": { "content": "$1", @@ -2567,10 +2583,10 @@ } }, "viewAllLoginOptions": { - "message": "View all log in options" + "message": "مشاهده همه گزینه‌های ورود به سیستم" }, "viewedItemId": { - "message": "Viewed item $ID$.", + "message": "مورد $ID$ مشاهده شد.", "placeholders": { "id": { "content": "$1", @@ -2579,7 +2595,7 @@ } }, "viewedPasswordItemId": { - "message": "Viewed password for item $ID$.", + "message": "رمز عبور مورد $ID$ مشاهده شد.", "placeholders": { "id": { "content": "$1", @@ -2588,7 +2604,7 @@ } }, "viewedHiddenFieldItemId": { - "message": "Viewed hidden field for item $ID$.", + "message": "فیلد پنهان برای مورد $ID$ مشاهده شد.", "placeholders": { "id": { "content": "$1", @@ -2597,7 +2613,7 @@ } }, "viewedCardNumberItemId": { - "message": "Viewed Card Number for item $ID$.", + "message": "شماره کارت مشاهده شده برای مورد $ID$.", "placeholders": { "id": { "content": "$1", @@ -2606,7 +2622,7 @@ } }, "viewedSecurityCodeItemId": { - "message": "Viewed security code for item $ID$.", + "message": "کد امنیتی مورد $ID$ مشاهده شد.", "placeholders": { "id": { "content": "$1", @@ -2615,7 +2631,7 @@ } }, "copiedPasswordItemId": { - "message": "Copied password for item $ID$.", + "message": "کلمه عبور مورد $ID$ کپی شد.", "placeholders": { "id": { "content": "$1", @@ -2624,7 +2640,7 @@ } }, "copiedHiddenFieldItemId": { - "message": "Copied hidden field for item $ID$.", + "message": "فیلد پنهان برای مورد $ID$ کپی شد.", "placeholders": { "id": { "content": "$1", @@ -2633,7 +2649,7 @@ } }, "copiedSecurityCodeItemId": { - "message": "Copied security code for item $ID$.", + "message": "کد امنیتی مورد $ID$ کپی شد.", "placeholders": { "id": { "content": "$1", @@ -2642,7 +2658,7 @@ } }, "autofilledItemId": { - "message": "Auto-filled item $ID$.", + "message": "$ID$ مورد پر شده خودکار.", "placeholders": { "id": { "content": "$1", @@ -2651,7 +2667,7 @@ } }, "createdCollectionId": { - "message": "Created collection $ID$.", + "message": "مجموعه $ID$ ایجاد شد.", "placeholders": { "id": { "content": "$1", @@ -2660,7 +2676,7 @@ } }, "editedCollectionId": { - "message": "Edited collection $ID$.", + "message": "مجموعه $ID$ ویرایش شد.", "placeholders": { "id": { "content": "$1", @@ -2669,7 +2685,7 @@ } }, "deletedCollectionId": { - "message": "Deleted collection $ID$.", + "message": "مجموعه $ID$ حذف شد.", "placeholders": { "id": { "content": "$1", @@ -2678,7 +2694,7 @@ } }, "editedPolicyId": { - "message": "Edited policy $ID$.", + "message": "سیاست $ID$ ویرایش.", "placeholders": { "id": { "content": "$1", @@ -2687,7 +2703,7 @@ } }, "createdGroupId": { - "message": "Created group $ID$.", + "message": "گروه $ID$ ایجاد شد.", "placeholders": { "id": { "content": "$1", @@ -2696,7 +2712,7 @@ } }, "editedGroupId": { - "message": "Edited group $ID$.", + "message": "گروه $ID$ ویرایش شد.", "placeholders": { "id": { "content": "$1", @@ -2705,7 +2721,7 @@ } }, "deletedGroupId": { - "message": "Deleted group $ID$.", + "message": "گروه $ID$ حذف شد.", "placeholders": { "id": { "content": "$1", @@ -2714,7 +2730,7 @@ } }, "removedUserId": { - "message": "Removed user $ID$.", + "message": "کاربر $ID$ حذف شد.", "placeholders": { "id": { "content": "$1", @@ -2723,7 +2739,7 @@ } }, "removeUserIdAccess": { - "message": "Remove $ID$ access", + "message": "دسترسی $ID$ حذف شد", "placeholders": { "id": { "content": "$1", @@ -2732,7 +2748,7 @@ } }, "revokedUserId": { - "message": "Revoked organization access for $ID$.", + "message": "دسترسی سازمان برای $ID$ لغو شد.", "placeholders": { "id": { "content": "$1", @@ -2741,7 +2757,7 @@ } }, "restoredUserId": { - "message": "Restored organization access for $ID$.", + "message": "دسترسی سازمان برای $ID$ را بازیابی کرد.", "placeholders": { "id": { "content": "$1", @@ -2750,7 +2766,7 @@ } }, "revokeUserId": { - "message": "Revoke $ID$ access", + "message": "دسترسی $ID$ را لغو کن", "placeholders": { "id": { "content": "$1", @@ -2759,7 +2775,7 @@ } }, "createdAttachmentForItem": { - "message": "Created attachment for item $ID$.", + "message": "پیوست برای مورد $ID$ ایجاد شد.", "placeholders": { "id": { "content": "$1", @@ -2768,7 +2784,7 @@ } }, "deletedAttachmentForItem": { - "message": "Deleted attachment for item $ID$.", + "message": "پیوست برای مورد $ID$ حذف شد.", "placeholders": { "id": { "content": "$1", @@ -2777,7 +2793,7 @@ } }, "editedCollectionsForItem": { - "message": "Edited collections for item $ID$.", + "message": "مجموعه های ویرایش شده برای مورد $ID$.", "placeholders": { "id": { "content": "$1", @@ -2786,7 +2802,7 @@ } }, "invitedUserId": { - "message": "Invited user $ID$.", + "message": "کاربر $ID$ دعوت شد.", "placeholders": { "id": { "content": "$1", @@ -2795,7 +2811,7 @@ } }, "confirmedUserId": { - "message": "Confirmed user $ID$.", + "message": "کاربر $ID$ تأیید شد.", "placeholders": { "id": { "content": "$1", @@ -2804,7 +2820,7 @@ } }, "editedUserId": { - "message": "Edited user $ID$.", + "message": "کاربر $ID$ ویرایش شد.", "placeholders": { "id": { "content": "$1", @@ -2813,7 +2829,7 @@ } }, "editedGroupsForUser": { - "message": "Edited groups for user $ID$.", + "message": "گروه های ویرایش شده برای کاربر $ID$.", "placeholders": { "id": { "content": "$1", @@ -2822,7 +2838,7 @@ } }, "unlinkedSsoUser": { - "message": "Unlinked SSO for user $ID$.", + "message": "SSO برای کاربر $ID$ پیوند نخورده است.", "placeholders": { "id": { "content": "$1", @@ -2831,7 +2847,7 @@ } }, "createdOrganizationId": { - "message": "Created organization $ID$.", + "message": "سازمان $ID$ ایجاد شد.", "placeholders": { "id": { "content": "$1", @@ -2840,7 +2856,7 @@ } }, "addedOrganizationId": { - "message": "Added organization $ID$.", + "message": "سازمان $ID$ اضافه شد.", "placeholders": { "id": { "content": "$1", @@ -2849,7 +2865,7 @@ } }, "removedOrganizationId": { - "message": "Removed organization $ID$.", + "message": "سازمان $ID$ حذف شد.", "placeholders": { "id": { "content": "$1", @@ -2858,7 +2874,7 @@ } }, "accessedClientVault": { - "message": "Accessed $ID$ organization vault.", + "message": "به گاوصندوق سازمان $ID$ دسترسی پیدا کرد.", "placeholders": { "id": { "content": "$1", @@ -2867,40 +2883,40 @@ } }, "device": { - "message": "Device" + "message": "دستگاه" }, "view": { - "message": "View" + "message": "مشاهده" }, "invalidDateRange": { - "message": "Invalid date range." + "message": "بازه تاریخ نامعتبر." }, "errorOccurred": { - "message": "An error has occurred." + "message": "خطایی رخ داده است." }, "userAccess": { - "message": "User access" + "message": "دسترسی کاربر" }, "userType": { - "message": "User type" + "message": "نوع کاربر" }, "groupAccess": { - "message": "Group access" + "message": "دسترسی گروه" }, "groupAccessUserDesc": { - "message": "Edit the groups that this user belongs to." + "message": "گروه‌هایی که کاربر به آن تعلق دارد را ویرایش کنید." }, "invitedUsers": { - "message": "User(s) invited" + "message": "کاربر دعوت شد" }, "resendInvitation": { - "message": "Resend invitation" + "message": "فرستادن مجدد دعوتنامه" }, "resendEmail": { - "message": "Resend email" + "message": "ارسال مجدد ایمیل" }, "hasBeenReinvited": { - "message": "$USER$ reinvited", + "message": "$USER$ دوباره دعوت شد", "placeholders": { "user": { "content": "$1", @@ -2909,13 +2925,13 @@ } }, "confirm": { - "message": "Confirm" + "message": "تأیید" }, "confirmUser": { - "message": "Confirm user" + "message": "تأیید کاربر" }, "hasBeenConfirmed": { - "message": "$USER$ confirmed.", + "message": "$USER$ تأیید شد.", "placeholders": { "user": { "content": "$1", @@ -2924,64 +2940,64 @@ } }, "confirmUsers": { - "message": "Confirm users" + "message": "تأیید کاربرها" }, "usersNeedConfirmed": { - "message": "You have users that have accepted their invitation, but still need to be confirmed. Users will not have access to the organization until they are confirmed." + "message": "شما کاربرانی دارید که دعوت را پذیرفته اند، اما هنوز باید تأیید شوند. کاربران تا زمانی که تأیید نشوند به سازمان دسترسی نخواهند داشت." }, "startDate": { - "message": "Start date" + "message": "تاریخ شروع" }, "endDate": { - "message": "End date" + "message": "تاریخ پایان" }, "verifyEmail": { - "message": "Verify email" + "message": "تأیید ایمیل" }, "verifyEmailDesc": { - "message": "Verify your account's email address to unlock access to all features." + "message": "برای باز کردن قفل دسترسی به همه ویژگی‌ها، نشانی ایمیل حساب خود را تأیید کنید." }, "verifyEmailFirst": { - "message": "Your account's email address first must be verified." + "message": "اول نشانی ایمیل حساب کاربری شما باید تأیید شود." }, "checkInboxForVerification": { - "message": "Check your email inbox for a verification link." + "message": "لطفاً صندوق ورودی ایمیل خود را برای پیوند تأیید بررسی کنید." }, "emailVerified": { - "message": "Account email verified" + "message": "ایمیل حساب تأیید شد" }, "emailVerifiedFailed": { - "message": "Unable to verify your email. Try sending a new verification email." + "message": "تأیید ایمیل شما امکان پذیر نیست. سعی کنید یک ایمیل تأیید جدید ارسال کنید." }, "emailVerificationRequired": { - "message": "Email verification required" + "message": "تأیید ایمیل لازم است" }, "emailVerificationRequiredDesc": { - "message": "You must verify your email to use this feature." + "message": "برای استفاده از این ویژگی باید ایمیل خود را تأیید کنید." }, "updateBrowser": { - "message": "Update browser" + "message": "به‌روزرسانی مرورگر" }, "updateBrowserDesc": { - "message": "You are using an unsupported web browser. The web vault may not function properly." + "message": "شما از یک مرورگر وب پشتیبانی نشده استفاده می‌کنید. گاوصندوق وب ممکن است به درستی کار نکند." }, "joinOrganization": { - "message": "Join organization" + "message": "به سازمان بپیوندید" }, "joinOrganizationDesc": { - "message": "You've been invited to join the organization listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + "message": "شما برای پیوستن به سازمان فهرست شده در بالا دعوت شده اید. برای پذیرش دعوت، باید وارد شوید یا یک حساب کاربری جدید در Bitwarden ایجاد کنید." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "دعوتنامه پذیرفته شد" }, "inviteAcceptedDesc": { - "message": "You can access this organization once an administrator confirms your membership. We'll send you an email when that happens." + "message": "زمانی که یک سرپرست عضویت شما را تأیید کرد، می‌توانید به این سازمان دسترسی داشته باشید. زمانی که این اتفاق بیفتد، یک ایمیل برای شما ارسال خواهیم کرد." }, "inviteAcceptFailed": { - "message": "Unable to accept invitation. Ask an organization admin to send a new invitation." + "message": "قادر به پذیرش دعوت نیست. از مدیر سازمان بخواهید دعوتنامه جدیدی ارسال کند." }, "inviteAcceptFailedShort": { - "message": "Unable to accept invitation. $DESCRIPTION$", + "message": "قادر به پذیرش دعوت نیست. $DESCRIPTION$", "placeholders": { "description": { "content": "$1", @@ -2990,40 +3006,40 @@ } }, "rememberEmail": { - "message": "Remember email" + "message": "ایمیل را به خاطر بسپار" }, "recoverAccountTwoStepDesc": { - "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." + "message": "اگر نمی‌توانید از طریق روش‌های ورود دو مرحله‌ای معمولی به حساب خود دسترسی پیدا کنید، می‌توانید از کد بازیابی ورود به سیستم دو مرحله‌ای خود برای خاموش کردن همه ارائه‌دهندگان دو مرحله‌ای در حساب خود استفاده کنید." }, "recoverAccountTwoStep": { - "message": "Recover account two-step login" + "message": "بازیابی حساب ورود دو مرحله ای" }, "twoStepRecoverDisabled": { - "message": "Two-step login turned off on your account." + "message": "ورود دو مرحله ای در حساب شما غیرفعال شد." }, "learnMore": { - "message": "Learn more" + "message": "بیشتر بدانید" }, "deleteRecoverDesc": { - "message": "Enter your email address below to recover and delete your account." + "message": "نشانی ایمیل خود را در زیر برای بازیابی و حذف حساب خود وارد کنید." }, "deleteRecoverEmailSent": { - "message": "If your account exists, we've sent you an email with further instructions." + "message": "اگر حساب شما وجود دارد، ما یک ایمیل با دستورالعمل های بیشتر برای شما ارسال کرده ایم." }, "deleteRecoverConfirmDesc": { - "message": "You have requested to delete your Bitwarden account. Use the button below to confirm." + "message": "شما درخواست کرده اید که حساب Bitwarden خود را حذف کنید. برای تأیید از دکمه زیر استفاده کنید." }, "myOrganization": { - "message": "My organization" + "message": "سازمان من" }, "organizationInfo": { - "message": "Organization info" + "message": "اطلاعات سازمان" }, "deleteOrganization": { - "message": "Delete organization" + "message": "حذف سازمان" }, "deletingOrganizationContentWarning": { - "message": "Enter the master password to confirm deletion of $ORGANIZATION$ and all associated data. Vault data in $ORGANIZATION$ includes:", + "message": "کلمه عبور اصلی را وارد کنید تا حذف $ORGANIZATION$ و همه داده‌های مرتبط را تأیید کنید. داده‌های گلوصندوق در $ORGANIZATION$ شامل:", "placeholders": { "organization": { "content": "$1", @@ -3032,10 +3048,10 @@ } }, "deletingOrganizationActiveUserAccountsWarning": { - "message": "User accounts will remain active after deletion but will no longer be associated to this organization." + "message": "حساب‌های کاربری پس از حذف فعال می‌مانند اما دیگر به این سازمان مرتبط نمی‌شوند." }, "deletingOrganizationIsPermanentWarning": { - "message": "Deleting $ORGANIZATION$ is permanent and irreversible.", + "message": "حذف $ORGANIZATION$ دائمی و غیر قابل برگشت است.", "placeholders": { "organization": { "content": "$1", @@ -3044,34 +3060,34 @@ } }, "organizationDeleted": { - "message": "Organization deleted" + "message": "سازمان حذف شد" }, "organizationDeletedDesc": { - "message": "The organization and all associated data has been deleted." + "message": "سازمان و همه داده های مرتبط حذف شده است." }, "organizationUpdated": { - "message": "Organization saved" + "message": "سازمان دخیره شده است" }, "taxInformation": { - "message": "Tax information" + "message": "اطلاعات مالیات" }, "taxInformationDesc": { - "message": "For customers within the US, ZIP code is required to satisfy sales tax requirements, for other countries you may optionally provide a tax identification number (VAT/GST) and/or address to appear on your invoices." + "message": "برای مشتریان در ایالات متحده، کد پستی برای برآورده کردن الزامات مالیات بر فروش مورد نیاز است، برای سایر کشورها می‌توانید به صورت اختیاری یک شماره شناسایی مالیاتی (VAT/GST) و یا نشانی ارائه دهید تا در صورتحساب‌هایتان نمایش داده شود." }, "billingPlan": { - "message": "Plan", + "message": "طرح", "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." }, "changeBillingPlan": { - "message": "Upgrade plan", + "message": "ارتقاء طرح", "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." }, "changeBillingPlanUpgrade": { - "message": "Upgrade your account to another plan by providing the information below. Please ensure that you have an active payment method added to the account.", + "message": "با ارائه اطلاعات زیر حساب خود را به طرح دیگری ارتقاء دهید. لطفاً مطمئن شوید که یک روش پرداخت فعال به حساب اضافه شده است.", "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." }, "invoiceNumber": { - "message": "Invoice #$NUMBER$", + "message": "فاکتور #$NUMBER$", "description": "ex. Invoice #79C66F0-0001", "placeholders": { "number": { @@ -3081,31 +3097,31 @@ } }, "viewInvoice": { - "message": "View invoice" + "message": "مشاهده صورتحساب" }, "downloadInvoice": { - "message": "Download invoice" + "message": "دانلود صورتحساب" }, "verifyBankAccount": { - "message": "Verify bank account" + "message": "تأیید حساب بانکی" }, "verifyBankAccountDesc": { - "message": "We have made two micro-deposits to your bank account (it may take 1-2 business days to show up). Enter these amounts to verify the bank account." + "message": "ما دو سپرده خرد به حساب بانکی شما واریز کرده‌ایم (ممکن است ۱ تا ۲ روز کاری طول بکشد تا نشان داده شود). این مبالغ را برای تأیید حساب بانکی وارد کردیم." }, "verifyBankAccountInitialDesc": { - "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make two micro-deposits within the next 1-2 business days. Enter these amounts on the organization's billing page to verify the bank account." + "message": "پرداخت با حساب بانکی فقط برای مشتریان در ایالات متحده امکان پذیر است. شما باید حساب بانکی خود را تأیید کنید. ما طی ۱ تا ۲ روز کاری آینده دو سپرده خرد خواهیم داشت. برای تأیید حساب بانکی، این مبالغ را در صفحه صورتحساب سازمان وارد کنید." }, "verifyBankAccountFailureWarning": { - "message": "Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "عدم تأیید حساب بانکی منجر به عدم پرداخت و تعلیق اشتراک شما می‌شود." }, "verifiedBankAccount": { - "message": "Bank account verified" + "message": "حساب بانکی تأیید شد" }, "bankAccount": { - "message": "Bank account" + "message": "حساب بانکی" }, "amountX": { - "message": "Amount $COUNT$", + "message": "مبلغ $COUNT$", "description": "Used in bank account verification of micro-deposits. Amount, as in a currency amount. Ex. Amount 1 is $2.00, Amount 2 is $1.50", "placeholders": { "count": { @@ -3115,50 +3131,50 @@ } }, "routingNumber": { - "message": "Routing number", + "message": "شماره مسیریابی", "description": "Bank account routing number" }, "accountNumber": { - "message": "Account number" + "message": "شماره حساب" }, "accountHolderName": { - "message": "Account holder name" + "message": "نام صاحب حساب" }, "bankAccountType": { - "message": "Account type" + "message": "نوع حساب‌" }, "bankAccountTypeCompany": { - "message": "Company (business)" + "message": "شرکت (کسب و کار)" }, "bankAccountTypeIndividual": { - "message": "Individual (personal)" + "message": "فردی (شخصی)" }, "enterInstallationId": { - "message": "Enter your installation id" + "message": "شناسه نصب خود را وارد کنید" }, "limitSubscriptionDesc": { - "message": "Set a seat limit for your subscription. Once this limit is reached, you will not be able to invite new members." + "message": "برای اشتراک خود محدودیت جایگاه تعیین کنید. پس از رسیدن به این محدودیت، نمی‌توانید اعضای جدید را دعوت کنید." }, "maxSeatLimit": { - "message": "Seat Limit (optional)", + "message": "محدودیت جایگاه (اختیاری)", "description": "Upper limit of seats to allow through autoscaling" }, "maxSeatCost": { - "message": "Max potential seat cost" + "message": "حداکثر هزینه بالقوه جایگاه" }, "addSeats": { - "message": "Add seats", + "message": "افزودن جایگاه", "description": "Seat = User Seat" }, "removeSeats": { - "message": "Remove seats", + "message": "حذف جایگاه", "description": "Seat = User Seat" }, "subscriptionDesc": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited users exceed your subscription seats, you will immediately receive a prorated charge for the additional users." + "message": "تنظیمات اشتراک شما منجر به تغییرات نسبتاً زیادی در مجموع صورتحساب شما می‌شود. اگر کاربرانی که به تازگی دعوت شده‌اند بیشتر از جایگاه‌های اشتراک شما باشند، بلافاصله هزینه‌ای متناسب برای کاربران اضافی دریافت خواهید کرد." }, "subscriptionUserSeats": { - "message": "Your subscription allows for a total of $COUNT$ members.", + "message": "اشتراک شما مجموعاً به $COUNT$ عضو اجازه می‌دهد.", "placeholders": { "count": { "content": "$1", @@ -3167,25 +3183,25 @@ } }, "limitSubscription": { - "message": "Limit subscription (optional)" + "message": "محدود کردن اشتراک (اختیاری)" }, "subscriptionSeats": { - "message": "Subscription seats" + "message": "جایگاه‌های اشتراک" }, "subscriptionUpdated": { - "message": "Subscription updated" + "message": "اشتراک به‌روز شد" }, "additionalOptions": { - "message": "Additional options" + "message": "گزینه‌های اضافی" }, "additionalOptionsDesc": { - "message": "For additional help in managing your subscription, please contact Customer Support." + "message": "برای راهنمایی بیشتر در مدیریت اشتراک خود، لطفاً با پشتیبانی مشتری تماس بگیرید." }, "subscriptionUserSeatsUnlimitedAutoscale": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members." + "message": "تنظیمات اشتراک شما منجر به تغییرات نسبتاً زیادی در مجموع صورتحساب شما می‌شود. اگر عضو‌هایی که به تازگی دعوت شده‌اند بیشتر از جایگاه‌های اشتراک شما باشند، بلافاصله هزینه‌ای متناسب برای عضو‌هایی اضافی دریافت خواهید کرد." }, "subscriptionUserSeatsLimitedAutoscale": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members until your $MAX$ seat limit is reached.", + "message": "تنظیمات اشتراک شما منجر به تغییرات نسبتاً زیادی در مجموع صورتحساب شما می‌شود. اگر اعضای تازه دعوت شده از جایگاه‌های اشتراک شما بیشتر شوند، بلافاصله هزینه‌ای متناسب برای اعضای اضافی دریافت می‌کنید تا زمانی که به محدودیت جایگاه $MAX$ شما برسند.", "placeholders": { "max": { "content": "$1", @@ -3194,7 +3210,7 @@ } }, "subscriptionFreePlan": { - "message": "You cannot invite more than $COUNT$ members without upgrading your plan.", + "message": "بدون ارتقای طرح خود نمی‌توانید بیش از $COUNT$ عضو دعوت کنید.", "placeholders": { "count": { "content": "$1", @@ -3203,7 +3219,7 @@ } }, "subscriptionFamiliesPlan": { - "message": "You cannot invite more than $COUNT$ members without upgrading your plan. Please contact Customer Support to upgrade.", + "message": "بدون ارتقای طرح خود نمی‌توانید بیش از $COUNT$ عضو دعوت کنید. لطفاً برای ارتقاء با پشتیبانی مشتری تماس بگیرید.", "placeholders": { "count": { "content": "$1", @@ -3212,7 +3228,7 @@ } }, "subscriptionSponsoredFamiliesPlan": { - "message": "Your subscription allows for a total of $COUNT$ members. Your plan is sponsored and billed to an external organization.", + "message": "اشتراک شما به مجموع $COUNT$ عضو اجازه می‌دهد. طرح شما حمایت مالی می‌شود و برای یک سازمان خارجی صورتحساب می‌شود.", "placeholders": { "count": { "content": "$1", @@ -3221,7 +3237,7 @@ } }, "subscriptionMaxReached": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. You cannot invite more than $COUNT$ members without increasing your subscription seats.", + "message": "تنظیمات اشتراک شما منجر به تغییرات نسبتاً زیادی در مجموع صورتحساب شما می‌شود. شما نمی‌توانید بیش از $COUNT$ عضو را بدون افزایش جایگاه‌های اشتراک خود دعوت کنید.", "placeholders": { "count": { "content": "$1", @@ -3230,19 +3246,19 @@ } }, "seatsToAdd": { - "message": "Seats to add" + "message": "جایگاه برای اضافه کردن" }, "seatsToRemove": { - "message": "Seats to remove" + "message": "جایگاه برای حذف کردن" }, "seatsAddNote": { - "message": "Adding user seats will result in adjustments to your billing totals and immediately charge your payment method on file. The first charge will be prorated for the remainder of the current billing cycle." + "message": "افزودن جایگاه‌های کاربر منجر به تغییراتی در مجموع صورتحساب شما می‌شود و بلافاصله از روش پرداخت شما در پرونده کسر می‌شود. اولین هزینه برای باقیمانده‌ی چرخه صورتحساب فعلی به نسبت محاسبه می‌شود." }, "seatsRemoveNote": { - "message": "Removing user seats will result in adjustments to your billing totals that will be prorated as credits toward your next billing charge." + "message": "حذف جایگاه‌های کاربر منجر به تغییراتی در مجموع صورتحساب شما می‌شود که به عنوان اعتبار نسبت به هزینه صورتحساب بعدی شما تقسیم می‌شود." }, "adjustedSeats": { - "message": "Adjusted $AMOUNT$ user seats.", + "message": "جایگاه‌های کاربر $AMOUNT$ تنظیم شده است.", "placeholders": { "amount": { "content": "$1", @@ -3251,272 +3267,272 @@ } }, "keyUpdated": { - "message": "Key updated" + "message": "کلیدها به‌روز شد" }, "updateKeyTitle": { - "message": "Update key" + "message": "کلید به‌روزرسانی" }, "updateEncryptionKey": { - "message": "Update encryption key" + "message": "کلید رمزگذاری را به‌روز کنید" }, "updateEncryptionKeyShortDesc": { - "message": "You are currently using an outdated encryption scheme." + "message": "شما در حال حاضر از یک طرح رمزگذاری قدیمی استفاده می‌کنید." }, "updateEncryptionKeyDesc": { - "message": "We've moved to larger encryption keys that provide better security and access to newer features. Updating your encryption key is quick and easy. Just type your master password below. This update will eventually become mandatory." + "message": "ما به سمت کلیدهای رمزگذاری بزرگتر رفته ایم که امنیت بهتر و دسترسی به ویژگی های جدیدتر را فراهم می‌کند. به‌روزرسانی کلید رمزگذاری شما سریع و آسان است. فقط کلمه عبور اصلی خود را در زیر تایپ کنید. این به‌روزرسانی در نهایت اجباری خواهد شد." }, "updateEncryptionKeyWarning": { - "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." + "message": "پس از به‌روزرسانی کلید رمزگذاری، باید از سیستم خارج شوید و دوباره به همه برنامه‌های Bitwarden که در حال حاضر استفاده می‌کنید (مانند برنامه تلفن همراه یا برنامه‌های افزودنی مرورگر) وارد شوید. عدم خروج و ورود مجدد (که کلید رمزگذاری جدید شما را دانلود می‌کند) ممکن است منجر به خراب شدن داده‌ها شود. ما سعی خواهیم کرد شما را به طور خودکار از سیستم خارج کنیم، اما ممکن است با تأخیر انجام شود." }, "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "message": "هر برون ریزی رمزگذاری شده ای که ذخیره کرده اید نیز نامعتبر خواهد بود." }, "subscription": { - "message": "Subscription" + "message": "اشتراک" }, "loading": { - "message": "Loading" + "message": "در حال بارگذاری" }, "upgrade": { - "message": "Upgrade" + "message": "به‌روزرسانی" }, "upgradeOrganization": { - "message": "Upgrade organization" + "message": "سازمان را ارتقا دهید" }, "upgradeOrganizationDesc": { - "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." + "message": "این ویژگی برای سازمان‌های رایگان در دسترس نیست. برای باز کردن قفل ویژگی‌های بیشتر، طرح پولی را انتخاب." }, "createOrganizationStep1": { - "message": "Create organization: Step 1" + "message": "ایجاد سازمان: مرحله ۱" }, "createOrganizationCreatePersonalAccount": { - "message": "Before creating your organization, you first need to create a free personal account." + "message": "قبل از ایجاد سازمان خود، ابتدا باید یک حساب شخصی رایگان ایجاد کنید." }, "refunded": { - "message": "Refunded" + "message": "بازپرداخت شد" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "شما چیزی را انتخاب نکرده اید." }, "acceptPolicies": { - "message": "By checking this box you agree to the following:" + "message": "با علامت زدن این کادر با موارد زیر موافقت می‌کنید:" }, "acceptPoliciesRequired": { - "message": "Terms of Service and Privacy Policy have not been acknowledged." + "message": "شرایط خدمات و سیاست حفظ حریم خصوصی تأیید نشده است." }, "termsOfService": { - "message": "Terms of Service" + "message": "شرایط استفاده از خدمات" }, "privacyPolicy": { - "message": "Privacy Policy" + "message": "سیاست حفظ حریم خصوصی" }, "filters": { - "message": "Filters" + "message": "فیلترها" }, "vaultTimeout": { - "message": "Vault timeout" + "message": "متوقف شدن گاو‌صندوق" }, "vaultTimeoutDesc": { - "message": "Choose when your vault will take the vault timeout action." + "message": "انتخاب کنید که گاو‌صندوق شما چه زمانی عمل توقف زمانی گاوصندوق را انجام دهد." }, "oneMinute": { - "message": "1 minute" + "message": "۱ دقیقه" }, "fiveMinutes": { - "message": "5 minutes" + "message": "۵ دقیقه" }, "fifteenMinutes": { - "message": "15 minutes" + "message": "۱۵ دقیقه" }, "thirtyMinutes": { - "message": "30 minutes" + "message": "۳۰ دقیقه" }, "oneHour": { - "message": "1 hour" + "message": "۱ ساعت" }, "fourHours": { - "message": "4 hours" + "message": "4 ساعت" }, "onRefresh": { - "message": "On browser refresh" + "message": "در به‌روزرسانی مرورگر" }, "dateUpdated": { - "message": "Updated", + "message": "به‌روزرسانی شد", "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "ایجاد شد", "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Password updated", + "message": "کلمه عبور به‌روزرسانی شد", "description": "ex. Date this password was updated" }, "organizationIsDisabled": { - "message": "Organization suspended" + "message": "سازمان از کار افتاده است" }, "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + "message": "موارد موجود در سازمان‌های غیرفعال، قابل دسترسی نیستند. برای دریافت کمک با مالک سازمان خود تماس بگیرید." }, "licenseIsExpired": { - "message": "License is expired." + "message": "مجوز منقضی شده است." }, "updatedUsers": { - "message": "Updated users" + "message": "کاربران به‌روز شده" }, "selected": { - "message": "Selected" + "message": "انتخاب شده" }, "ownership": { - "message": "Ownership" + "message": "مالکیت" }, "whoOwnsThisItem": { - "message": "Who owns this item?" + "message": "چه کسی مالک این مورد است؟" }, "strong": { - "message": "Strong", + "message": "قوی", "description": "ex. A strong password. Scale: Very Weak -> Weak -> Good -> Strong" }, "good": { - "message": "Good", + "message": "خوب", "description": "ex. A good password. Scale: Very Weak -> Weak -> Good -> Strong" }, "weak": { - "message": "Weak", + "message": "ضعیف", "description": "ex. A weak password. Scale: Very Weak -> Weak -> Good -> Strong" }, "veryWeak": { - "message": "Very Weak", + "message": "خیلی ضعیف", "description": "ex. A very weak password. Scale: Very Weak -> Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Weak master password" + "message": "کلمه عبور اصلی ضعیف" }, "weakMasterPasswordDesc": { - "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + "message": "کلمه عبور اصلی که شما انتخاب کرده اید ضعیف است. شما باید یک کلمه عبور اصلی قوی انتخاب کنید (یا یک عبارت عبور) تا به درستی از اکانت Bitwarden خود محافظت کنید. آیا مطمئن هستید می‌خواهید از این کلمه عبور اصلی استفاده کنید؟" }, "rotateAccountEncKey": { - "message": "Also rotate my account's encryption key" + "message": "همچنین کلید رمزگذاری حساب من را بچرخان" }, "rotateEncKeyTitle": { - "message": "Rotate encryption key" + "message": "چرخش کلید رمزگذاری" }, "rotateEncKeyConfirmation": { - "message": "Are you sure you want to rotate your account's encryption key?" + "message": "آیا مطمئنید که می‌خواهید کلید رمزگذاری حساب خود را بچرخانید؟" }, "attachmentsNeedFix": { - "message": "This item has old file attachments that need to be fixed." + "message": "این مورد دارای پرونده های پیوست قدیمی است که باید اصلاح شوند." }, "attachmentFixDesc": { - "message": "This is an old file attachment the needs to be fixed. Click to learn more." + "message": "این یک پرونده پیوست قدیمی است که باید اصلاح شود. برای کسب اطلاعات بیشتر کلیک کنید." }, "fix": { - "message": "Fix", + "message": "اصلاح", "description": "This is a verb. ex. 'Fix The Car'" }, "oldAttachmentsNeedFixDesc": { - "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." + "message": "پرونده‌های پیوستی قدیمی در گاوصندوق شما وجود دارد که قبل از اینکه بتوانید کلید رمزگذاری حساب خود را بچرخانید باید اصلاح شوند." }, "yourAccountsFingerprint": { - "message": "Your account's fingerprint phrase", + "message": "عبارت اثر انگشت حساب شما", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "fingerprintEnsureIntegrityVerify": { - "message": "To ensure the integrity of your encryption keys, please verify the user's fingerprint phrase before continuing.", + "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." }, "fingerprintMatchInfo": { - "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." + "message": "لطفاً مطمئن شوید که قفل گاوصندوق شما باز است و عبارت اثر انگشت در دستگاه دیگر مطابقت دارد." }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "عبارت اثر انگشت" }, "dontAskFingerprintAgain": { - "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", + "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": { - "message": "Free", + "message": "رايگان", "description": "Free, as in 'Free beer'" }, "apiKey": { - "message": "API Key" + "message": "کلید API" }, "apiKeyDesc": { - "message": "Your API key can be used to authenticate to the Bitwarden public API." + "message": "کلید API شما می‌تواند برای احراز هویت به API عمومی Bitwarden استفاده شود." }, "apiKeyRotateDesc": { - "message": "Rotating the API key will invalidate the previous key. You can rotate your API key if you believe that the current key is no longer safe to use." + "message": "چرخاندن کلید API، کلید قبلی را باطل می کند. اگر فکر می‌کنید که کلید فعلی دیگر برای استفاده ایمن نیست، می‌توانید کلید API خود را بچرخانید." }, "apiKeyWarning": { - "message": "Your API key has full access to the organization. It should be kept secret." + "message": "کلید API شما به سازمان دسترسی کامل دارد. باید مخفی بماند." }, "userApiKeyDesc": { - "message": "Your API key can be used to authenticate in the Bitwarden CLI." + "message": "کلید API شما می‌تواند برای احراز هویت در Bitwarden CLI استفاده شود." }, "userApiKeyWarning": { - "message": "Your API key is an alternative authentication mechanism. It should be kept secret." + "message": "کلید API شما یک مکانیسم احراز هویت جایگزین است. باید مخفی بماند." }, "oauth2ClientCredentials": { - "message": "OAuth 2.0 Client Credentials", + "message": "اعتبارنامه مشتری OAuth 2.0", "description": "'OAuth 2.0' is a programming protocol. It should probably not be translated." }, "viewApiKey": { - "message": "View API key" + "message": "مشاهده کلید API" }, "rotateApiKey": { - "message": "Rotate API key" + "message": "کلید API را بچرخانید" }, "selectOneCollection": { - "message": "You must select at least one collection." + "message": "شما باید حداقل یک مجموعه را انتخاب کنید." }, "couldNotChargeCardPayInvoice": { - "message": "We were not able to charge your card. Please view and pay the unpaid invoice listed below." + "message": "ما نتوانستیم کارت شما را شارژ کنیم. لطفاً فاکتور پرداخت نشده ذکر شده در زیر را مشاهده و پرداخت کنید." }, "inAppPurchase": { - "message": "In-app purchase" + "message": "پرداخت درون برنامه ای" }, "cannotPerformInAppPurchase": { - "message": "You cannot perform this action while using an in-app purchase payment method." + "message": "هنگام استفاده از روش پرداخت خرید درون برنامه ای، نمی‌توانید این عمل را انجام دهید." }, "manageSubscriptionFromStore": { - "message": "You must manage your subscription from the store where your in-app purchase was made." + "message": "باید اشتراک خود را از فروشگاهی که خرید درون برنامه ای شما انجام شده است مدیریت کنید." }, "minLength": { - "message": "Minimum length" + "message": "حداقل طول" }, "clone": { - "message": "Clone" + "message": "شبیه سازی" }, "masterPassPolicyTitle": { - "message": "Master password requirements" + "message": "کلمه عبور اصلی الزامیست" }, "masterPassPolicyDesc": { - "message": "Set requirements for master password strength." + "message": "الزامات را برای قدرت کلمه عبور اصلی تنظیم کنید." }, "twoStepLoginPolicyTitle": { - "message": "Require two-step login" + "message": "فعال کردن ورود دو مرحله ای الزامیست" }, "twoStepLoginPolicyDesc": { - "message": "Require members to set up two-step login." + "message": "از اعضا بخواهید که ورود دو مرحله ای را تنظیم کنند." }, "twoStepLoginPolicyWarning": { - "message": "Organization members who are not owners or admins and do not have two-step login setup for their account will be removed from the organization and will receive an email notifying them about the change." + "message": "اعضای سازمانی که مالک یا سرپرست نیستند و ورود دو مرحله ای برای حساب خود راه اندازی نکرده اند، از سازمان حذف می‌شوند و ایمیلی دریافت می‌کنند که به آن‌ها در مورد تغییر اطلاع می‌دهد." }, "twoStepLoginPolicyUserWarning": { - "message": "You are a member of an organization that requires two-step login to be setup on your user account. If you turn off all two-step login providers you will be automatically removed from these organizations." + "message": "شما عضو سازمانی هستید که برای راه اندازی حساب کاربری شما نیاز به ورود دو مرحله ای دارد. اگر همه ارائه دهندگان ورود به سیستم دو مرحله ای را خاموش کنید، به طور خودکار از این سازمان ها حذف خواهید شد." }, "passwordGeneratorPolicyDesc": { - "message": "Set requirements for password generator." + "message": "الزامات را برای تولید کننده کلمه عبور تنظیم کنید." }, "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." + "message": "یک یا چند سیاست سازمان بر تنظیمات تولید کننده شما تأثیر می‌گذارد." }, "masterPasswordPolicyInEffect": { - "message": "One or more organization policies require your master password to meet the following requirements:" + "message": "یک یا چند سیاست سازمانی برای تأمین شرایط زیر به کلمه عبور اصلی شما احتیاج دارد:" }, "policyInEffectMinComplexity": { - "message": "Minimum complexity score of $SCORE$", + "message": "حداقل نمره پیچیدگی $SCORE$", "placeholders": { "score": { "content": "$1", @@ -3525,7 +3541,7 @@ } }, "policyInEffectMinLength": { - "message": "Minimum length of $LENGTH$", + "message": "حداقل طول $LENGTH$", "placeholders": { "length": { "content": "$1", @@ -3534,16 +3550,16 @@ } }, "policyInEffectUppercase": { - "message": "Contain one or more uppercase characters" + "message": "حاوی یک یا چند کاراکتر بزرگ" }, "policyInEffectLowercase": { - "message": "Contain one or more lowercase characters" + "message": "حاوی یک یا چند کاراکتر کوچک" }, "policyInEffectNumbers": { - "message": "Contain one or more numbers" + "message": "حاوی یک یا چند عدد بیشتر" }, "policyInEffectSpecial": { - "message": "Contain one or more of the following special characters $CHARS$", + "message": "حاوی یک یا چند کاراکتر خاص زیر است $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -3552,57 +3568,57 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "Your new master password does not meet the policy requirements." + "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پیروی نمی‌کند." }, "minimumNumberOfWords": { - "message": "Minimum number of words" + "message": "حداقل تعداد کلمات" }, "defaultType": { - "message": "Default type" + "message": "نوع پیش‌فرض" }, "userPreference": { - "message": "User preference" + "message": "تنظیمات کاربر" }, "vaultTimeoutAction": { - "message": "Vault timeout action" + "message": "عمل متوقف شدن گاو‌صندوق" }, "vaultTimeoutActionLockDesc": { - "message": "Master password or other unlock method is required to access your vault again." + "message": "یک گاوصندوق قفل شده درخواست وارد کردن مجدد کلمه عبور اصلی را برای دسترسی می‌دهد." }, "vaultTimeoutActionLogOutDesc": { - "message": "Re-authentication is required to access your vault again." + "message": "یک گاوصندوق خارج شده درخواست احراز هویت مجدد را برای دسترسی آن می‌دهد." }, "lock": { - "message": "Lock", + "message": "قفل", "description": "Verb form: to make secure or inaccesible by" }, "trash": { - "message": "Trash", + "message": "زباله‌ها", "description": "Noun: A special folder for holding deleted items that have not yet been permanently deleted" }, "searchTrash": { - "message": "Search trash" + "message": "جستجوی زباله‌ها" }, "permanentlyDelete": { - "message": "Permanently delete" + "message": "حذف دائمی" }, "permanentlyDeleteSelected": { - "message": "Permanently delete selected" + "message": "حذف دائمی انتخاب شد" }, "permanentlyDeleteItem": { - "message": "Permanently delete item" + "message": "حذف دائمی مورد" }, "permanentlyDeleteItemConfirmation": { - "message": "Are you sure you want to permanently delete this item?" + "message": "مطمئن هستید که می‌خواهید این مورد را برای همیشه پاک کنید؟" }, "permanentlyDeletedItem": { - "message": "Item permanently deleted" + "message": "مورد برای همیشه حذف شد" }, "permanentlyDeletedItems": { - "message": "Items permanently deleted" + "message": "موارد برای همیشه حذف شد" }, "permanentlyDeleteSelectedItemsDesc": { - "message": "You have selected $COUNT$ item(s) to permanently delete. Are you sure you want to permanently delete all of these items?", + "message": "شما $COUNT$ مورد را برای حذف دائمی انتخاب کرده اید. آیا مطمئنید که می‌خواهید همه این موارد را برای همیشه حذف کنید؟", "placeholders": { "count": { "content": "$1", @@ -3611,7 +3627,7 @@ } }, "permanentlyDeletedItemId": { - "message": "Item $ID$ permanently deleted", + "message": "مورد $ID$ برای همیشه حذف شد", "placeholders": { "id": { "content": "$1", @@ -3620,28 +3636,28 @@ } }, "restore": { - "message": "Restore" + "message": "بازیابی" }, "restoreSelected": { - "message": "Restore selected" + "message": "بازیابی انتخاب شده" }, "restoreItem": { - "message": "Restore item" + "message": "بازیابی مورد" }, "restoredItem": { - "message": "Item restored" + "message": "مورد بازیابی شد" }, "restoredItems": { - "message": "Items restored" + "message": "موارد بازیابی شد" }, "restoreItemConfirmation": { - "message": "Are you sure you want to restore this item?" + "message": "آیا مطمئن هستید که می‌خواهید این مورد را بازیابی کنید؟" }, "restoreItems": { - "message": "Restore items" + "message": "بازیابی موارد" }, "restoreSelectedItemsDesc": { - "message": "You have selected $COUNT$ item(s) to restore. Are you sure you want to restore all of these items?", + "message": "شما $COUNT$ مورد را برای بازیابی انتخاب کرده اید. آیا مطمئنید که می‌خواهید تمام این موارد را بازیابی کنید؟", "placeholders": { "count": { "content": "$1", @@ -3650,7 +3666,7 @@ } }, "restoredItemId": { - "message": "Item $ID$ restored", + "message": "مورد $ID$ بازیابی شد", "placeholders": { "id": { "content": "$1", @@ -3659,310 +3675,310 @@ } }, "vaultTimeoutLogOutConfirmation": { - "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" + "message": "خروج از سیستم، تمام دسترسی ها به گاو‌صندوق شما را از بین می‌برد و نیاز به احراز هویت آنلاین پس از مدت زمان توقف دارد. آیا مطمئن هستید که می‌خواهید از این تنظیمات استفاده کنید؟" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Timeout action confirmation" + "message": "تأیید عمل توقف" }, "hidePasswords": { - "message": "Hide passwords" + "message": "پنهان کردن کلمه‌های عبور" }, "countryPostalCodeRequiredDesc": { - "message": "We require this information for calculating sales tax and financial reporting only." + "message": "ما فقط برای محاسبه مالیات فروش و گزارش مالی به این اطلاعات نیاز داریم." }, "includeVAT": { - "message": "Include VAT/GST Information (optional)" + "message": "شامل اطلاعات VAT/GST (اختیاری)" }, "taxIdNumber": { - "message": "VAT/GST Tax ID" + "message": "شناسه مالیات VAT/GST" }, "taxInfoUpdated": { - "message": "Tax information updated." + "message": "اطلاعات مالیاتی به‌روز شد." }, "setMasterPassword": { - "message": "Set master password" + "message": "تنظیم کلمه عبور اصلی" }, "ssoCompleteRegistration": { - "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." + "message": "برای پر کردن ورود به سیستم با SSO، لطفاً یک کلمه عبور اصلی برای دسترسی و محافظت از گاوصندوق خود تنظیم کنید." }, "identifier": { - "message": "Identifier" + "message": "شناسه" }, "organizationIdentifier": { - "message": "Organization identifier" + "message": "شناسه سازمان" }, "ssoLogInWithOrgIdentifier": { - "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." + "message": "با استفاده از پورتال ورود واحد سازمان خود وارد شوید. لطفاً برای شروع، شناسه SSO سازمان خود را وارد کنید." }, "enterpriseSingleSignOn": { - "message": "Enterprise single sign-on" + "message": "ورود به سیستم پروژه" }, "ssoHandOff": { - "message": "You may now close this tab and continue in the extension." + "message": "اکنون می‌توانید این برگه را ببندید و در افزونه ادامه دهید." }, "includeAllTeamsFeatures": { - "message": "All Teams features, plus:" + "message": "همه ویژگی های تیم، به علاوه:" }, "includeSsoAuthentication": { - "message": "SSO Authentication via SAML2.0 and OpenID Connect" + "message": "احراز هویت SSO از طریق SAML2.0 و OpenID Connect" }, "includeEnterprisePolicies": { - "message": "Enterprise policies" + "message": "سیاست های سازمانی" }, "ssoValidationFailed": { - "message": "SSO validation failed" + "message": "تأیید SSO ناموفق بود" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "شناسه سازمان SSO مورد نیاز است." }, "ssoIdentifier": { - "message": "SSO identifier" + "message": "شناسه SSO" }, "ssoIdentifierHint": { - "message": "Provide this ID to your members to login with SSO." + "message": "این شناسه را در اختیار اعضای خود قرار دهید تا با SSO وارد شوند." }, "unlinkSso": { - "message": "Unlink SSO" + "message": "لغو پیوند SSO" }, "unlinkSsoConfirmation": { - "message": "Are you sure you want to unlink SSO for this organization?" + "message": "آیا مطمئنید که می‌خواهید پیوند SSO را برای این سازمان لغو کنید؟" }, "linkSso": { - "message": "Link SSO" + "message": "پیوند SSO" }, "singleOrg": { - "message": "Single organization" + "message": "سازمان واحد" }, "singleOrgDesc": { - "message": "Restrict members from joining other organizations." + "message": "اعضا را از پیوستن به سازمان‌های دیگر محدود کنید." }, "singleOrgBlockCreateMessage": { - "message": "Your current organization has a policy that does not allow you to join more than one organization. Please contact your organization admins or sign up from a different Bitwarden account." + "message": "سازمان فعلی شما سیاستی دارد که به شما اجازه نمی‌دهد به بیش از یک سازمان بپیوندید. لطفاً با مدیران سازمان خود تماس بگیرید یا از یک حساب Bitwarden دیگر ثبت نام کنید." }, "singleOrgPolicyWarning": { - "message": "Organization members who are not owners or admins and are already a member of another organization will be removed from your organization." + "message": "اعضای سازمانی که مالک یا سرپرست نیستند و از قبل عضو سازمان دیگری هستند از سازمان شما حذف خواهند شد." }, "requireSso": { - "message": "Require single sign-on authentication" + "message": "نیاز به احراز هویت یکبار ورود به سیستم" }, "requireSsoPolicyDesc": { - "message": "Require members to log in with the Enterprise single sign-on method." + "message": "از اعضا بخواهید که با روش پرمیوم یکبار ورود وارد شوند." }, "prerequisite": { - "message": "Prerequisite" + "message": "پیش‌نیاز" }, "requireSsoPolicyReq": { - "message": "The single organization Enterprise policy must be turned on before activating this policy." + "message": "قبل از فعال کردن این سیاست، سیاست سازمانی واحد باید روشن باشد." }, "requireSsoPolicyReqError": { - "message": "Single organization policy not set up." + "message": "سیاست سازمان واحد تنظیم نشده است." }, "requireSsoExemption": { - "message": "Organization owners and admins are exempt from this policy's enforcement." + "message": "مالکان و سرپرستان سازمان از اجرای این سیاست مستثنی هستند." }, "sendTypeFile": { - "message": "File" + "message": "پرونده" }, "sendTypeText": { - "message": "Text" + "message": "متن" }, "createSend": { - "message": "New Send", + "message": "ارسال جدید", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { - "message": "Edit Send", + "message": "ویرایش ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send saved", + "message": "ارسال ذخیره شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send saved", + "message": "ارسال ذخیره شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "Send deleted", + "message": "ارسال حذف شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSend": { - "message": "Delete Send", + "message": "حذف ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", + "message": "آیا مطمئن هستید که می‌خواهید این ارسال را حذف کنید؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "whatTypeOfSend": { - "message": "What type of Send is this?", + "message": "این چه نوع ارسالی است؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { - "message": "Deletion date" + "message": "تاریخ حذف" }, "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "message": "ارسال در تاریخ و ساعت مشخص شده برای همیشه حذف خواهد شد.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { - "message": "Expiration date" + "message": "تاريخ انقضاء" }, "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", + "message": "در صورت تنظیم، دسترسی به این ارسال در تاریخ و ساعت مشخص شده منقضی می‌شود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { - "message": "Maximum access count" + "message": "تعداد دسترسی حداکثر" }, "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", + "message": "در صورت تنظیم، با رسیدن به حداکثر تعداد دسترسی، کاربران دیگر نمی‌توانند به این ارسال دسترسی پیدا کنند.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { - "message": "Current access count" + "message": "تعداد دسترسی فعلی" }, "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", + "message": "به صورت اختیاری برای دسترسی کاربران به این ارسال به یک کلمه عبور نیاز دارید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNotesDesc": { - "message": "Private notes about this Send.", + "message": "یادداشت های خصوصی در مورد این ارسال.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disabled": { - "message": "Disabled" + "message": "غیرفعال شد" }, "revoked": { - "message": "Revoked" + "message": "لغو شد" }, "sendLink": { - "message": "Send link", + "message": "ارسال پیوند", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLink": { - "message": "Copy Send link", + "message": "پیوند ارسال را کپی کن", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { - "message": "Remove password" + "message": "حذف کلمه عبور" }, "removedPassword": { - "message": "Password removed" + "message": "کلمه عبور حذف شد" }, "removePasswordConfirmation": { - "message": "Are you sure you want to remove the password?" + "message": "مطمئنید که می‌خواهید کلمه عبور حذف شود؟" }, "hideEmail": { - "message": "Hide my email address from recipients." + "message": "نشانی ایمیلم را از گیرندگان مخفی کن." }, "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", + "message": "این ارسال را غیرفعال کنید تا کسی نتواند به آن دسترسی پیدا کند.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "allSends": { - "message": "All Sends" + "message": "همه ارسال ها" }, "maxAccessCountReached": { - "message": "Max access count reached", + "message": "به حداکثر تعداد دسترسی رسیده است", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "pendingDeletion": { - "message": "Pending deletion" + "message": "در انتظار حذف" }, "expired": { - "message": "Expired" + "message": "منقضی شده" }, "searchSends": { - "message": "Search Sends", + "message": "جستجوی ارسال‌ها", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendProtectedPassword": { - "message": "This Send is protected with a password. Please type the password below to continue.", + "message": "این ارسال با کلمه عبور محافظت می‌شود. لطفاً برای ادامه کلمه عبور زیر را تایپ کنید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendProtectedPasswordDontKnow": { - "message": "Don't know the password? Ask the sender for the password needed to access this Send.", + "message": "کلمه عبور را نمی‌دانید؟ از فرستنده کلمه عبور لازم را برای دسترسی به این ارسال بخواهید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", + "message": "این ارسال به طور پیش‌فرض پنهان است. با استفاده از دکمه زیر می‌توانید نمایان بودن آن را تغییر دهید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "downloadFile": { - "message": "Download file" + "message": "بارگیری پرونده" }, "sendAccessUnavailable": { - "message": "The Send you are trying to access does not exist or is no longer available.", + "message": "ارسالی که می‌خواهید به آن دسترسی پیدا کنید وجود ندارد یا دیگر در دسترس نیست.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "missingSendFile": { - "message": "The file associated with this Send could not be found.", + "message": "پرونده مرتبط با این ارسال یافت نشد.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "noSendsInList": { - "message": "There are no Sends to list.", + "message": "هیچ \"ارسالی\" برای نمایش وجود ندارد.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "emergencyAccess": { - "message": "Emergency access" + "message": "دسترسی اضطراری" }, "emergencyAccessDesc": { - "message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of an emergency. Visit our help page for more information and details into how zero knowledge sharing works." + "message": "اعطا و مدیریت دسترسی اضطراری برای مخاطبین مورد اعتماد. مخاطبین معتمد ممکن است در مواقع اضطراری درخواست دسترسی به مشاهده یا تصاحب حساب شما را کنند. برای اطلاعات بیشتر و جزئیات در مورد نحوه عملکرد اشتراک دانش صفر، از صفحه راهنمای ما دیدن کنید." }, "emergencyAccessOwnerWarning": { - "message": "You are an owner of one or more organizations. If you give takeover access to an emergency contact, they will be able to use all your permissions as owner after a takeover." + "message": "شما مالک یک یا چند سازمان هستید. اگر به یک مخاطب اضطراری دسترسی تصاحب بدهید، آن‌ها می‌توانند از تمام مجوزهای شما به عنوان مالک پس از تصاحب استفاده کنند." }, "trustedEmergencyContacts": { - "message": "Trusted emergency contacts" + "message": "مخاطبین اضطراری مورد اعتماد" }, "noTrustedContacts": { - "message": "You have not added any emergency contacts yet, invite a trusted contact to get started." + "message": "هنوز هیچ مخاطب اضطراری اضافه نکرده‌اید، برای شروع از یک مخاطب مورد اعتماد دعوت کنید." }, "addEmergencyContact": { - "message": "Add emergency contact" + "message": "افزودن یک مخاطب اضطراری" }, "designatedEmergencyContacts": { - "message": "Designated as emergency contact" + "message": "به عنوان تماس اضطراری تعیین شده است" }, "noGrantedAccess": { - "message": "You have not been designated as an emergency contact for anyone yet." + "message": "شما هنوز به عنوان مخاطب اضطراری برای کسی تعیین نشده اید." }, "inviteEmergencyContact": { - "message": "Invite emergency contact" + "message": "مخاطب اضطراری را دعوت کنید" }, "editEmergencyContact": { - "message": "Edit emergency contact" + "message": "ویرایش مخاطب اضطراری" }, "inviteEmergencyContactDesc": { - "message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + "message": "با وارد کردن نشانی ایمیل حساب Bitwarden در زیر، یک مخاطب اضطراری جدید را دعوت کنید. اگر آن‌ها قبلاً یک حساب Bitwarden نداشته باشند، از آن‌ها خواسته می‌شود که یک حساب جدید ایجاد کنند." }, "emergencyAccessRecoveryInitiated": { - "message": "Emergency access initiated" + "message": "دسترسی اضطراری آغاز شد" }, "emergencyAccessRecoveryApproved": { - "message": "Emergency access approved" + "message": "دسترسی اضطراری تأیید شد" }, "viewDesc": { - "message": "Can view all items in your own vault." + "message": "می‌توانید همه موارد را در گاوصندوق خود مشاهده کنید." }, "takeover": { - "message": "Takeover" + "message": "به عهده گرفتن" }, "takeoverDesc": { - "message": "Can reset your account with a new master password." + "message": "می‌توانید حساب خود را با کلمه عبور اصلی جدید بازنشانی کنید." }, "waitTime": { - "message": "Wait time" + "message": "زمان انتظار" }, "waitTimeDesc": { - "message": "Time required before automatically granting access." + "message": "زمان لازم قبل از اعطای خودکار دسترسی." }, "oneDay": { - "message": "1 day" + "message": "۱ روز" }, "days": { - "message": "$DAYS$ days", + "message": "$DAYS$ روز", "placeholders": { "days": { "content": "$1", @@ -3971,16 +3987,16 @@ } }, "invitedUser": { - "message": "Invited user." + "message": "کاربر دعوت شد." }, "acceptEmergencyAccess": { - "message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + "message": "از شما دعوت شده است که یک مخاطب اضطراری برای کاربر فهرست شده در بالا شوید. برای پذیرش دعوت، باید وارد شوید یا یک حساب کاربری جدید در Bitwarden ایجاد کنید." }, "emergencyInviteAcceptFailed": { - "message": "Unable to accept invitation. Ask the user to send a new invitation." + "message": "قادر به پذیرش دعوت نیست. از کاربر بخواهید یک دعوتنامه جدید ارسال کند." }, "emergencyInviteAcceptFailedShort": { - "message": "Unable to accept invitation. $DESCRIPTION$", + "message": "قادر به پذیرش دعوت نیست. $DESCRIPTION$", "placeholders": { "description": { "content": "$1", @@ -3989,13 +4005,13 @@ } }, "emergencyInviteAcceptedDesc": { - "message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens." + "message": "پس از تأیید هویت، شما می‌توانید به گزینه‌های اضطراری برای این کاربر دسترسی داشته باشید. زمانی که این اتفاق بیفتد، یک ایمیل برای شما ارسال خواهیم کرد." }, "requestAccess": { - "message": "Request Access" + "message": "درخواست دسترسی" }, "requestAccessConfirmation": { - "message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.", + "message": "آیا مطمئنید که می‌خواهید درخواست دسترسی اضطراری کنید؟ پس از $WAITTIME$ روز یا هر زمان که کاربر به صورت دستی درخواست را تأیید کرد، به شما دسترسی داده می‌شود.", "placeholders": { "waittime": { "content": "$1", @@ -4004,7 +4020,7 @@ } }, "requestSent": { - "message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.", + "message": "دسترسی اضطراری برای $USER$ درخواست شد. در صورت امکان ادامه، از طریق ایمیل به شما اطلاع خواهیم داد.", "placeholders": { "user": { "content": "$1", @@ -4013,13 +4029,13 @@ } }, "approve": { - "message": "Approve" + "message": "تأیید" }, "reject": { - "message": "Reject" + "message": "رد کردن" }, "approveAccessConfirmation": { - "message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.", + "message": "آیا مطمئنید که می‌خواهید دسترسی اضطراری را تأیید کنید؟ این به $USER$ اجازه می‌دهد تا حساب شما را به $ACTION$ برساند.", "placeholders": { "user": { "content": "$1", @@ -4032,13 +4048,13 @@ } }, "emergencyApproved": { - "message": "Emergency access approved" + "message": "دسترسی اضطراری تأیید شد" }, "emergencyRejected": { - "message": "Emergency access rejected" + "message": "دسترسی اضطراری رد شد" }, "passwordResetFor": { - "message": "Password reset for $USER$. You can now login using the new password.", + "message": "کلمه عبور برای $USER$ بازنشانی شد. اکنون می‌توانید با استفاده از کلمه عبور جدید وارد شوید.", "placeholders": { "user": { "content": "$1", @@ -4047,59 +4063,59 @@ } }, "personalOwnership": { - "message": "Remove individual vault" + "message": "حذف گاوصندوق شخصی" }, "personalOwnershipPolicyDesc": { - "message": "Require members to save items to an organization by removing the individual vault option." + "message": "از اعضا بخواهید که با حذف گزینه گاوصندوق فردی، موارد را در یک سازمان ذخیره کنند." }, "personalOwnershipExemption": { - "message": "Organization owners and administrators are exempt from this policy's enforcement." + "message": "مالکان و مدیران سازمان از اجرای این سیاست مستثنی هستند." }, "personalOwnershipSubmitError": { - "message": "Due to an Enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." + "message": "به دلیل سیاست پرمیوم، برای ذخیره موارد در گاوصندوق شخصی خود محدود شده اید. گزینه مالکیت را به یک سازمان تغییر دهید و مجموعه های موجود را انتخاب کنید." }, "disableSend": { - "message": "Remove Send" + "message": "حذف ارسال" }, "disableSendPolicyDesc": { - "message": "Do not allow members to create or edit Sends.", + "message": "به اعضا اجازه ایجاد یا ویرایش ارسال‌ها را ندهید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disableSendExemption": { - "message": "Organization members that can manage the organization's policies are exempt from this policy's enforcement." + "message": "اعضای سازمانی که می‌توانند سیاست‌های سازمان را مدیریت کنند از اجرای این سیاست مستثنی هستند." }, "sendDisabled": { - "message": "Send removed", + "message": "ارسال حذف شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Due to an Enterprise policy, you are only able to delete an existing Send.", + "message": "به دلیل سیاست سازمانی، شما فقط می‌توانید ارسال موجود را حذف کنید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptions": { - "message": "Send options", + "message": "گزینه‌های ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsPolicyDesc": { - "message": "Set options for creating and editing Sends.", + "message": "گزینه‌هایی را برای ایجاد و ویرایش ارسال‌ها تنظیم کنید.", "description": "'Sends' is a plural noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsExemption": { - "message": "Organization members that can manage the organization's policies are exempt from this policy's enforcement." + "message": "اعضای سازمانی که می‌توانند سیاست‌های سازمان را مدیریت کنند از اجرای این سیاست مستثنی هستند." }, "disableHideEmail": { - "message": "Always show member’s email address with recipients when creating or editing a Send.", + "message": "هنگام ایجاد یا ویرایش ارسال، همیشه نشانی ایمیل اعضا را به گیرندگان نشان بده.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" + "message": "سیاست‌های سازمان زیر در حال حاضر در حال اجرا هستند:" }, "sendDisableHideEmailInEffect": { - "message": "Users are not allowed to hide their email address from recipients when creating or editing a Send.", + "message": "کاربران مجاز به مخفی کردن نشانی ایمیل خود در هنگام ایجاد یا ویرایش ارسال از گیرندگان نیستند.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "modifiedPolicyId": { - "message": "Modified policy $ID$.", + "message": "سیاست تغییر یافته $ID$.", "placeholders": { "id": { "content": "$1", @@ -4108,94 +4124,94 @@ } }, "planPrice": { - "message": "Plan price" + "message": "قیمت طرح" }, "estimatedTax": { - "message": "Estimated tax" + "message": "مالیات برآورد شده" }, "custom": { - "message": "Custom" + "message": "سفارشی" }, "customDesc": { - "message": "Grant customized permissions to members" + "message": "اعطای مجوزهای سفارشی به اعضا" }, "customDescNonEnterpriseStart": { - "message": "Custom roles is an ", + "message": "نقش های سفارشی است ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customDescNonEnterpriseLink": { - "message": "enterprise feature", + "message": "ویژگی سازمانی", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customDescNonEnterpriseEnd": { - "message": ". Contact our support team to upgrade your subscription", + "message": ". برای ارتقاء اشتراک خود با تیم پشتیبانی ما تماس بگیرید", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customNonEnterpriseError": { - "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + "message": "برای فعال کردن مجوزهای سفارشی، سازمان باید در برنامه پرمیوم ۲۰۲۰ باشد." }, "permissions": { - "message": "Permissions" + "message": "مجوزها" }, "permission": { - "message": "Permission" + "message": "مجوز" }, "managerPermissions": { - "message": "Manager Permissions" + "message": "مدیریت مجوزها" }, "adminPermissions": { - "message": "Admin Permissions" + "message": "مجوزهای مدیریت" }, "accessEventLogs": { - "message": "Access event logs" + "message": "به گزارش‌های رویداد دسترسی داشته باشید" }, "accessImportExport": { - "message": "Access import/export" + "message": "دسترسی به برون ریزی/درون ریزی" }, "accessReports": { - "message": "Access reports" + "message": "دسترسی به گزارش‌ها" }, "missingPermissions": { - "message": "You lack the necessary permissions to perform this action." + "message": "شما فاقد مجوزهای لازم برای انجام این عمل هستید." }, "manageAllCollections": { - "message": "Manage all collections" + "message": "مدیریت تمام مجموعه‌ها" }, "createNewCollections": { - "message": "Create new collections" + "message": "ایجاد مجموعه‌های جدید" }, "editAnyCollection": { - "message": "Edit any collection" + "message": "تمام مجموعه‌ها ویرایش را ویرایش کنید" }, "deleteAnyCollection": { - "message": "Delete any collection" + "message": "هر مجموعه ای را حذف کنید" }, "manageAssignedCollections": { - "message": "Manage assigned collections" + "message": "مدیریت مجموعه های اختصاص داده شده" }, "editAssignedCollections": { - "message": "Edit assigned collections" + "message": "ویرایش مجموعه های اختصاص داده شده" }, "deleteAssignedCollections": { - "message": "Delete assigned collections" + "message": "حذف مجموعه های اختصاص داده شده" }, "manageGroups": { - "message": "Manage groups" + "message": "مدیریت گروه‌ها" }, "managePolicies": { - "message": "Manage policies" + "message": "مدیریت سیاست ها" }, "manageSso": { - "message": "Manage SSO" + "message": "مدیریت SSO" }, "manageUsers": { - "message": "Manage users" + "message": "مدیریت کاربران" }, "manageResetPassword": { - "message": "Manage password reset" + "message": "مدیریت بازنشانی کلمه عبور" }, "disableRequiredError": { - "message": "You must manually turn the $POLICYNAME$ policy before this policy can be turned off.", + "message": "قبل از اینکه این سیاست غیرفعال شود، باید سیاست $POLICYNAME$ را به صورت دستی تغییر دهید.", "placeholders": { "policyName": { "content": "$1", @@ -4204,84 +4220,84 @@ } }, "personalOwnershipPolicyInEffect": { - "message": "An organization policy is affecting your ownership options." + "message": "سیاست سازمانی بر تنظیمات مالکیت شما تأثیر می‌گذارد." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "یک سیاست سازمانی، درون ریزی موارد به گاوصندوق فردی شما را مسدود کرده است." }, "personalOwnershipCheckboxDesc": { - "message": "Remove individual ownership for organization users" + "message": "حذف مالکیت فردی برای کاربران سازمان" }, "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", + "message": "هنگام دسترسی به ارسال، متن را به طور پیش فرض پنهان کن", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNameDesc": { - "message": "A friendly name to describe this Send.", + "message": "یک نام دوستانه برای توصیف این ارسال.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { - "message": "The text you want to Send." + "message": "متنی که می‌خواهید ارسال کنید." }, "sendFileDesc": { - "message": "The file you want to Send." + "message": "پرونده ای که می‌خواهید ارسال کنید." }, "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." + "message": "این پیوند را برای به اشتراک گذاری ارسال بعد از ارسال کپی کن." }, "sendLinkLabel": { - "message": "Send link", + "message": "ارسال پیوند", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "send": { - "message": "Send", + "message": "ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAccessTaglineProductDesc": { - "message": "Bitwarden Send transmits sensitive, temporary information to others easily and securely.", + "message": "\"ارسال\" Bitwarden اطلاعات حساس و موقتی را به راحتی و ایمن به دیگران منتقل می‌کند.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAccessTaglineLearnMore": { - "message": "Learn more about", + "message": "درباره‌ اش بیش‌تر بدانید", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**Learn more about** Bitwarden Send or sign up to try it today.'" }, "sendVaultCardProductDesc": { - "message": "Share text or files directly with anyone." + "message": "متن یا پرونده‌ها را مستقیماً با هر کسی به اشتراک بگذارید." }, "sendVaultCardLearnMore": { - "message": "Learn more", + "message": "بیشتر بدانید", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**Learn more**, see how it works, or try it now. '" }, "sendVaultCardSee": { - "message": "see", + "message": "ببینید", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, **see** how it works, or try it now.'" }, "sendVaultCardHowItWorks": { - "message": "how it works", + "message": "نحوه عملکرد", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see **how it works**, or try it now.'" }, "sendVaultCardOr": { - "message": "or", + "message": "یا", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, **or** try it now.'" }, "sendVaultCardTryItNow": { - "message": "try it now", + "message": "الان امتحان کنید", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, or **try it now**.'" }, "sendAccessTaglineOr": { - "message": "or", + "message": "یا", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send **or** sign up to try it today.'" }, "sendAccessTaglineSignUp": { - "message": "sign up", + "message": "ثبت‌نام", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send or **sign up** to try it today.'" }, "sendAccessTaglineTryToday": { - "message": "to try it today.", + "message": "برای امتحان کردن امروز.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send or sign up to **try it today.**'" }, "sendCreatorIdentifier": { - "message": "Bitwarden user $USER_IDENTIFIER$ shared the following with you", + "message": "کاربر Bitwarden $USER_IDENTIFIER$ موارد زیر را با شما به اشتراک گذاشت", "placeholders": { "user_identifier": { "content": "$1", @@ -4290,56 +4306,56 @@ } }, "viewSendHiddenEmailWarning": { - "message": "The Bitwarden user who created this Send has chosen to hide their email address. You should ensure you trust the source of this link before using or downloading its content.", + "message": "کاربر Bitwarden که این ارسال را ایجاد کرده است انتخاب کرده که نشانی ایمیل خود را پنهان کند. قبل از استفاده یا دانلود محتوای این پیوند، باید مطمئن شوید که به منبع این پیوند اعتماد دارید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDateIsInvalid": { - "message": "The expiration date provided is not valid." + "message": "تاریخ انقضاء ارائه شده معتبر نیست." }, "deletionDateIsInvalid": { - "message": "The deletion date provided is not valid." + "message": "تاریخ حذف ارائه شده معتبر نیست." }, "expirationDateAndTimeRequired": { - "message": "An expiration date and time are required." + "message": "تاریخ انقضاء و زمان لازم است." }, "deletionDateAndTimeRequired": { - "message": "A deletion date and time are required." + "message": "تاریخ و زمان حذف لازم است." }, "dateParsingError": { - "message": "There was an error saving your deletion and expiration dates." + "message": "هنگام ذخیره حذف و تاریخ انقضاء شما خطایی روی داد." }, "webAuthnFallbackMsg": { - "message": "To verify your 2FA please click the button below." + "message": "برای تأیید 2FA خود لطفاً روی دکمه زیر کلیک کنید." }, "webAuthnAuthenticate": { - "message": "Authenticate WebAuthn" + "message": "تأیید اعتبار در WebAuthn" }, "webAuthnNotSupported": { - "message": "WebAuthn is not supported in this browser." + "message": "WebAuthn در این مرورگر پشتیبانی نمی‌شود." }, "webAuthnSuccess": { - "message": "WebAuthn verified successfully! You may close this tab." + "message": "WebAuthn با موفقیت تأیید شد! می‌توانید این برگه را ببندید." }, "hintEqualsPassword": { - "message": "Your password hint cannot be the same as your password." + "message": "اشاره به کلمه عبور شما نمی‌تواند همان کلمه عبور شما باشد." }, "enrollPasswordReset": { - "message": "Enroll in password reset" + "message": "در بازنشانی کلمه عبور ثبت نام کنید" }, "enrolledPasswordReset": { - "message": "Enrolled in password reset" + "message": "در بازنشانی کلمه عبور ثبت نام کردید" }, "withdrawPasswordReset": { - "message": "Withdraw from password reset" + "message": "از بازنشانی کلمه عبور خارج شوید" }, "enrollPasswordResetSuccess": { - "message": "Enrollment success!" + "message": "ثبت‌نام موفقیت آمیز بود!" }, "withdrawPasswordResetSuccess": { - "message": "Withdrawal success!" + "message": "خروج موفقیت آمیز بود!" }, "eventEnrollPasswordReset": { - "message": "User $ID$ enrolled in password reset.", + "message": "کاربر $ID$ در بازنشانی کلمه عبور ثبت‌نام کرد.", "placeholders": { "id": { "content": "$1", @@ -4348,7 +4364,7 @@ } }, "eventWithdrawPasswordReset": { - "message": "User $ID$ withdrew from password reset.", + "message": "کاربر $ID$ از بازنشانی کلمه عبور خارج شد.", "placeholders": { "id": { "content": "$1", @@ -4357,7 +4373,7 @@ } }, "eventAdminPasswordReset": { - "message": "Master password reset for user $ID$.", + "message": "بازنشانی کلمه عبور اصلی برای کاربر $ID$.", "placeholders": { "id": { "content": "$1", @@ -4366,7 +4382,7 @@ } }, "eventResetSsoLink": { - "message": "Reset SSO link for user $ID$", + "message": "بازنشانی پیوند SSO برای کاربر $ID$", "placeholders": { "id": { "content": "$1", @@ -4375,7 +4391,7 @@ } }, "firstSsoLogin": { - "message": "$ID$ logged in using Sso for the first time", + "message": "$ID$ برای اولین بار با استفاده از Sso وارد سیستم شد", "placeholders": { "id": { "content": "$1", @@ -4384,10 +4400,10 @@ } }, "resetPassword": { - "message": "Reset password" + "message": "تنظیم مجدد کلمه عبور" }, "resetPasswordLoggedOutWarning": { - "message": "Proceeding will log $NAME$ out of their current session, requiring them to log back in. Active sessions on other devices may continue to remain active for up to one hour.", + "message": "ادامه دادن، $NAME$ را از نشست فعلی اش خارج می‌کند و باید دوباره وارد سیستم شود. نشست فعال در دستگاه‌های دیگر ممکن است تا یک ساعت فعال بمانند.", "placeholders": { "name": { "content": "$1", @@ -4396,206 +4412,206 @@ } }, "thisUser": { - "message": "this user" + "message": "این کاربر" }, "resetPasswordMasterPasswordPolicyInEffect": { - "message": "One or more organization policies require the master password to meet the following requirements:" + "message": "یک یا چند سیاست سازمانی برای تأمین شرایط زیر به کلمه عبور اصلی احتیاج دارد:" }, "resetPasswordSuccess": { - "message": "Password reset success!" + "message": "بازیابی رمزعبور با موفقیت انجام شد!" }, "resetPasswordEnrollmentWarning": { - "message": "Enrollment will allow organization administrators to change your master password" + "message": "ثبت نام به مدیران سازمان امکان می‌دهد کلمه عبور اصلی شما را تغییر دهند" }, "resetPasswordPolicy": { - "message": "Master password reset" + "message": "کلمه عبور اصلی بازنشانی شد" }, "resetPasswordPolicyDescription": { - "message": "Allow admins to reset master passwords for members." + "message": "به مدیران اجازه دهید تا کلمه‌های عبور اصلی اعضا را بازنشانی کنند." }, "resetPasswordPolicyWarning": { - "message": "Members in the organization will need to self-enroll or be auto-enrolled before administrators can reset their master password." + "message": "قبل از اینکه مدیران بتوانند کلمه عبور اصلی خود را بازنشانی کنند، اعضای سازمان باید خودجوش ثبت نام کنند یا به صورت خودکار ثبت نام کنند." }, "resetPasswordPolicyAutoEnroll": { - "message": "Automatic enrollment" + "message": "ثبت نام خودکار" }, "resetPasswordPolicyAutoEnrollDescription": { - "message": "All members will be automatically enrolled in password reset once their invite is accepted and will not be allowed to withdraw." + "message": "پس از پذیرفته شدن دعوت، همه اعضا به‌طور خودکار در بازنشانی رمز عبور ثبت‌نام می‌شوند و اجازه انصراف نخواهند داشت." }, "resetPasswordPolicyAutoEnrollWarning": { - "message": "Members already in the organization will not be retroactively enrolled in password reset. They will need to self-enroll before administrators can reset their master password." + "message": "اعضایی که از قبل در سازمان هستند در بازنشانی کلمه عبور ثبت نام نخواهند کرد. قبل از اینکه مدیران بتوانند کلمه عبور اصلی خود را بازنشانی کنند، باید خود ثبت نام کنند." }, "resetPasswordPolicyAutoEnrollCheckbox": { - "message": "Require new members to be enrolled automatically" + "message": "لازم است اعضای جدید به طور خودکار ثبت‌نام شوند" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "This organization has an Enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + "message": "این سازمان دارای سیاست سازمانی ای است که به طور خودکار شما را در بازنشانی کلمه عبور ثبت نام می‌کند. این ثبت نام به مدیران سازمان اجازه می‌دهد تا کلمه عبور اصلی شما را تغییر دهند." }, "resetPasswordOrgKeysError": { - "message": "Organization keys response is null" + "message": "پاسخ کلیدهای سازمان صفر است" }, "resetPasswordDetailsError": { - "message": "Reset password details response is null" + "message": "پاسخ جزئیات کلمه عبور بازنشانی پوچ است" }, "trashCleanupWarning": { - "message": "Items that have been in trash more than 30 days will be automatically deleted." + "message": "مواردی که بیش از ۳۰ روز در سطل زباله بوده اند به طور خودکار حذف می‌شوند." }, "trashCleanupWarningSelfHosted": { - "message": "Items that have been in trash for a while will be automatically deleted." + "message": "مواردی که مدتی در سطل زباله بوده اند به طور خودکار حذف می‌شوند." }, "passwordPrompt": { - "message": "Master password re-prompt" + "message": "درخواست مجدد کلمه عبور اصلی" }, "passwordConfirmation": { - "message": "Master password confirmation" + "message": "تأیید کلمه عبور اصلی" }, "passwordConfirmationDesc": { - "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + "message": "این عمل محافظت می‌شود. برای ادامه، لطفاً کلمه عبور اصلی خود را دوباره وارد کنید تا هویت‌تان را تأیید کنید." }, "reinviteSelected": { - "message": "Resend invitations" + "message": "ارسال مجدد دعوتنامه‌ها" }, "resendNotification": { - "message": "Resend notification" + "message": "ارسال مجدد اعلان" }, "noSelectedUsersApplicable": { - "message": "This action is not applicable to any of the selected users." + "message": "این عمل برای هیچ یک از کاربران انتخاب شده قابل اجرا نیست." }, "removeUsersWarning": { - "message": "Are you sure you want to remove the following users? The process may take a few seconds to complete and cannot be interrupted or canceled." + "message": "آیا مطمئنید که می‌خواهید کاربران زیر را حذف کنید؟ فرآیند ممکن است چند ثانیه طول بکشد و نمی‌توان آن را قطع یا لغو کرد." }, "removeOrgUsersConfirmation": { - "message": "When member(s) are removed, they no longer have access to organization data and this action is irreversible. To add the member back to the organization, they must be invited and onboarded again. The process may take a few seconds to complete and cannot be interrupted or canceled." + "message": "هنگامی که اعضا حذف می‌شوند، دیگر به داده های سازمان دسترسی ندارند و این اقدام غیرقابل برگشت است. برای افزودن دوباره عضو به سازمان، باید دوباره دعوت شوند و وارد شوند. فرآیند ممکن است چند ثانیه طول بکشد و نمی‌توان آن را قطع یا لغو کرد." }, "revokeUsersWarning": { - "message": "When member(s) are revoked, they no longer have access to organization data. To quickly restore member access, go to the Revoked tab. The process may take a few seconds to complete and cannot be interrupted or canceled." + "message": "هنگامی که اعضا لغو می‌شوند، دیگر به داده‌های سازمان دسترسی ندارند. برای بازیابی سریع دسترسی اعضا، به برگه \"لغو شد\" بروید. فرآیند ممکن است چند ثانیه طول بکشد و نمی توان آن را قطع یا لغو کرد." }, "theme": { - "message": "Theme" + "message": "پوسته" }, "themeDesc": { - "message": "Choose a theme for your web vault." + "message": "یک پوسته برای گاوصندوق وب خود انتخاب کنید." }, "themeSystem": { - "message": "Use system theme" + "message": "استفاده از پوسته سیستم" }, "themeDark": { - "message": "Dark" + "message": "تاریک" }, "themeLight": { - "message": "Light" + "message": "روشن" }, "confirmSelected": { - "message": "Confirm selected" + "message": "انتخاب تأیید شد" }, "bulkConfirmStatus": { - "message": "Bulk action status" + "message": "وضعیت اقدام انبوه" }, "bulkConfirmMessage": { - "message": "Confirmed successfully" + "message": "با موفقیت تأیید شد" }, "bulkReinviteMessage": { - "message": "Reinvited successfully" + "message": "با موفقیت مجدد دعوت شد" }, "bulkRemovedMessage": { - "message": "Removed successfully" + "message": "با موفقیت حذف شد" }, "bulkRevokedMessage": { - "message": "Revoked organization access successfully" + "message": "دسترسی سازمان با موفقیت لغو شد" }, "bulkRestoredMessage": { - "message": "Restored organization access successfully" + "message": "دسترسی سازمان با موفقیت بازیابی شد" }, "bulkFilteredMessage": { - "message": "Excluded, not applicable for this action" + "message": "مستثنی شده، برای این اقدام قابل اجرا نیست" }, "fingerprint": { - "message": "Fingerprint" + "message": "اثر انگشت" }, "removeUsers": { - "message": "Remove users" + "message": "حذف کاربرها" }, "revokeUsers": { - "message": "Revoke users" + "message": "لغو کاربرها" }, "restoreUsers": { - "message": "Restore users" + "message": "بازیابی کاربرها" }, "error": { - "message": "Error" + "message": "خطا" }, "resetPasswordManageUsers": { - "message": "Manage users must also be granted with the manage password reset permission" + "message": "مدیر کاربران همچنین باید مجوز مدیریت بازنشانی کلمه عبور را داشته باشد" }, "setupProvider": { - "message": "Provider setup" + "message": "راه اندازی ارائه دهنده" }, "setupProviderLoginDesc": { - "message": "You've been invited to setup a new Provider. To continue, you need to log in or create a new Bitwarden account." + "message": "شما برای راه اندازی یک ارائه دهنده جدید دعوت شده اید. برای ادامه، باید وارد شوید یا یک حساب Bitwarden جدید ایجاد کنید." }, "setupProviderDesc": { - "message": "Please enter the details below to complete the Provider setup. Contact Customer Support if you have any questions." + "message": "لطفاً جزئیات زیر را وارد کنید تا راه اندازی ارائه دهنده کامل شود. اگر سوالی دارید با پشتیبانی مشتری تماس بگیرید." }, "providerName": { - "message": "Provider name" + "message": "نام ارائه دهنده" }, "providerSetup": { - "message": "Provider successfully set up" + "message": "ارائه دهنده با موفقیت راه اندازی شد" }, "clients": { - "message": "Clients" + "message": "مشتری‌ها" }, "client": { - "message": "Client", + "message": "مشتری", "description": "This is used as a table header to describe which client application created an event log." }, "providerAdmin": { - "message": "Provider admin" + "message": "مدیر ارائه دهنده" }, "providerAdminDesc": { - "message": "The highest access user that can manage all aspects of your Provider as well as access and manage client organizations." + "message": "بالاترین دسترسی کاربر که می‌تواند تمام جنبه‌های ارائه دهنده شما و همچنین دسترسی و مدیریت سازمان‌های مشتری را مدیریت کند." }, "serviceUser": { - "message": "Service user" + "message": "کاربر سرویس" }, "serviceUserDesc": { - "message": "Service users can access and manage all client organizations." + "message": "کاربران خدمات می‌توانند به تمام سازمان‌های مشتری دسترسی داشته باشند و مدیریت کنند." }, "providerInviteUserDesc": { - "message": "Invite a new user to your Provider by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + "message": "با وارد کردن نشانی ایمیل حساب Bitwarden در زیر، یک کاربر جدید را به ارائه دهنده خود دعوت کنید. اگر آنها قبلاً یک حساب Bitwarden نداشته باشند، از آنها خواسته می‌شود یک حساب جدید ایجاد کنند." }, "joinProvider": { - "message": "Join Provider" + "message": "پیوستن به ارائه دهنده" }, "joinProviderDesc": { - "message": "You've been invited to join the Provider listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + "message": "شما برای پیوستن به ارائه دهنده فهرست شده در بالا دعوت شده اید. برای پذیرش دعوت، باید وارد شوید یا یک حساب کاربری جدید در Bitwarden ایجاد کنید." }, "providerInviteAcceptFailed": { - "message": "Unable to accept invitation. Ask a Provider admin to send a new invitation." + "message": "قادر به پذیرش دعوت نیست. از مدیر ارائه دهنده بخواهید دعوتنامه جدیدی ارسال کند." }, "providerInviteAcceptedDesc": { - "message": "You can access this Provider once an administrator confirms your membership. We'll send you an email when that happens." + "message": "زمانی که یک سرپرست عضویت شما را تأیید کرد، می‌توانید به این ارائه دهنده دسترسی داشته باشید. زمانی که این اتفاق بیفتد، یک ایمیل برای شما ارسال خواهیم کرد." }, "providerUsersNeedConfirmed": { - "message": "You have users that have accepted their invitation, but still need to be confirmed. Users will not have access to the Provider until they are confirmed." + "message": "شما کاربرانی دارید که دعوت را پذیرفته اند، اما هنوز باید تأیید شوند. کاربران تا زمانی که تأیید نشوند به ارائه دهنده دسترسی نخواهند داشت." }, "provider": { - "message": "Provider" + "message": "ارائه دهنده" }, "newClientOrganization": { - "message": "New client organization" + "message": "سازمان مشتری جدید" }, "newClientOrganizationDesc": { - "message": "Create a new client organization that will be associated with you as the Provider. You will be able to access and manage this organization." + "message": "یک سازمان مشتری جدید ایجاد کنید که با شما به عنوان ارائه دهنده مرتبط باشد. شما می توانید به این سازمان دسترسی داشته باشید و آن را مدیریت کنید." }, "addExistingOrganization": { - "message": "Add existing organization" + "message": "سازمان موجود را اضافه کنید" }, "myProvider": { - "message": "My Provider" + "message": "ارائه دهنده‌ی من" }, "addOrganizationConfirmation": { - "message": "Are you sure you want to add $ORGANIZATION$ as a client to $PROVIDER$?", + "message": "آیا مطمئنید که می‌خواهید $ORGANIZATION$ را به عنوان مشتری به $PROVIDER$ اضافه کنید؟", "placeholders": { "organization": { "content": "$1", @@ -4608,10 +4624,10 @@ } }, "organizationJoinedProvider": { - "message": "Organization was successfully added to the Provider" + "message": "سازمان با موفقیت به ارائه دهنده اضافه شد" }, "accessingUsingProvider": { - "message": "Accessing organization using Provider $PROVIDER$", + "message": "دسترسی به سازمان با استفاده از ارائه دهنده $PROVIDER$", "placeholders": { "provider": { "content": "$1", @@ -4620,13 +4636,13 @@ } }, "providerIsDisabled": { - "message": "Provider suspended" + "message": "ارائه دهنده به حالت تعلیق درآمد" }, "providerUpdated": { - "message": "Provider saved" + "message": "ارائه دهنده ذخیره شد" }, "yourProviderIs": { - "message": "Your Provider is $PROVIDER$. They have administrative and billing privileges for your organization.", + "message": "ارائه‌دهنده شما $PROVIDER$ است. آن‌ها دارای امتیازات اداری و صدور صورتحساب برای سازمان شما هستند.", "placeholders": { "provider": { "content": "$1", @@ -4635,7 +4651,7 @@ } }, "detachedOrganization": { - "message": "The organization $ORGANIZATION$ has been detached from your Provider.", + "message": "سازمان $ORGANIZATION$ از ارائه دهنده شما جدا شده است.", "placeholders": { "organization": { "content": "$1", @@ -4644,43 +4660,43 @@ } }, "detachOrganizationConfirmation": { - "message": "Are you sure you want to detach this organization? The organization will continue to exist but will no longer be managed by the Provider." + "message": "آیا مطمئنید که می‌خواهید این سازمان را جدا کنید؟ سازمان به حیات خود ادامه خواهد داد اما دیگر توسط ارائه دهنده مدیریت نخواهد شد." }, "add": { - "message": "Add" + "message": "افزودن" }, "updatedMasterPassword": { - "message": "Master password saved" + "message": "کلمه عبور اصلی ذخیره شد" }, "updateMasterPassword": { - "message": "Update master password" + "message": "به‌روزرسانی کلمه عبور اصلی" }, "updateMasterPasswordWarning": { - "message": "Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "کلمه عبور اصلی شما اخیراً توسط سرپرست سازمان‌تان تغییر کرده است. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, "masterPasswordInvalidWarning": { - "message": "Your master password does not meet the policy requirements of this organization. In order to join the organization, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "کلمه عبور اصلی شما الزامات سیاست این سازمان را برآورده نمی‌کند. برای پیوستن به سازمان، باید کلمه عبور اصلی خود را هم اکنون به‌روز کنید. ادامه، شما را از نشست فعلی خود خارج می‌کند و از شما می‌خواهد دوباره وارد سیستم شوید. نشست‌های فعال در دستگاه‌های دیگر ممکن است تا یک ساعت فعال بمانند." }, "maximumVaultTimeout": { - "message": "Vault timeout" + "message": "متوقف شدن گاو‌صندوق" }, "maximumVaultTimeoutDesc": { - "message": "Set a maximum vault timeout for members." + "message": "حداکثر بازه زمانی گاوصندوق را برای اعضا تنظیم کنید." }, "maximumVaultTimeoutLabel": { - "message": "Maximum vault timeout" + "message": "حداکثر مهلت گاوصندوق" }, "invalidMaximumVaultTimeout": { - "message": "Invalid maximum vault timeout." + "message": "پایان زمان گاوصندوق نامعتبر است." }, "hours": { - "message": "Hours" + "message": "ساعت" }, "minutes": { - "message": "Minutes" + "message": "دقیقه" }, "vaultTimeoutPolicyInEffect": { - "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", + "message": "خط مشی های سازمانتان بر مهلت زمانی گاوصندوق شما تأثیر می‌گذارد. حداکثر زمان مجاز گاوصندوق $HOURS$ ساعت و $MINUTES$ دقیقه است", "placeholders": { "hours": { "content": "$1", @@ -4693,193 +4709,193 @@ } }, "customVaultTimeout": { - "message": "Custom vault timeout" + "message": "پایان زمان گاوصندوق سفارشی" }, "vaultTimeoutToLarge": { - "message": "Your vault timeout exceeds the restriction set by your organization." + "message": "مهلت زمانی شما بیش از محدودیت تعیین شده توسط سازمانتان است." }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "حداقل مهلت زمانی سفارشی ۱ دقیقه است." }, "vaultTimeoutRangeError": { - "message": "Vault timeout is not within allowed range." + "message": "مهلت زمانی گاوصندوق در محدوده مجاز نیست." }, "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "message": "حذف برون ریزی گاوصندوق شخصی" }, "disablePersonalVaultExportDesc": { - "message": "Do not allow members to export their individual vault data." + "message": "به اعضا اجازه ندهید که داده‌های گاوصندوق شخصی خود را برون ریزی کنند." }, "vaultExportDisabled": { - "message": "Vault export removed" + "message": "برون ریزی گاوصندوق غیرفعال شده است" }, "personalVaultExportPolicyInEffect": { - "message": "One or more organization policies prevents you from exporting your individual vault." + "message": "یک یا چند خط مشی سازمان از برون ریزی گاوصندوق شخصی شما جلوگیری می‌کند." }, "selectType": { - "message": "Select SSO type" + "message": "نوع SSO را انتخاب کنید" }, "type": { - "message": "Type" + "message": "نوع" }, "openIdConnectConfig": { - "message": "OpenID connect configuration" + "message": "پیکربندی اتصال OpenID" }, "samlSpConfig": { - "message": "SAML service provider configuration" + "message": "پیکربندی ارائه دهنده خدمات SAML" }, "samlIdpConfig": { - "message": "SAML identity provider configuration" + "message": "پیکربندی ارائه دهنده هویت SAML" }, "callbackPath": { - "message": "Callback path" + "message": "مسیر برگشت به تماس" }, "signedOutCallbackPath": { - "message": "Signed out callback path" + "message": "مسیر برگشت از سیستم خارج شده است" }, "authority": { - "message": "Authority" + "message": "قدرت" }, "clientId": { - "message": "Client ID" + "message": "شناسه کاربر" }, "clientSecret": { - "message": "Client secret" + "message": "راز مشتری" }, "metadataAddress": { - "message": "Metadata address" + "message": "نشانی فراداده" }, "oidcRedirectBehavior": { - "message": "OIDC redirect behavior" + "message": "رفتار تغییر مسیر OIDC" }, "getClaimsFromUserInfoEndpoint": { - "message": "Get claims from user info endpoint" + "message": "ادعاهایی را از نقطه پایانی اطلاعات کاربر دریافت کنید" }, "additionalScopes": { - "message": "Custom scopes" + "message": "محدوده‌های سفارشی" }, "additionalUserIdClaimTypes": { - "message": "Custom user ID claim types" + "message": "انواع ادعای شناسه کاربر سفارشی" }, "additionalEmailClaimTypes": { - "message": "Email claim types" + "message": "انواع ادعای ایمیل" }, "additionalNameClaimTypes": { - "message": "Custom name claim types" + "message": "انواع ادعای نام سفارشی" }, "acrValues": { - "message": "Requested authentication context class reference values" + "message": "مقادیر مرجع کلاس زمینه احراز هویت درخواست شده است" }, "expectedReturnAcrValue": { - "message": "Expected \"acr\" claim value in response" + "message": "مقدار ادعای \"acr\" مورد انتظار در پاسخ" }, "spEntityId": { - "message": "SP entity ID" + "message": "شناسه نهاد SP" }, "spMetadataUrl": { - "message": "SAML 2.0 metadata URL" + "message": "نشانی وب فراداده SAML 2.0" }, "spAcsUrl": { - "message": "Assertion consumer service (ACS) URL" + "message": "نشانی اینترنتی خدمات مصرف کننده ادعایی (ACS)" }, "spNameIdFormat": { - "message": "Name ID format" + "message": "فرمت شناسه نام" }, "spOutboundSigningAlgorithm": { - "message": "Outbound signing algorithm" + "message": "الگوریتم امضای خروجی" }, "spSigningBehavior": { - "message": "Signing behavior" + "message": "رفتار امضایی" }, "spMinIncomingSigningAlgorithm": { - "message": "Minimum incoming signing algorithm" + "message": "الگوریتم امضای حداقل ورودی" }, "spWantAssertionsSigned": { - "message": "Expect signed assertions" + "message": "منتظر اظهارات امضا شده باشید" }, "spValidateCertificates": { - "message": "Validate certificates" + "message": "اعتبارسنجی گواهینامه‌ها" }, "idpEntityId": { - "message": "Entity ID" + "message": "شناسه موجودیت" }, "idpBindingType": { - "message": "Binding type" + "message": "نوع جلد" }, "idpSingleSignOnServiceUrl": { - "message": "Single sign-on service URL" + "message": "نشانی اینترنتی سرویس ورود به سیستم واحد" }, "idpSingleLogoutServiceUrl": { - "message": "Single log-out service URL" + "message": "نشانی اینترنتی سرویس خروج از سیستم واحد" }, "idpX509PublicCert": { - "message": "X509 public certificate" + "message": "گواهی عمومی X509" }, "idpOutboundSigningAlgorithm": { - "message": "Outbound signing algorithm" + "message": "الگوریتم امضای خروجی" }, "idpAllowUnsolicitedAuthnResponse": { - "message": "Allow unsolicited authentication response" + "message": "پاسخ احراز هویت ناخواسته را مجاز کنید" }, "idpAllowOutboundLogoutRequests": { - "message": "Allow outbound logout requests" + "message": "اجازه درخواست خروج از خروجی" }, "idpSignAuthenticationRequests": { - "message": "Sign authentication requests" + "message": "درخواست‌های احراز هویت را امضا کنید" }, "ssoSettingsSaved": { - "message": "Single sign-on configuration saved" + "message": "پیکربندی ورود به سیستم واحد ذخیره شد" }, "sponsoredFamilies": { - "message": "Free Bitwarden Families" + "message": "خانواده‌های Bitwarden رایگان" }, "sponsoredFamiliesEligible": { - "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." + "message": "شما و خانواده‌تان واجد شرایط دریافت خانواده‌های Bitwarden رایگان هستید. با ایمیل شخصی خود بازخرید کنید تا اطلاعات خود را حتی زمانی که در محل کار نیستید ایمن نگه دارید." }, "sponsoredFamiliesEligibleCard": { - "message": "Redeem your Free Bitwarden for Families plan today to keep your data secure even when you are not at work." + "message": "امروز برنامه رایگان Bitwarden برای خانواده‌های خود را بازخرید کنید تا اطلاعات خود را حتی زمانی که در محل کار نیستید ایمن نگه دارید." }, "sponsoredFamiliesInclude": { - "message": "The Bitwarden for Families plan include" + "message": "طرح Bitwarden برای خانواده‌ها شامل" }, "sponsoredFamiliesPremiumAccess": { - "message": "Premium access for up to 6 users" + "message": "دسترسی پرمیوم برای حداکثر 6 کاربر" }, "sponsoredFamiliesSharedCollections": { - "message": "Shared collections for Family secrets" + "message": "مجموعه‌های مشترک برای اسرار خانواده" }, "badToken": { - "message": "The link is no longer valid. Please have the sponsor resend the offer." + "message": "پیوند دیگر معتبر نیست. لطفاً از حمایت کننده بخواهید پیشنهاد را مجدداً ارسال کند." }, "reclaimedFreePlan": { - "message": "Reclaimed free plan" + "message": "طرح رایگان بازپس گیری شد" }, "redeem": { - "message": "Redeem" + "message": "بازخرید" }, "sponsoredFamiliesSelectOffer": { - "message": "Select the organization you would like sponsored" + "message": "سازمانی را که می‌خواهید حمایت مالی شود انتخاب کنید" }, "familiesSponsoringOrgSelect": { - "message": "Which Free Families offer would you like to redeem?" + "message": "کدام پیشنهاد خانواده رایگان را می‌خواهید پس‌خرید کنید؟" }, "sponsoredFamiliesEmail": { - "message": "Enter your personal email to redeem Bitwarden Families" + "message": "برای بازخرید خانواده‌های Bitwarden ایمیل شخصی خود را وارد کنید" }, "sponsoredFamiliesLeaveCopy": { - "message": "If you remove an offer or are removed from the sponsoring organization, your Families sponsorship will expire at the next renewal date." + "message": "اگر پیشنهادی را حذف کنید یا از سازمان حامی حذف شوید، حمایت مالی خانواده‌ها در تاریخ تمدید بعدی منقضی می‌شود." }, "acceptBitwardenFamiliesHelp": { - "message": "Accept offer for an existing organization or create a new Families organization." + "message": "پیشنهاد برای یک سازمان موجود را بپذیرید یا یک سازمان خانواده جدید ایجاد کنید." }, "setupSponsoredFamiliesLoginDesc": { - "message": "You've been offered a free Bitwarden Families plan organization. To continue, you need to log in to the account that received the offer." + "message": "به شما یک سازمان طرح خانواده Bitwarden رایگان پیشنهاد شده است. برای ادامه، باید وارد حسابی شوید که پیشنهاد را دریافت کرده است." }, "sponsoredFamiliesAcceptFailed": { - "message": "Unable to accept offer. Please resend the offer email from your Enterprise account and try again." + "message": "قادر به پذیرش پیشنهاد نیست. لطفاً ایمیل پیشنهاد را از حساب Enterprise خود دوباره ارسال کنید و مجدداً امتحان کنید." }, "sponsoredFamiliesAcceptFailedShort": { - "message": "Unable to accept offer. $DESCRIPTION$", + "message": "قادر به پذیرش پیشنهاد نیست. $DESCRIPTION$", "placeholders": { "description": { "content": "$1", @@ -4888,19 +4904,19 @@ } }, "sponsoredFamiliesOffer": { - "message": "Accept Free Bitwarden Families" + "message": "خانواده‌های رایگان Bitwarden را بپذیرید" }, "sponsoredFamiliesOfferRedeemed": { - "message": "Free Bitwarden Families offer successfully redeemed" + "message": "پیشنهاد رایگان خانواده‌های Bitwarden با موفقیت بازخرید شد" }, "redeemed": { - "message": "Redeemed" + "message": "بازخرید" }, "redeemedAccount": { - "message": "Account redeemed" + "message": "حساب بازخرید شد" }, "revokeAccount": { - "message": "Revoke account $NAME$", + "message": "حساب $NAME$ را لغو کنید", "placeholders": { "name": { "content": "$1", @@ -4909,7 +4925,7 @@ } }, "resendEmailLabel": { - "message": "Resend sponsorship email to $NAME$ sponsorship", + "message": "ایمیل حمایت مالی را مجدداً به حمایت مالی $NAME$ ارسال کنید", "placeholders": { "name": { "content": "$1", @@ -4918,61 +4934,61 @@ } }, "freeFamiliesPlan": { - "message": "Free Families plan" + "message": "طرح خانواده رایگان" }, "redeemNow": { - "message": "Redeem now" + "message": "اکنون بازخرید کنید" }, "recipient": { - "message": "Recipient" + "message": "گیرنده" }, "removeSponsorship": { - "message": "Remove sponsorship" + "message": "حمایت مالی را حذف کنید" }, "removeSponsorshipConfirmation": { - "message": "After removing a sponsorship, you will be responsible for this subscription and related invoices. Are you sure you want to continue?" + "message": "پس از حذف یک حمایت مالی، مسئولیت این اشتراک و فاکتورهای مربوط به آن بر عهده شما خواهد بود. آیا مطمئنید که می‌خواهید ادامه دهید؟" }, "sponsorshipCreated": { - "message": "Sponsorship created" + "message": "حمایت مالی ایجاد شد" }, "emailSent": { - "message": "Email sent" + "message": "ایمیل ارسال شد" }, "revokeSponsorshipConfirmation": { - "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" + "message": "پس از حذف این حساب، حمایت مالی طرح خانواده در پایان دوره صورت‌حساب منقضی می‌شود. تا زمانی که پیشنهاد حمایتی موجود منقضی نشده باشد، نمی‌توانید از پیشنهاد حمایت مالی جدید استفاده کنید. آیا مطمئنید که می‌خواهید ادامه دهید؟" }, "removeSponsorshipSuccess": { - "message": "Sponsorship removed" + "message": "حمایت مالی حذف شد" }, "ssoKeyConnectorError": { - "message": "Key Connector error: make sure Key Connector is available and working correctly." + "message": "خطای رابط کلید: مطمئن شوید که رابط کلید در دسترس است و به درستی کار می‌کند." }, "keyConnectorUrl": { - "message": "Key Connector URL" + "message": "نشانی اینترنتی رابط کلید" }, "sendVerificationCode": { - "message": "Send a verification code to your email" + "message": "یک کد تأیید به ایمیل خود ارسال کنید" }, "sendCode": { - "message": "Send code" + "message": "ارسال کد" }, "codeSent": { - "message": "Code sent" + "message": "کد ارسال شد" }, "verificationCode": { - "message": "Verification code" + "message": "کد تأیید" }, "confirmIdentity": { - "message": "Confirm your identity to continue." + "message": "برای ادامه، هویت خود را تأیید کنید." }, "verificationCodeRequired": { - "message": "Verification code is required." + "message": "کد تأیید مورد نیاز است." }, "invalidVerificationCode": { - "message": "Invalid verification code" + "message": "کد تأیید نامعتبر است" }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "message": "$ORGANIZATION$ در حال استفاده از SSO با یک سرور کلید خود میزبان است. برای ورود اعضای این سازمان دیگر نیازی به کلمه عبور اصلی نیست.", "placeholders": { "organization": { "content": "$1", @@ -4981,169 +4997,169 @@ } }, "leaveOrganization": { - "message": "Leave organization" + "message": "ترک سازمان" }, "removeMasterPassword": { - "message": "Remove master password" + "message": "حذف کلمه عبور اصلی" }, "removedMasterPassword": { - "message": "Master password removed" + "message": "کلمه عبور اصلی حذف شد" }, "allowSso": { - "message": "Allow SSO authentication" + "message": "امکان احراز هویت با SSO" }, "allowSsoDesc": { - "message": "Once set up, your configuration will be saved and members will be able to authenticate using their Identity Provider credentials." + "message": "پس از راه اندازی، پیکربندی شما ذخیره می‌شود و اعضا می‌توانند با استفاده از اعتبار ارائه دهنده هویت خود احراز هویت کنند." }, "ssoPolicyHelpStart": { - "message": "Use the", + "message": "استفاده", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "ssoPolicyHelpLink": { - "message": "require single-sign-on authentication policy", + "message": "نیاز به سیاست احراز هویت با یک علامت وجود دارد", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "ssoPolicyHelpEnd": { - "message": "to require all members to log in with SSO.", + "message": "از همه اعضا بخواهد با SSO وارد شوند.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "ssoPolicyHelpKeyConnector": { - "message": "The require SSO authentication and single organization policies are required to set up Key Connector decryption." + "message": "برای راه‌اندازی رمزگشایی رابط کلید، نیاز به احراز هویت SSO و سیاست‌های سازمانی واحد مورد نیاز است." }, "memberDecryptionOption": { - "message": "Member decryption options" + "message": "گزینه‌های رمزگشایی اعضا" }, "memberDecryptionPassDesc": { - "message": "Once authenticated, members will decrypt vault data using their master passwords." + "message": "پس از احراز هویت، اعضا با استفاده از کلمه عبور اصلی خود، داده‌های صندوق را رمزگشایی می‌کنند." }, "keyConnector": { - "message": "Key Connector" + "message": "رابط کلید" }, "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": "ورود به سیستم با SSO را به سرور کلید رمزگشایی خود میزبانی شده متصل کنید. با استفاده از این گزینه، اعضا نیازی به استفاده از کلمه عبور اصلی خود برای رمزگشایی داده‌های گاوصندوق ندارند. برای راهنمایی راه اندازی با پشتیبانی Bitwarden تماس بگیرید." }, "keyConnectorPolicyRestriction": { - "message": "\"Login with SSO and Key Connector Decryption\" is activated. This policy will only apply to owners and admins." + "message": "\"ورود با SSO و رمزگشایی رابط کلید\" فعال شده است. این سیاست فقط برای مالکان و سرپرستان اعمال می‌شود." }, "enabledSso": { - "message": "SSO turned on" + "message": "SSO روشن شد" }, "disabledSso": { - "message": "SSO turned on" + "message": "SSO روشن شد" }, "enabledKeyConnector": { - "message": "Key Connector activated" + "message": "رابط کلید فعال شد" }, "disabledKeyConnector": { - "message": "Key Connector deactivated" + "message": "رابط کلید غیرفعال شد" }, "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": "هنگامی که اعضا شروع به استفاده از رابط کلید کردند، سازمان شما نمی‌تواند به رمزگشایی کلمه عبور اصلی بازگردد. فقط در صورتی ادامه دهید که با استقرار و مدیریت یک سرور کلید راحت هستید." }, "migratedKeyConnector": { - "message": "Migrated to Key Connector" + "message": "به رابط کلید منتقل شد" }, "paymentSponsored": { - "message": "Please provide a payment method to associate with the organization. Don't worry, we won't charge you anything unless you select additional features or your sponsorship expires. " + "message": "لطفاً یک روش پرداخت برای ارتباط با سازمان ارائه دهید. نگران نباشید، ما هیچ هزینه ای از شما دریافت نمی‌کنیم مگر اینکه ویژگی‌های اضافی را انتخاب کنید یا حمایت مالی شما منقضی شود. " }, "orgCreatedSponsorshipInvalid": { - "message": "The sponsorship offer has expired. You may delete the organization you created to avoid a charge at the end of your 7 day trial. Otherwise you may close this prompt to keep the organization and assume billing responsibility." + "message": "پیشنهاد حمایت مالی منقضی شده است. می‌توانید سازمانی را که ایجاد کرده‌اید حذف کنید تا در پایان دوره آزمایشی ۷ روزه خود از کسر هزینه جلوگیری کنید. در غیر این صورت ممکن است این درخواست را ببندید تا سازمان را حفظ کنید و مسئولیت صورتحساب را بر عهده بگیرید." }, "newFamiliesOrganization": { - "message": "New Families organization" + "message": "سازمان خانواده‌های جدید" }, "acceptOffer": { - "message": "Accept offer" + "message": "قبول کردن پیشنهاد" }, "sponsoringOrg": { - "message": "Sponsoring organization" + "message": "سازمان حامی" }, "keyConnectorTest": { - "message": "Test" + "message": "آزمایش" }, "keyConnectorTestSuccess": { - "message": "Success! Key Connector reached." + "message": "موفقیت! رابط کلید رسید." }, "keyConnectorTestFail": { - "message": "Cannot reach Key Connector. Check URL." + "message": "نمی‌توان به رابط کلید دسترسی پیدا کرد. نشانی اینترنتی را بررسی کنید." }, "sponsorshipTokenHasExpired": { - "message": "The sponsorship offer has expired." + "message": "پیشنهاد حمایت مالی منقضی شده است." }, "freeWithSponsorship": { - "message": "FREE with sponsorship" + "message": "رایگان با حمایت مالی" }, "viewBillingSyncToken": { - "message": "View billing sync token" + "message": "توکن همگام‌سازی صورتحساب را مشاهده کنید" }, "generateBillingSyncToken": { - "message": "Generate billing sync token" + "message": "توکن همگام‌سازی صورتحساب را ایجاد کنید" }, "copyPasteBillingSync": { - "message": "Copy and paste this token into the billing sync settings of your self-hosted organization." + "message": "این توکن را کپی کرده و در تنظیمات همگام‌سازی صورتحساب سازمان خود میزبان جای‌گذاری کنید." }, "billingSyncCanAccess": { - "message": "Your billing sync token can access and edit this organization's subscription settings." + "message": "توکن همگام‌سازی صورتحساب شما می‌تواند به تنظیمات اشتراک این سازمان دسترسی داشته باشد و آن را ویرایش کند." }, "manageBillingSync": { - "message": "Manage billing sync" + "message": "مدیریت همگام‌سازی صورتحساب" }, "setUpBillingSync": { - "message": "Set up billing sync" + "message": "همگام‌سازی صورتحساب را تنظیم کنید" }, "generateToken": { - "message": "Generate token" + "message": "تولید توکن" }, "rotateToken": { - "message": "Rotate token" + "message": "چرخاندن توکن" }, "rotateBillingSyncTokenWarning": { - "message": "If you proceed, you will need to re-setup billing sync on your self-hosted server." + "message": "اگر ادامه دهید، باید همگام‌سازی صورتحساب را در سرور خود میزبان خود مجدد تنظیم کنید." }, "rotateBillingSyncTokenTitle": { - "message": "Rotating the billing sync token will invalidate the previous token." + "message": "چرخاندن توکن همگام‌سازی صورتحساب، توکن قبلی را باطل می‌کند." }, "selfHostingTitle": { - "message": "Self-hosting" + "message": "خود میزبانی" }, "selfHostingEnterpriseOrganizationSectionCopy": { - "message": "To set-up your organization on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up billing sync." + "message": "برای راه اندازی سازمان خود بر روی سروتان، باید پرونده مجوز خود را آپلود کنید. برای پشتیبانی از طرح‌های خانواده‌های رایگان و قابلیت‌های صورتحساب پیشرفته برای سازمان خود میزبان، باید همگام‌سازی صورتحساب را راه‌اندازی کنید." }, "billingSyncApiKeyRotated": { - "message": "Token rotated" + "message": "توکن چرخانده شد" }, "billingSync": { - "message": "Billing sync" + "message": "همگام‌سازی صورتحساب" }, "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": "همگام‌سازی صورتحساب با پیوند دادن Bitwarden خود میزبان شما به سرور ابری Bitwarden، برنامه‌های خانواده رایگان را برای اعضا و قابلیت‌های پیشرفته صورتحساب ارائه می‌کند." }, "billingSyncKeyDesc": { - "message": "A billing sync token from your cloud organization's subscription settings is required to complete this form." + "message": "برای تکمیل این فرم، یک توکن همگام‌سازی صورتحساب از تنظیمات اشتراک سازمان ابری شما لازم است." }, "billingSyncKey": { - "message": "Billing sync token" + "message": "توکن همگام‌سازی صورتحساب" }, "active": { - "message": "Active" + "message": "فعال" }, "inactive": { - "message": "Inactive" + "message": "غیرفعال" }, "sentAwaitingSync": { - "message": "Sent (awaiting sync)" + "message": "ارسال شد (در انتظار همگام‌سازی)" }, "sent": { - "message": "Sent" + "message": "ارسال شد" }, "requestRemoved": { - "message": "Removed (awaiting sync)" + "message": "حذف شد (در انتظار همگام‌سازی)" }, "requested": { - "message": "Requested" + "message": "درخواست شده" }, "formErrorSummaryPlural": { - "message": "$COUNT$ fields above need your attention.", + "message": "فیلدهای $COUNT$ در بالا به توجه شما نیاز دارند.", "placeholders": { "count": { "content": "$1", @@ -5152,10 +5168,10 @@ } }, "formErrorSummarySingle": { - "message": "1 field above needs your attention." + "message": "۱ فیلد بالا به توجه شما نیاز دارد." }, "fieldRequiredError": { - "message": "$FIELDNAME$ is required.", + "message": "$FIELDNAME$ مورد نیاز است.", "placeholders": { "fieldname": { "content": "$1", @@ -5164,31 +5180,31 @@ } }, "required": { - "message": "required" + "message": "الزامی است" }, "idpSingleSignOnServiceUrlRequired": { - "message": "Required if Entity ID is not a URL." + "message": "اگر شناسه موجودیت یک نشانی اینترنتی نباشد، الزامی است." }, "openIdOptionalCustomizations": { - "message": "Optional customizations" + "message": "سفارشی سازی اختیاری" }, "openIdAuthorityRequired": { - "message": "Required if Authority is not valid." + "message": "در صورت معتبر نبودن اعتبار الزامی است." }, "separateMultipleWithComma": { - "message": "Separate multiple with a comma." + "message": "چندتایی را با کاما جدا کنید." }, "sessionTimeout": { - "message": "Your session has timed out. Please go back and try logging in again." + "message": "زمان نشست شما به پایان رسید. لطفاً برگردید و دوباره وارد سیستم شوید." }, "exportingPersonalVaultTitle": { - "message": "Exporting individual vault" + "message": "برون ریزی گاو‌صندوق شخصی" }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "در حال برون ریزی گاوصندوق سازمان" }, "exportingPersonalVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated password history or attachments.", + "message": "فقط موارد شخصی گاوصندوق مرتبط با $EMAIL$ برو ریزی خواهند شد. موارد گاوصندوق سازمان شامل نخواهد شد. فقط اطلاعات مورد گاوصندوق برون ریزی خواهد شد و شامل تاریخچه کلمه عبور مرتبط یا پیوست نمی‌شود.", "placeholders": { "email": { "content": "$1", @@ -5197,7 +5213,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": "فقط گاوصندوق سازمانی مرتبط با $ORGANIZATION$ برون ریزی خواهد شد. اقلام شخصی گاوصندوق و اقلام سایر سازمان‌ها شامل نمی‌شود.", "placeholders": { "organization": { "content": "$1", @@ -5206,82 +5222,82 @@ } }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "دسترسی رد شد. شما اجازه مشاهده این صفحه را ندارید." }, "masterPassword": { - "message": "Master password" + "message": "کلمه عبور اصلی" }, "security": { - "message": "Security" + "message": "امنیت" }, "keys": { - "message": "Keys" + "message": "کلید ها" }, "billingHistory": { - "message": "Billing history" + "message": "سابقه صورتحساب" }, "backToReports": { - "message": "Back to reports" + "message": "بازگشت به گزارش‌ها" }, "organizationPicker": { - "message": "Organization picker" + "message": "انتخاب کننده سازمان" }, "currentOrganization": { - "message": "Current organization", + "message": "سازمان فعلی", "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." }, "accountSettings": { - "message": "Account settings" + "message": "تنظیمات حساب کاربری" }, "generator": { - "message": "Generator" + "message": "تولید کننده" }, "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" + "message": "چه چیزی دوست دارید تولید کنید؟" }, "passwordType": { - "message": "Password type" + "message": "نوع کلمه عبور" }, "regenerateUsername": { - "message": "Regenerate username" + "message": "ایجاد مجدد نام کاربری" }, "generateUsername": { - "message": "Generate username" + "message": "ایجاد نام کاربری" }, "usernameType": { - "message": "Username type" + "message": "نوع نام کاربری" }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "به علاوه نشانی ایمیل داده شده", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Use your email provider's sub-addressing capabilities." + "message": "از قابلیت های آدرس دهی فرعی ارائه دهنده ایمیل خود استفاده کنید." }, "catchallEmail": { - "message": "Catch-all email" + "message": "دریافت همه ایمیل‌ها" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "از صندوق ورودی پیکربندی شده دامنه خود استفاده کنید." }, "random": { - "message": "Random", + "message": "تصادفی", "description": "Generates domain-based username using random letters" }, "randomWord": { - "message": "Random word" + "message": "کلمه تصادفی" }, "service": { - "message": "Service" + "message": "سرویس" }, "unknownCipher": { - "message": "Unknown item, you may need to request permission to access this item." + "message": "مورد ناشناس، ممکن است برای دسترسی به این مورد نیاز به درخواست مجوز داشته باشید." }, "cannotSponsorSelf": { - "message": "You cannot redeem for the active account. Enter a different email." + "message": "شما نمی‌توانید برای حساب فعال استفاده کنید. ایمیل دیگری وارد کنید." }, "revokeWhenExpired": { - "message": "Expires $DATE$", + "message": "$DATE$ منقضی شده", "placeholders": { "date": { "content": "$1", @@ -5290,7 +5306,7 @@ } }, "awaitingSyncSingular": { - "message": "Token rotated $DAYS$ day ago. Update the billing sync token in your self-hosted organization settings.", + "message": "توکن $DAYS$ روز پیش چرخید. توکن همگام‌سازی صورتحساب را در تنظیمات سازمان خود میزبان به‌روزرسانی کنید.", "placeholders": { "days": { "content": "$1", @@ -5299,7 +5315,7 @@ } }, "awaitingSyncPlural": { - "message": "Token rotated $DAYS$ days ago. Update the billing sync token in your self-hosted organization settings.", + "message": "توکن $DAYS$ روز پیش چرخید. توکن همگام‌سازی صورتحساب را در تنظیمات سازمان خود میزبان به‌روزرسانی کنید.", "placeholders": { "days": { "content": "$1", @@ -5308,14 +5324,14 @@ } }, "lastSync": { - "message": "Last sync", + "message": "آخرین همگام‌سازی", "Description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\"" }, "sponsorshipsSynced": { - "message": "Self-hosted sponsorships synced." + "message": "حمایت‌های مالی خود میزبان همگام‌سازی شد." }, "billingManagedByProvider": { - "message": "Managed by $PROVIDER$", + "message": "مدیریت شده توسط $PROVIDER$", "placeholders": { "provider": { "content": "$1", @@ -5324,36 +5340,36 @@ } }, "billingContactProviderForAssistance": { - "message": "Please reach out to them for further assistance", + "message": "لطفاً برای کمک بیشتر با آن‌ها تماس بگیرید", "description": "This text is displayed if an organization's billing is managed by a Provider. It tells the user to contact the Provider for assistance." }, "forwardedEmail": { - "message": "Forwarded email alias" + "message": "نام مستعار ایمیل فوروارد شده" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "یک نام مستعار ایمیل با یک سرویس ارسال خارجی ایجاد کنید." }, "hostname": { - "message": "Hostname", + "message": "نام میزبان", "description": "Part of a URL." }, "apiAccessToken": { - "message": "API access token" + "message": "توکن دسترسی API" }, "deviceVerification": { - "message": "Device verification" + "message": "تأیید دستگاه" }, "enableDeviceVerification": { - "message": "Turn on device verification" + "message": "تأیید دستگاه را روشن کنید" }, "deviceVerificationDesc": { - "message": "Verification codes are sent to your email address when logging in from an unrecognized device" + "message": "هنگام ورود از یک دستگاه ناشناس، کدهای تأیید به نشانی ایمیل شما ارسال می‌شود" }, "updatedDeviceVerification": { - "message": "Updated device verification" + "message": "تأیید دستگاه به‌روز شد" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { - "message": "Are you sure you want to turn on device verification? The verification code emails will arrive at: $EMAIL$", + "message": "آیا مطمئنید که می‌خواهید تأیید دستگاه را روشن کنید؟ ایمیل‌های کد تأیید به این آدرس می‌رسند: $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -5362,70 +5378,70 @@ } }, "premiumSubcriptionRequired": { - "message": "Premium subscription required" + "message": "اشتراک پرمیوم نیاز است" }, "scim": { - "message": "SCIM provisioning", + "message": "تأمین SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." }, "scimDescription": { - "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", + "message": "به‌طور خودکار کاربران و گروه‌ها را با ارائه‌دهنده هویت ترجیحی خود از طریق تأمین SCIM فراهم کنید", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDesc": { - "message": "Enable SCIM", + "message": "SCIM را فعال کنید", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimEnabledCheckboxDescHelpText": { - "message": "Set up your preferred identity provider by configuring the URL and SCIM API Key", + "message": "با پیکربندی نشانی اینترنتی و کلید SCIM API، ارائه دهنده هویت ترجیحی خود را تنظیم کنید", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "scimApiKeyHelperText": { - "message": "This API key has access to manage users within your organization. It should be kept secret." + "message": "این کلید API به مدیریت کاربران در سازمان شما دسترسی دارد. که باید مخفی بماند." }, "copyScimKey": { - "message": "Copy the SCIM API key to your clipboard", + "message": "کلید SCIM API را در کلیپ بورد خود کپی کنید", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateScimKey": { - "message": "Rotate the SCIM API key", + "message": "کلید SCIM API را بچرخانید", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateScimKeyWarning": { - "message": "Are you sure you want to rotate the SCIM API Key? The current key will no longer work for any existing integrations.", + "message": "آیا مطمئنید که می‌خواهید کلید SCIM API را بچرخانید؟ کلید فعلی دیگر برای ادغام‌های موجود کار نخواهد کرد.", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateKey": { - "message": "Rotate key" + "message": "چرخاندن توکن" }, "scimApiKey": { - "message": "SCIM API key", + "message": "کلید SCIM API", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "copyScimUrl": { - "message": "Copy the SCIM endpoint URL to your clipboard", + "message": "نشانی اینترنتی نقطه پایانی SCIM را در کلیپ بورد خود کپی کنید", "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." }, "scimUrl": { - "message": "SCIM URL", + "message": "نشانی اینترنتی SCIM", "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." }, "scimApiKeyRotated": { - "message": "SCIM API key successfully rotated", + "message": "کلید SCIM API با موفقیت چرخید", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "scimSettingsSaved": { - "message": "SCIM settings saved", + "message": "تنظیمات SCIM ذخیره شد", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "inputRequired": { - "message": "Input is required." + "message": "ورودی مورد نیاز است." }, "inputEmail": { - "message": "Input is not an email address." + "message": "ورودی یک نشانی ایمیل نیست." }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "ورودی باید حداقل $COUNT$ کاراکتر داشته باشد.", "placeholders": { "count": { "content": "$1", @@ -5434,7 +5450,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "طول ورودی نباید بیش از $COUNT$ کاراکتر باشد.", "placeholders": { "count": { "content": "$1", @@ -5443,7 +5459,7 @@ } }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "فیلد $COUNT$ در بالا به توجه شما نیاز دارد.", "placeholders": { "count": { "content": "$1", @@ -5452,246 +5468,246 @@ } }, "turnOn": { - "message": "Turn on" + "message": "روشن" }, "on": { - "message": "On" + "message": "روشن" }, "members": { - "message": "Members" + "message": "اعضا" }, "reporting": { - "message": "Reporting" + "message": "گزارش نویسی" }, "cardBrandMir": { - "message": "Mir" + "message": "میر" }, "numberOfUsers": { - "message": "Number of users" + "message": "تعداد کاربران" }, "loggingInAs": { - "message": "Logging in as" + "message": "در حال ورود به عنوان" }, "notYou": { - "message": "Not you?" + "message": "شما نیستید؟" }, "pickAnAvatarColor": { - "message": "Pick an avatar color" + "message": "یک رنگ آواتار را انتخاب کنید" }, "customizeAvatar": { - "message": "Customize avatar" + "message": "شخصی‌سازی آواتار" }, "avatarUpdated": { - "message": "Avatar updated" + "message": "آواتار به‌روز شد" }, "brightBlue": { - "message": "Bright Blue" + "message": "آبی درخشان" }, "green": { - "message": "Green" + "message": "سبز" }, "orange": { - "message": "Orange" + "message": "نارنجی" }, "lavender": { - "message": "Lavender" + "message": "بنفش کمرنگ" }, "yellow": { - "message": "Yellow" + "message": "زرد" }, "indigo": { - "message": "Indigo" + "message": "نیلی" }, "teal": { - "message": "Teal" + "message": "فیروزه‌ای" }, "salmon": { - "message": "Salmon" + "message": "ماهی سالمون" }, "pink": { - "message": "Pink" + "message": "صورتی" }, "customColor": { - "message": "Custom Color" + "message": "رنگ سفارشی" }, "multiSelectPlaceholder": { - "message": "-- Type to Filter --" + "message": "-- برای فیلتر تایپ کنید --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "در حال بازیابی گزینه‌ها..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "موردی یافت نشد" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "پاک کردن همه" }, "toggleCharacterCount": { - "message": "Toggle character count", + "message": "تغییر تعداد کاراکترها", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "passwordCharacterCount": { - "message": "Password character count", + "message": "تعداد کاراکترهای کلمه عبور", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "hide": { - "message": "Hide" + "message": "پنهان‌سازی" }, "projects": { - "message": "Projects" + "message": "پروژه ها" }, "lastEdited": { - "message": "Last Edited" + "message": "آخرین ویرایش" }, "editSecret": { - "message": "Edit Secret" + "message": "راز را ویرایش کنید" }, "addSecret": { - "message": "Add Secret" + "message": "افزودن راز" }, "copySecretName": { - "message": "Copy Secret Name" + "message": "کپی نام مخفی" }, "copySecretValue": { - "message": "Copy Secret Value" + "message": "کپی مقدار مخفی" }, "deleteSecret": { - "message": "Delete Secret" + "message": "حذف راز" }, "deleteSecrets": { - "message": "Delete Secrets" + "message": "حذف رازها" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "پروژه هایی را انتخاب کنید که راز با آن‌ها مرتبط باشد. فقط کاربران سازمانی که به این پروژه ها دسترسی دارند می‌توانند این راز را ببینند." }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "پروژه ها را تایپ یا انتخاب کنید" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "پروژه را تایپ یا انتخاب کنید" }, "project": { - "message": "Project" + "message": "پروژه" }, "editProject": { - "message": "Edit Project" + "message": "ویرایش پروژه" }, "viewProject": { - "message": "View Project" + "message": "مشاهده پروژه" }, "deleteProject": { - "message": "Delete Project" + "message": "حذف پروژه" }, "deleteProjects": { - "message": "Delete Projects" + "message": "حذف پروژه ها" }, "secret": { - "message": "Secret" + "message": "راز" }, "serviceAccount": { - "message": "Service Account" + "message": "حساب سرویس" }, "serviceAccounts": { - "message": "Service Accounts" + "message": "حساب‌های خدمات" }, "new": { - "message": "New" + "message": "جدید" }, "secrets": { - "message": "Secrets" + "message": "رازها" }, "nameValuePair": { - "message": "Name/Value Pair" + "message": "جفت نام/مقدار" }, "secretEdited": { - "message": "Secret edited" + "message": "راز ویرایش شد" }, "secretCreated": { - "message": "Secret created" + "message": "راز ایجاد شد" }, "newSecret": { - "message": "New Secret" + "message": "راز جدید" }, "newServiceAccount": { - "message": "New Service Account" + "message": "حساب سرویس جدید" }, "secretsNoItemsTitle": { - "message": "No secrets to show" + "message": "رازی برای نمایش نیست" }, "secretsNoItemsMessage": { - "message": "To get started, add a new secret or import secrets." + "message": "برای شروع، یک راز جدید اضافه کنید یا رازها را درون ریزی کنید." }, "serviceAccountsNoItemsTitle": { - "message": "Nothing to show yet" + "message": "هنوز چیزی برای نشان دادن موجود نیست" }, "serviceAccountsNoItemsMessage": { - "message": "Create a new Service Account to get started automating secret access." + "message": "برای شروع خودکارسازی دسترسی مخفی، یک حساب سرویس جدید ایجاد کنید." }, "searchSecrets": { - "message": "Search Secrets" + "message": "جستجوی رازها" }, "deleteServiceAccounts": { - "message": "Delete Service Accounts" + "message": "حساب‌های سرویس را حذف کنید" }, "deleteServiceAccount": { - "message": "Delete Service Account" + "message": "حساب سرویس را حذف کنید" }, "viewServiceAccount": { - "message": "View Service Account" + "message": "حساب سرویس را ببینید" }, "searchServiceAccounts": { - "message": "Search Service Accounts" + "message": "جستجوی حساب‌های سرویس" }, "addProject": { - "message": "Add Project" + "message": "افزودن پروژه" }, "projectEdited": { - "message": "Project edited" + "message": "پروژه ویرایش شد" }, "projectSaved": { - "message": "Project saved" + "message": "پروژه ذخیره شد" }, "projectCreated": { - "message": "Project created" + "message": "پروژه ساخته شد" }, "projectName": { - "message": "Project Name" + "message": "نام پروژه" }, "newProject": { - "message": "New Project" + "message": "پروژه جدید" }, "softDeleteSecretWarning": { - "message": "Deleting secrets can affect existing integrations." + "message": "حذف رازها می‌تواند ادغام های موجود را تحت تاثیر قرار دهد." }, "softDeletesSuccessToast": { - "message": "Secrets sent to trash" + "message": "رازهای به زباله‌ها فرستاده شد" }, "serviceAccountCreated": { - "message": "Service Account Created" + "message": "سرویس حساب ساخته شد" }, "smAccess": { - "message": "Access" + "message": "دسترسی" }, "projectCommaSecret": { - "message": "Project, Secret" + "message": "پروژه، راز" }, "serviceAccountName": { - "message": "Service account name" + "message": "نام حساب سرویس" }, "newSaSelectAccess": { - "message": "Type or Select Projects or Secrets" + "message": "پروژه‌ها یا اسرار را تایپ یا انتخاب کنید" }, "newSaTypeToFilter": { - "message": "Type to Filter" + "message": "برای فیلتر تایپ کنید" }, "deleteProjectsToast": { - "message": "Projects deleted" + "message": "پروژه حذف شد" }, "deleteProjectToast": { - "message": "The project and all associated secrets have been deleted" + "message": "پروژه و تمام رازهای مرتبط حذف شد" }, "deleteProjectDialogMessage": { - "message": "Deleting project $PROJECT$ is permanent and irreversible.", + "message": "حذف پروژه $PROJECT$ دائمی و غیر قابل برگشت است.", "placeholders": { "project": { "content": "$1", @@ -5700,7 +5716,7 @@ } }, "deleteProjectInputLabel": { - "message": "Type \"$CONFIRM$\" to continue", + "message": "برای ادامه، «$CONFIRM$» را تایپ کنید", "placeholders": { "confirm": { "content": "$1", @@ -5709,7 +5725,7 @@ } }, "deleteProjectConfirmMessage": { - "message": "Delete $PROJECT$", + "message": "حذف $PROJECT$", "placeholders": { "project": { "content": "$1", @@ -5718,7 +5734,7 @@ } }, "deleteProjectsConfirmMessage": { - "message": "Delete $COUNT$ Projects", + "message": "$COUNT$ پروژه را حذف کنید", "placeholders": { "count": { "content": "$1", @@ -5727,118 +5743,118 @@ } }, "deleteProjectsDialogMessage": { - "message": "Deleting projects is permanent and irreversible." + "message": "حذف پروژه‌ها دائمی و غیر قابل برگشت است." }, "projectsNoItemsTitle": { - "message": "No projects to display" + "message": "پروژه‌ای برای نمایش نیست" }, "projectsNoItemsMessage": { - "message": "Add a new project to get started organizing secrets." + "message": "برای شروع سازماندهی رازها، پروژه جدیدی اضافه کنید." }, "smConfirmationRequired": { - "message": "Confirmation required" + "message": "به تأیید نیاز دارد" }, "bulkDeleteProjectsErrorMessage": { - "message": "The following projects could not be deleted:" + "message": "پروژه‌های زیر قابل حذف نیستند:" }, "softDeleteSuccessToast": { - "message": "Secret sent to trash" + "message": "راز به سطل زباله فرستاده شد" }, "searchProjects": { - "message": "Search Projects" + "message": "جستجوی پروژه‌ها" }, "accessTokens": { - "message": "Access tokens" + "message": "دسترسی به توکن‌ها" }, "createAccessToken": { - "message": "Create access token" + "message": "توکن دسترسی ایجاد کنید" }, "expires": { - "message": "Expires" + "message": "منقضی می‌شود" }, "canRead": { - "message": "Can Read" + "message": "می‌تواند بخواند" }, "accessTokensNoItemsTitle": { - "message": "No access tokens to show" + "message": "هیچ توکن دسترسی برای نمایش وجود ندارد" }, "accessTokensNoItemsDesc": { - "message": "To get started, create an access token" + "message": "برای شروع، یک توکن دسترسی ایجاد کنید" }, "downloadAccessToken": { - "message": "Download or copy before closing." + "message": "قبل از بستن بارگیری یا کپی کنید." }, "expiresOnAccessToken": { - "message": "Expires on:" + "message": "تاریخ انقضا:" }, "accessTokenCallOutTitle": { - "message": "Access tokens are not stored and cannot be retrieved" + "message": "توکن های دسترسی ذخیره نمی‌شوند و قابل بازیابی نیستند" }, "copyToken": { - "message": "Copy token" + "message": "کپی توکن" }, "accessToken": { - "message": "Access token" + "message": "دسترسی به توکن" }, "accessTokenExpirationRequired": { - "message": "Expiration date required" + "message": "تاریخ انقضا مورد نیاز است" }, "accessTokenCreatedAndCopied": { - "message": "Access token created and copied to clipboard" + "message": "توکن دسترسی ایجاد و در کلیپ بورد کپی شد" }, "accessTokenPermissionsBetaNotification": { - "message": "Permissions management is unavailable for beta." + "message": "مدیریت مجوزها برای نسخه بتا در دسترس نیست." }, "revokeAccessToken": { - "message": "Revoke Access Token" + "message": "لغو دسترسی به توکن" }, "submenu": { - "message": "Submenu" + "message": "زیرمنو" }, "from": { - "message": "From" + "message": "از" }, "to": { - "message": "To" + "message": "به" }, "member": { - "message": "Member" + "message": "عضو" }, "update": { - "message": "Update" + "message": "به‌روزرسانی" }, "role": { - "message": "Role" + "message": "نقش" }, "canView": { - "message": "Can view" + "message": "می‌تواند مشاهده کند" }, "canViewExceptPass": { - "message": "Can view, except passwords" + "message": "قابل مشاهده، به غیر از کلمه‌های عبور" }, "canEdit": { - "message": "Can edit" + "message": "می‌تواند ویرایش کند" }, "canEditExceptPass": { - "message": "Can edit, except passwords" + "message": "قابل ویرایش، به غیر از کلمه‌های عبور" }, "group": { - "message": "Group" + "message": "گروه" }, "groupAccessAll": { - "message": "This group can access and modify all items." + "message": "این گروه می‌تواند به همه موارد دسترسی داشته باشد و آن‌ها را تغییر دهد." }, "memberAccessAll": { - "message": "This member can access and modify all items." + "message": "این عضو می‌تواند به همه موارد دسترسی داشته باشد و آن‌ها را تغییر دهد." }, "moreFromBitwarden": { - "message": "بیشتر بدانید از Bitwarden" + "message": "از Bitwarden بیشتر بدانید" }, "switchProducts": { - "message": "تغییر محصول" + "message": "محصولات را تغییر دهید" }, "freeOrgInvLimitReachedManageBilling": { - "message": "Free organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.", + "message": "سازمان‌های رایگان ممکن است تا $SEATCOUNT$ عضو داشته باشند. برای دعوت از اعضای بیشتر، به یک طرح پولی ارتقا دهید.", "placeholders": { "seatcount": { "content": "$1", @@ -5847,7 +5863,7 @@ } }, "freeOrgInvLimitReachedNoManageBilling": { - "message": "Free organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.", + "message": "سازمان‌های رایگان ممکن است تا $SEATCOUNT$ عضو داشته باشند. برای ارتقا با مالک سازمان خود تماس بگیرید.", "placeholders": { "seatcount": { "content": "$1", @@ -5856,7 +5872,7 @@ } }, "freeOrgMaxCollectionReachedManageBilling": { - "message": "Free organizations may have up to $COLLECTIONCOUNT$ collections. Upgrade to a paid plan to add more collections.", + "message": "سازمان‌های رایگان ممکن است تا $COLLECTIONCOUNT$ مجموعه داشته باشند. برای افزودن مجموعه‌های بیشتر، به یک طرح پولی ارتقا دهید.", "placeholders": { "COLLECTIONCOUNT": { "content": "$1", @@ -5865,7 +5881,7 @@ } }, "freeOrgMaxCollectionReachedNoManageBilling": { - "message": "Free organizations may have up to $COLLECTIONCOUNT$ collections. Contact your organization owner to upgrade.", + "message": "سازمان‌های رایگان ممکن است تا $COLLECTIONCOUNT$ مجموعه داشته باشند. برای ارتقا با مالک سازمان خود تماس بگیرید.", "placeholders": { "COLLECTIONCOUNT": { "content": "$1", diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 355eb4eb24e..e7384aad8df 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -930,7 +930,7 @@ "message": "Vahvista tiedoston salasana" }, "accountRestrictedOptionDescription": { - "message": "Salaa vienti ja rajoita tuonti vain nykyiselle Bitwarden-tilille tilisi salausavaimella, joka pohjautuu tilisi käyttäjätunnukseen ja pääsalasanaan." + "message": "Salaa vienti ja rajoita tuonti vain nykyiselle Bitwarden-tilille tilisi käyttäjätunnukseen ja pääsalasanaan pohjautuvalla salausavaimella." }, "passwordProtectedOptionDescription": { "message": "Salaa vientitiedosto salasanalla, joka mahdollistaa sen tuonnin mille tahansa Bitwarden-tilille." @@ -939,7 +939,7 @@ "message": "Viennin tyyppi" }, "accountRestricted": { - "message": "Tiliä rajoitettu" + "message": "Tiliä on rajoitettu" }, "passwordProtected": { "message": "Salasanasuojattu" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Hanki Premium vain $PRICE$ vuosihintaan tai vaihtoehtoisesti premium-tilit $FAMILYPLANUSERCOUNT$ käyttäjälle ja rajoittamattomalla perhejaolla ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Perheille -tilauksella." + }, "addons": { "message": "Laajennukset" }, @@ -5562,13 +5578,13 @@ "message": "Poista salaisuudet" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Valitse projektit, joihin salaisuus liitetään. Salaisuuden voivat nähdä vain ne organisaation käyttäjät, joilla on näiden projektien käyttöoikeus." }, "typeOrSelectProjects": { - "message": "Kirjoita tai valitse projektit" + "message": "Syötä tai valitse projektit" }, "typeOrSelectProject": { - "message": "Kirjoita tai valitse projekti" + "message": "Syötä tai valitse projekti" }, "project": { "message": "Projekti" @@ -5619,7 +5635,7 @@ "message": "Näytettäviä salaisuuksia ei ole" }, "secretsNoItemsMessage": { - "message": "Aloita lisäämällä uusi salaisuus tai tuomalla salaisuudet." + "message": "Aloita lisäämällä uusi salaisuus tai tuomalla niitä." }, "serviceAccountsNoItemsTitle": { "message": "Mitään näytettävää ei vielä ole" @@ -5679,13 +5695,13 @@ "message": "Palvelutilin nimi" }, "newSaSelectAccess": { - "message": "Kirjoita tai valitse projektit tai salaisuudet" + "message": "Syötä tai valitse projektit tai salaisuudet" }, "newSaTypeToFilter": { "message": "Suodata kirjoittamalla" }, "deleteProjectsToast": { - "message": "Projektit poistettiin" + "message": "Projekteja poistettiin" }, "deleteProjectToast": { "message": "Projekti ja kaikki siihen liittyvät salaisuudet poistettiin" @@ -5700,7 +5716,7 @@ } }, "deleteProjectInputLabel": { - "message": "Jatka kirjoittamalla \"$CONFIRM$\"", + "message": "Jatka syöttämällä \"$CONFIRM$\"", "placeholders": { "confirm": { "content": "$1", @@ -5748,7 +5764,7 @@ "message": "Hae projekteista" }, "accessTokens": { - "message": "Hae käyttötunnisteista" + "message": "Käyttötunnisteet" }, "createAccessToken": { "message": "Luo käyttötunniste" diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index bea9b0d93ec..27e1df79811 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index d7f9e8db196..80bde415e59 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -428,13 +428,13 @@ "message": "Mon coffre" }, "allVaults": { - "message": "Tous les Coffres-forts" + "message": "Tous les coffres" }, "vault": { "message": "Coffre" }, "vaults": { - "message": "Coffres-forts" + "message": "Coffres" }, "vaultItems": { "message": "Objets du Coffre" @@ -555,7 +555,7 @@ "message": "Êtes-vous sûr de vouloir vous déconnecter ?" }, "logOut": { - "message": "Déconnexion" + "message": "Se déconnecter" }, "ok": { "message": "Ok" @@ -576,7 +576,7 @@ "message": "La connexion avec l'appareil doit être activée dans les paramètres de l'application mobile Bitwarden. Avez-vous besoin d'une autre option?" }, "loginWithMasterPassword": { - "message": "Se connecter avec le mot de passe maître" + "message": "Se connecter avec le mot de passe principal" }, "createAccount": { "message": "Créer un compte" @@ -606,25 +606,25 @@ "message": "Comment doit-on vous appeler ?" }, "masterPass": { - "message": "Mot de passe maître" + "message": "Mot de passe principal" }, "masterPassDesc": { - "message": "Le mot de passe maître est le mot de passe que vous utilisez pour accéder à votre coffre. Il est très important de ne pas l'oublier. Il n'existe aucun moyen de le récupérer si vous le perdez." + "message": "Le mot de passe principal est le mot de passe que vous utilisez pour accéder à votre coffre. Il est très important de ne pas oublier votre mot de passe principal. Il n'existe aucun moyen de récupérer le mot de passe si vous l'oubliez." }, "masterPassImportant": { - "message": "Le mot de passe maître ne peut pas être récupérés si vous l'oubliez!" + "message": "Le mot de passe principal ne peut pas être récupéré si vous l'oubliez !" }, "masterPassHintDesc": { - "message": "Un indice de mot de passe maître peut vous aider à vous rappeler de votre mot de passe en cas d'oubli." + "message": "Un indice de mot de passe principal peut vous aider à vous souvenir de votre mot de passe si vous l'oubliez." }, "reTypeMasterPass": { - "message": "Saisir à nouveau le mot de passe maître" + "message": "Ressaisir le mot de passe principal" }, "masterPassHint": { - "message": "Indice du mot de passe maître (facultatif)" + "message": "Indice du mot de passe principal (facultatif)" }, "masterPassHintLabel": { - "message": "Indice du mot de passe maître" + "message": "Indice du mot de passe principal" }, "settings": { "message": "Paramètres" @@ -633,10 +633,10 @@ "message": "Indice du mot de passe" }, "enterEmailToGetHint": { - "message": "Saisissez l'adresse e-mail de votre compte pour recevoir l'indice de votre mot de passe maître." + "message": "Saisissez l'adresse électronique de votre compte pour recevoir l'indice de votre mot de passe principal." }, "getMasterPasswordHint": { - "message": "Obtenir l'indice du mot de passe maître" + "message": "Obtenir l'indice du mot de passe principal" }, "emailRequired": { "message": "L'adresse e-mail est requise." @@ -645,16 +645,16 @@ "message": "Adresse e-mail invalide." }, "masterPasswordRequired": { - "message": "Le mot de passe maître est requis." + "message": "Le mot de passe principal est requis." }, "confirmMasterPasswordRequired": { - "message": "Le mot de passe maître doit être entré de nouveau." + "message": "Une nouvelle saisie du mot de passe principal est nécessaire." }, "masterPasswordMinlength": { - "message": "Le mot de passe maître doit comporter au moins 8 caractères." + "message": "Le mot de passe principal doit comporter au moins 8 caractères." }, "masterPassDoesntMatch": { - "message": "La confirmation du mot de passe maître ne correspond pas." + "message": "La confirmation du mot de passe principal ne correspond pas." }, "newAccountCreated": { "message": "Votre nouveau compte a été créé ! Vous pouvez maintenant vous authentifier." @@ -663,7 +663,7 @@ "message": "Compte créé avec succès." }, "masterPassSent": { - "message": "Nous vous avons envoyé un e-mail contenant votre indice de mot de passe maître." + "message": "Nous vous avons envoyé un courriel avec votre indice de mot de passe principal." }, "unexpectedError": { "message": "Une erreur inattendue est survenue." @@ -672,7 +672,7 @@ "message": "Adresse e-mail" }, "yourVaultIsLocked": { - "message": "Votre coffre est verrouillé. Saisissez votre mot de passe maître pour continuer." + "message": "Votre coffre est verrouillé. Vérifiez votre mot de passe principal pour continuer." }, "unlock": { "message": "Déverrouiller" @@ -691,7 +691,7 @@ } }, "invalidMasterPassword": { - "message": "Mot de passe maître invalide" + "message": "Mot de passe principal invalide" }, "invalidFilePassword": { "message": "Mot de passe de fichier invalide, veuillez utiliser le mot de passe que vous avez entré lorsque vous avez créé le fichier d'exportation." @@ -760,7 +760,7 @@ "message": "Envoyer à nouveau l'e-mail du code de vérification" }, "useAnotherTwoStepMethod": { - "message": "Utiliser une autre méthode d'identification en deux étapes" + "message": "Utiliser une autre méthode d'authentification à deux facteurs" }, "insertYubiKey": { "message": "Insérez votre YubiKey dans le port USB de votre ordinateur puis appuyez sur son bouton." @@ -772,16 +772,16 @@ "message": "Identifiant non disponible" }, "noTwoStepProviders": { - "message": "Ce compte dispose d'une authentification en deux étapes, cependant aucun de vos services d'authentification en deux étapes n'est supporté par ce navigateur web." + "message": "Ce compte dispose d'une authentification à deux facteurs mise en place, cependant, aucun des fournisseurs à deux facteurs configurés n'est pris en charge par ce navigateur web." }, "noTwoStepProviders2": { "message": "Merci d'utiliser un navigateur web compatible (comme Chrome) et/ou d'ajouter des services additionnels d'identification en deux étapes qui sont mieux supportés par les navigateurs web (comme par exemple une application d'authentification)." }, "twoStepOptions": { - "message": "Options d'identification en deux étapes" + "message": "Options d'authentification à deux facteurs" }, "recoveryCodeDesc": { - "message": "Vous avez perdu l'accès à tous vos services d'authentification à deux facteurs ? Utilisez votre code de récupération pour désactiver tous les services d'authentification à deux facteurs sur votre compte." + "message": "Vous avez perdu l'accès à tous vos fournisseurs d'authentification à deux facteurs ? Utilisez votre code de récupération pour désactiver tous les fournisseurs d'authentification à deux facteurs de votre compte." }, "recoveryCodeTitle": { "message": "Code de récupération" @@ -794,7 +794,7 @@ "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." }, "yubiKeyTitle": { - "message": "Clé de sécurité YubiKey OTP" + "message": "Clé de sécurité OTP YubiKey" }, "yubiKeyDesc": { "message": "Utilisez une YubiKey pour accéder à votre compte. Fonctionne avec les YubiKey série 4, série 5 et NEO." @@ -918,7 +918,7 @@ "message": "Ce mot de passe sera utilisé pour exporter et importer ce fichier" }, "confirmMasterPassword": { - "message": "Confirmer le Mot de Passe Maître" + "message": "Confirmer le mot de passe principal" }, "confirmFormat": { "message": "Confirmer le Format" @@ -930,7 +930,7 @@ "message": "Confirmer le Mot de Passe du Fichier" }, "accountRestrictedOptionDescription": { - "message": "Chiffrez le fichier avec la clé de chiffrement de votre compte, dérivée de vos nom d'utilisateur et mot de passe maître. L'importation ne sera possible que sur ce compte Bitwarden." + "message": "Utilisez la clé de chiffrement de votre compte, dérivée du nom d'utilisateur et du mot de passe principal de votre compte, pour chiffrer l'export et restreindre l'import au seul compte Bitwarden actuel." }, "passwordProtectedOptionDescription": { "message": "Créez un mot de passe généré par l'utilisateur pour protéger l'exportation. Utilisez ceci pour créer une exportation qui peut être utilisée dans d'autres comptes." @@ -963,10 +963,10 @@ "message": "Score de complexité minimum" }, "minNumbers": { - "message": "Nombre minimum de chiffres" + "message": "Minimum de chiffres" }, "minSpecial": { - "message": "Nombre minimum de caractères spéciaux", + "message": "Minimum de caractères spéciaux", "description": "Minimum special characters" }, "ambiguous": { @@ -1022,7 +1022,7 @@ "message": "Changer l'e-mail" }, "changeEmailTwoFactorWarning": { - "message": "En continuant, vous changerez l'adresse e-mail de votre compte. Cela ne changera pas l'adresse e-mail utilisée pour l'authentification à deux facteurs. Vous pouvez modifier cette adresse e-mail dans les paramètres d'identification en deux étapes." + "message": "En poursuivant, l'adresse électronique de votre compte sera changée. L'adresse électronique utilisée pour l'authentification à deux facteurs ne sera pas changée. Vous pouvez changer cette adresse électronique dans les paramètres d'authentification à deux facteurs." }, "newEmail": { "message": "Nouvel e-mail" @@ -1040,7 +1040,7 @@ } }, "loggedOutWarning": { - "message": "En continuant, vous serez déconnecté de votre session actuelle et vous devrez vous reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives pendant encore une heure." + "message": "En poursuivant, vous serez déconnecté de votre session en cours, ce qui vous obligera à vous reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives pendant encore une heure." }, "emailChanged": { "message": "Adresse e-mail modifiée" @@ -1052,19 +1052,19 @@ "message": "Veuillez vous reconnecter. Si vous utilisez d'autres applications Bitwarden, déconnectez-vous puis reconnectez-vous dans ces applications également." }, "changeMasterPassword": { - "message": "Modifier le mot de passe maître" + "message": "Changer le mot de passe principal" }, "masterPasswordChanged": { - "message": "Mot de passe maître modifié" + "message": "Mot de passe principal enregistré" }, "currentMasterPass": { - "message": "Mot de passe maître actuel" + "message": "Mot de passe principal actuel" }, "newMasterPass": { - "message": "Nouveau mot de passe maître" + "message": "Nouveau mot de passe principal" }, "confirmNewMasterPass": { - "message": "Confirmer le nouveau mot de passe maître" + "message": "Confirmer le nouveau mot de passe principal" }, "encKeySettings": { "message": "Paramètres de la clé de chiffrement" @@ -1076,7 +1076,7 @@ "message": "Nombre d'itérations de KDF" }, "kdfIterationsDesc": { - "message": "Un nombre plus élevé d'itérations de KDF peut aider à protéger votre mot de passe maître contre une attaque par force brute. Nous recommandons une valeur de $VALUE$ ou plus.", + "message": "Des itérations KDF plus élevées peuvent aider à protéger votre mot de passe principal contre une attaque par force brute. Nous recommandons une valeur de $VALUE$ ou plus.", "placeholders": { "value": { "content": "$1", @@ -1109,10 +1109,10 @@ "message": "Révoquer les sessions" }, "deauthorizeSessionsDesc": { - "message": "Vous craignez que votre compte soit connecté sur un autre appareil ? Poursuivez ci-dessous pour révoquer tous les ordinateurs ou appareils que vous avez déjà utilisés. Cette étape de sécurité est recommandée si vous avez précédemment utilisé un ordinateur public ou enregistré accidentellement votre mot de passe sur un appareil qui n'est pas le vôtre. Cette étape effacera également toutes les sessions de connexion en deux étapes précédemment mémorisées." + "message": "Vous craignez que votre compte soit connecté sur un autre appareil ? Poursuivez ci-dessous pour annuler l'autorisation de tous les ordinateurs ou appareils que vous avez précédemment utilisés. Cette étape de sécurité est recommandée si vous avez utilisé un ordinateur public ou si vous avez accidentellement enregistré votre mot de passe sur un appareil qui n'est pas le vôtre. Cette étape effacera également toutes les sessions d'authentification à deux facteurs précédemment mémorisées." }, "deauthorizeSessionsWarning": { - "message": "Si vous continuez, vous serez également déconnecté de votre session en cours, ce qui vous nécessitera de vous reconnecter. Votre second facteur d'authentification vous sera également demandé, si cette option est activée. Les sessions actives sur d'autres appareils peuvent rester actives jusqu'à une heure." + "message": "En poursuivant, vous serez également déconnecté de votre session en cours, ce qui vous obligera à vous reconnecter. Vous serez également invité à vous reconnecter via l'authentification à deux facteurs, si elle est configurée. Les sessions actives sur d'autres appareils peuvent rester actives pendant encore une heure." }, "sessionsDeauthorized": { "message": "Toutes les sessions ont été révoquées" @@ -1288,29 +1288,29 @@ "message": "Nom de domaine mis à jour" }, "twoStepLogin": { - "message": "Identification en deux étapes" + "message": "Authentification à deux facteurs" }, "twoStepLoginEnforcement": { - "message": "Imposition de la Connexion en Deux Étapes" + "message": "Mise en application de l'authentification à deux facteurs" }, "twoStepLoginDesc": { "message": "Sécuriser votre compte en exigeant une étape supplémentaire lors de la connexion." }, "twoStepLoginOrganizationDescStart": { - "message": "Imposer les options de la Connexion en 2 Étapes aux membres en utilisant le ", + "message": "Imposer les options d'authentification à deux facteurs de Bitwarden pour les membres en utilisant le ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { - "message": "Politique de Connexion en 2 Étapes" + "message": "Politique d'authentification à deux facteurs" }, "twoStepLoginOrganizationDuoDesc": { - "message": "Pour imposer la Connexion en 2 Étapes via Duo, utilisez les options ci-dessous." + "message": "Pour imposer l'authentification à deux facteurs via Duo, utilisez les options ci-dessous." }, "twoStepLoginOrganizationSsoDesc": { - "message": "Si vous avez configuré SSO ou planifié le faire, la Connexion en 2 Étapes peut déjà être imposée via votre Fournisseur d'Identité." + "message": "Si vous avez configuré SSO ou si vous prévoyez de le faire, l'authentification à deux facteurs peut déjà être imposée via votre fournisseur d'identité." }, "twoStepLoginRecoveryWarning": { - "message": "Activer la connexion en deux étapes peut vous empêcher définitivement d'accéder à votre compte Bitwarden. Un code de récupération vous permet d'accéder à votre compte dans le cas où vous ne pouvez plus utiliser votre service de connexion en deux étapes habituel (ex. vous perdez votre appareil). Le support Bitwarden ne pourra pas vous aider si vous perdez l'accès à votre compte. Nous vous recommandons d'écrire ou d'imprimer le code de récupération et de le conserver en lieu sûr." + "message": "La mise en place d'un système d'authentification à deux étapes peut vous bloquer définitivement l'accès à votre compte Bitwarden. Un code de récupération vous permet d'accéder à votre compte dans le cas où vous ne pouvez plus utiliser votre fournisseur normal d'authentification à deux facteurs (exemple : vous perdez votre appareil). L'assistance de Bitwarden ne pourra pas vous aider si vous perdez l'accès à votre compte. Nous vous recommandons de noter ou d'imprimer le code de récupération et de le conserver dans un lieu sûr." }, "viewRecoveryCode": { "message": "Voir le code de récupération" @@ -1336,16 +1336,16 @@ "message": "Adhésion Premium" }, "premiumRequired": { - "message": "Version Premium requise" + "message": "Premium requis" }, "premiumRequiredDesc": { - "message": "Une adhésion premium est requise pour utiliser cette fonctionnalité." + "message": "Une adhésion Premium est requise pour utiliser cette fonctionnalité." }, "youHavePremiumAccess": { - "message": "Vous avez un accès premium" + "message": "Vous avez un accès Premium" }, "alreadyPremiumFromOrg": { - "message": "Vous avez déjà accès aux fonctionnalités premium grâce à une organisation dont vous êtes membre." + "message": "Vous avez déjà accès aux fonctionnalités Premium grâce à une organisation dont vous êtes membre." }, "manage": { "message": "Gérer" @@ -1357,19 +1357,19 @@ "message": "Révoquer l'Accès" }, "twoStepLoginProviderEnabled": { - "message": "Ce fournisseur de connexion en deux étapes est activé sur votre compte." + "message": "Ce fournisseur d'authentification à deux facteurs est actif sur votre compte." }, "twoStepLoginAuthDesc": { - "message": "Entrez votre mot de passe principal pour modifier les paramètres de connexion en deux étapes." + "message": "Saisissez votre mot de passe principal pour modifier les paramètres d'authentification à deux facteurs." }, "twoStepAuthenticatorDesc": { - "message": "Suivez ces étapes pour configurer la connexion en deux étapes avec une application d'authentification :" + "message": "Suivre ces étapes pour mettre en place l''authentification à deux facteurs avec une application d'authentification :" }, "twoStepAuthenticatorDownloadApp": { - "message": "Télécharger une application d'authentification en deux étapes" + "message": "Télécharger une application d'authentification à deux facteurs" }, "twoStepAuthenticatorNeedApp": { - "message": "Besoin d'une application d'authentification en deux étapes ? Téléchargez l'une des applications suivantes" + "message": "Besoin d'une application d'authentification à deux facteurs ? Téléchargez en une parmi les suivantes" }, "iosDevices": { "message": "Appareils iOS" @@ -1396,10 +1396,10 @@ "message": "Dans le cas où vous devez l’ajouter à un autre appareil, voici le code QR (ou clé) requis par votre application d'authentification." }, "twoStepDisableDesc": { - "message": "Êtes-vous sûr de que vouloir désactiver ce fournisseur de connexion en deux étapes ?" + "message": "Êtes-vous sûr de vouloir désactiver ce fournisseur d'authentification à deux facteurs ?" }, "twoStepDisabled": { - "message": "Fournisseur de connexion en deux étapes désactivé." + "message": "Fournisseur d'authentification à deux facteurs désactivé." }, "twoFactorYubikeyAdd": { "message": "Ajouter une nouvelle YubiKey à votre compte" @@ -1417,7 +1417,7 @@ "message": "Sauvegarder le contenu du formulaire." }, "twoFactorYubikeyWarning": { - "message": "En raison de limitations de plateforme, les YubiKeys ne sont pas utilisables sur toutes les applications Bitwarden. Vous devriez activer un autre fournisseur de connexion en deux étapes afin de pouvoir accéder à votre compte lorsque vous ne pouvez pas utiliser les YubiKeys. Plateformes prises en charge :" + "message": "En raison des limitations de la plateforme, les YubiKey ne peuvent pas être utilisées sur toutes les applications Bitwarden. Vous devriez mettre en place un autre fournisseur d'authentification à deux facteurs pouvoir accéder à votre compte lorsque les YubiKeys ne peuvent pas être utilisées. Plateformes prises en charge :" }, "twoFactorYubikeySupportUsb": { "message": "Coffre web, application de bureau, interface ligne de commande (CLI) et toutes les extensions de navigateur sur un appareil doté d'un port USB pouvant accepter votre YubiKey." @@ -1480,7 +1480,7 @@ "message": "Nom d'hôte de l'API" }, "twoFactorEmailDesc": { - "message": "Suivez ces étapes pour mettre en place la connexion en deux étapes avec e-mail :" + "message": "Suivre ces étapes pour mettre en place l'authentification à deux facteurs avec courriel :" }, "twoFactorEmailEnterEmail": { "message": "Entrez l'e-mail où vous souhaitez recevoir les codes de vérification" @@ -1519,7 +1519,7 @@ "message": "Sauvegarder le contenu du formulaire." }, "twoFactorU2fWarning": { - "message": "En raison de limitations de plateforme, FIDO U2F n'est pas utilisable sur toutes les applications Bitwarden. Vous devriez activer un autre fournisseur de connexion en deux étapes afin de pouvoir accéder à votre compte lorsque vous ne pouvez pas utiliser FIDO U2F. Plateformes prises en charge :" + "message": "En raison des limitations de la plateforme, FIDO U2F ne peut pas être utilisé sur toutes les applications Bitwarden. Vous devriez mettre en place un autre fournisseur d'authentification à deux facteurs pouvoir accéder à votre compte lorsque FIDO U2F ne peut pas être utilisé. Plateformes prises en charge :" }, "twoFactorU2fSupportWeb": { "message": "Coffre web et extensions sur un ordinateur fixe/portable avec un navigateur compatible U2F (Chrome, Opera, Vivaldi ou Firefox avec FIDO U2F activé)." @@ -1528,22 +1528,22 @@ "message": "Attente de l'appui sur le bouton de votre clé de sécurité" }, "twoFactorU2fClickSave": { - "message": "Cliquez sur le bouton « Sauvegarder » ci-dessous pour activer cette clé de sécurité pour une connexion en deux étapes." + "message": "Utilisez le bouton \"Sauvegarde\" ci-dessous pour activer cette clé de sécurité pour l'authentification à deux facteurs." }, "twoFactorU2fProblemReadingTryAgain": { "message": "Un problème est survenu lors de la lecture de la clé de sécurité. Veuillez réessayer." }, "twoFactorWebAuthnWarning": { - "message": "En raison de limitations de plateforme, WebAuthn n'est pas utilisable sur toutes les applications Bitwarden. Vous devriez activer un autre fournisseur de connexion en deux étapes afin de pouvoir accéder à votre compte lorsque vous ne pouvez pas utiliser WebAuthn. Plateformes prises en charge :" + "message": "En raison des limitations de la plate-forme, WebAuthn ne peut pas être utilisé sur toutes les applications Bitwarden. Vous devriez mettre en place un autre fournisseur d'authentification à deux facteurs afin de pouvoir accéder à votre compte lorsque WebAuthn ne peut pas être utilisé. Plateformes supportées :" }, "twoFactorWebAuthnSupportWeb": { "message": "Coffre web et extensions sur un ordinateur fixe/portable avec un navigateur compatible WebAuthn (Chrome, Opera, Vivaldi ou Firefox avec FIDO U2F activé)." }, "twoFactorRecoveryYourCode": { - "message": "Votre code de récupération de connexion en deux étapes Bitwarden" + "message": "Votre code de récupération de d'authentification à deux facteurs Bitwarden" }, "twoFactorRecoveryNoCode": { - "message": "Vous n'avez pas encore activé de fournisseur de connexion en deux étapes. Après avoir activé un fournisseur de connexion en deux étapes, vous pouvez consulter ici votre code de récupération." + "message": "Vous n'avez pas encore mis en place de fournisseurs d'authentification à deux facteurs. Une fois que vous aurez mis en place un fournisseur d'authentification à deux facteurs, vous pourrez revenir ici pour obtenir votre code de récupération." }, "printCode": { "message": "Imprimer le code", @@ -1582,16 +1582,16 @@ "message": "Aucun élément dans votre coffre n'a d'URI non sécurisés." }, "inactive2faReport": { - "message": "Rapport 2FA inactif" + "message": "Authentification à deux facteurs inactive" }, "inactive2faReportDesc": { - "message": "L'authentification à deux facteurs (2FA) est un paramètre de sécurité important qui permet de sécuriser vos comptes. Si le site Web le propose, vous devriez toujours activer l'authentification à deux facteurs." + "message": "L'authentification à deux facteurs ajoute une couche de protection à vos comptes. Mettez en place l'authentification à deux facteurs en utilisant Bitwarden authenticator pour ces comptes ou utilisez une méthode alternative." }, "inactive2faFound": { - "message": "Identifiants sans 2FA trouvés" + "message": "Identifiants sans authentification à deux facteurs trouvés" }, "inactive2faFoundDesc": { - "message": "Nous avons trouvé $COUNT$ site(s) web dans votre coffre qui peuvent ne pas être configurés avec une connexion en deux étapes (selon 2fa. Pour protéger davantage ces comptes, vous devez configurer une connexion en deux étapes.", + "message": "Nous avons trouvé $COUNT$ site(s) web dans votre coffre peut-être non configuré(s) avec l'authentification à deux facteurs (selon 2fa.directory). Pour protéger davantage ces comptes, vous devriez mettre en place l'authentification à deux facteurs.", "placeholders": { "count": { "content": "$1", @@ -1600,7 +1600,7 @@ } }, "noInactive2fa": { - "message": "Aucun site web n'a été trouvé dans votre coffre-fort avec une configuration d'authentification à deux facteurs manquante." + "message": "Aucun site web n'a été trouvé dans votre coffre avec une configuration d'authentification à deux facteurs manquante." }, "instructions": { "message": "Instructions" @@ -1609,7 +1609,7 @@ "message": "Rapport sur les mots de passe exposés" }, "exposedPasswordsReportDesc": { - "message": "Exposed passwords are passwords have been uncovered in known data breaches that were released publicly or sold on the dark web by hackers." + "message": "Les mots de passe exposés lors d'une brèche de données sont des cibles faciles pour les attaquants. Changez ces mots de passe pour éviter des intrusions potentielles." }, "exposedPasswordsFound": { "message": "Mots de passe exposés trouvés" @@ -1783,38 +1783,38 @@ "message": "Le crédit de votre compte peut être utilisé pour régler vos achats. Tout crédit disponible sera automatiquement appliqué aux factures générées pour ce compte." }, "goPremium": { - "message": "Devenir Premium", + "message": "Passez Premium", "description": "Another way of saying \"Get a Premium membership\"" }, "premiumUpdated": { - "message": "Vous venez de passer à un compte Premium." + "message": "Vous avez mis à niveau vers Premium." }, "premiumUpgradeUnlockFeatures": { - "message": "Mettre à niveau votre compte vers un abonnement premium et débloquez d'incroyables fonctionnalités supplémentaires." + "message": "Mettez votre compte à niveau vers une adhésion Premium et débloquez de superbes fonctionnalités supplémentaires." }, "premiumSignUpStorage": { - "message": "1 Go de stockage de fichiers chiffrés." + "message": "1 Go de stockage chiffré pour les fichiers joints." }, "premiumSignUpTwoStep": { - "message": "Options d'identification en deux étapes additionnelles comme YubiKey, FIDO U2F et Duo." + "message": "Options additionnelles d'authentification à deux facteurs telles que YubiKey, FIDO U2F et Duo." }, "premiumSignUpEmergency": { "message": "Accès d'urgence" }, "premiumSignUpReports": { - "message": "Rapports sur l'hygiène des mots de passe, la santé des comptes et les fuites de données pour assurer la sécurité de votre coffre." + "message": "Hygiène du mot de passe, santé du compte et rapports sur les brèches de données pour assurer la sécurité de votre coffre." }, "premiumSignUpTotp": { - "message": "Génération d'un code de vérification TOTP (2FA) pour les identifiants de votre coffre." + "message": "Générateur de code de vérification TOTP (2FA) pour les identifiants dans votre coffre." }, "premiumSignUpSupport": { - "message": "Support client prioritaire." + "message": "Assistance client prioritaire." }, "premiumSignUpFuture": { - "message": "Toutes les futures options premium. D'autres suivront prochainement !" + "message": "Toutes les futures fonctionnalités Premium. Plus à venir prochainement !" }, "premiumPrice": { - "message": "Tout pour seulement $PRICE$ /an !", + "message": "Tout pour seulement $PRICE$/an !", "placeholders": { "price": { "content": "$1", @@ -1822,14 +1822,30 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Passez Premium pour seulement $PRICE$/an, ou obtenez des comptes Premium pour $FAMILYPLANUSERCOUNT$ utilisateurs et un partage familial illimité avec un ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Plan Bitwarden Familles." + }, "addons": { "message": "Add-ons" }, "premiumAccess": { - "message": "Accès premium" + "message": "Accès Premium" }, "premiumAccessDesc": { - "message": "Vous pouvez ajouter un accès premium à tous les membres de votre organisation pour $PRICE$/$INTERVAL$.", + "message": "Vous pouvez ajouter l'accès Premium à tous les membres de votre organisation pour $PRICE$/$INTERVAL$.", "placeholders": { "price": { "content": "$1", @@ -1926,7 +1942,7 @@ "message": "L’abonnement a été marqué pour être annulé à la fin de la période de facturation actuelle." }, "reinstateSubscription": { - "message": "Rétablir l’abonnement" + "message": "Rétablir l'abonnement" }, "reinstateConfirmation": { "message": "Êtes-vous sûr de vouloir retirer la demande d’annulation en attente et rétablir votre abonnement ?" @@ -1938,7 +1954,7 @@ "message": "Êtes-vous sûr de vouloir annuler ? Vous perdrez l’accès à toutes les fonctionnalités de l’abonnement à la fin de ce cycle de facturation." }, "canceledSubscription": { - "message": "L'abonnement a été annulé." + "message": "Abonnement annulé" }, "neverExpires": { "message": "N'expire jamais" @@ -2070,7 +2086,7 @@ "message": "Fichier de licence" }, "licenseFileDesc": { - "message": "Votre fichier de licence aura un nom similaire à $FILE_NAME$", + "message": "Votre fichier de licence sera nommé comme quelque chose comme $FILE_NAME$", "placeholders": { "file_name": { "content": "$1", @@ -2079,7 +2095,7 @@ } }, "uploadLicenseFilePremium": { - "message": "Pour mettre à niveau votre compte vers un abonnement premium, vous devez fournir un fichier de licence valide." + "message": "Pour mettre à niveau votre compte vers une adhésion Premium, vous devez charger un fichier de licence valide." }, "uploadLicenseFileOrg": { "message": "Pour créer une organisation sur une instance auto-hébergée vous devez fournir un fichier de licence valide." @@ -2237,7 +2253,7 @@ "message": "Hébergement local (optionnel)" }, "usersGetPremium": { - "message": "Les utilisateurs auront accès aux fonctionnalités premium" + "message": "Les utilisateurs ont accès aux fonctionnalités Premium" }, "controlAccessWithGroups": { "message": "Contrôlez l'accès des utilisateurs avec des groupes" @@ -2249,7 +2265,7 @@ "message": "Suivez les actions des utilisateurs avec les journaux d'audit" }, "enforce2faDuo": { - "message": "Forcez l'authentification à deux facteurs (2FA) avec Duo" + "message": "Imposer 2FA avec Duo" }, "priorityCustomerSupport": { "message": "Support client prioritaire" @@ -2417,7 +2433,7 @@ } }, "userUsingTwoStep": { - "message": "Cet utilisateur utilise l'identification en deux étapes pour protéger son compte." + "message": "Cet utilisateur utilise l'authentification à deux facteurs pour protéger son compte." }, "userAccessAllItems": { "message": "Cet utilisateur peut voir et modifier tous les éléments." @@ -2507,19 +2523,19 @@ "message": "Mot de passe changé." }, "enabledUpdated2fa": { - "message": "Connexion en deux étapes activée/mise à jour." + "message": "Authentification à deux facteurs sauvegardée" }, "disabled2fa": { - "message": "Connexion en deux étapes désactivée." + "message": "Authentification à deux facteurs désactivée" }, "recovered2fa": { - "message": "Compte récupéré depuis une connexion en deux étapes." + "message": "Compte récupéré à partir de l'authentification à deux facteurs." }, "failedLogin": { "message": "Tentative de connexion avec mot de passe incorrect." }, "failedLogin2fa": { - "message": "Tentative de connexion échouée avec deuxième facteur incorrect." + "message": "Tentative de connexion échouée avec une authentification à deux facteurs incorrecte." }, "exportedVault": { "message": "Le coffre a été exporté." @@ -2993,13 +3009,13 @@ "message": "Se souvenir de l'e-mail" }, "recoverAccountTwoStepDesc": { - "message": "Si vous ne pouvez pas accéder à votre compte grâce à vos méthodes de connexion en deux étapes habituelles, vous pouvez utiliser votre code de récupération de connexion en deux étapes pour désactiver tous les fournisseurs en deux étapes sur votre compte." + "message": "Si vous ne pouvez pas accéder à votre compte par les méthodes normales d'authentification à deux facteurs, vous pouvez utiliser votre code de récupération d'authentification à deux facteurs pour désactiver tous les fournisseurs à deux facteurs sur votre compte." }, "recoverAccountTwoStep": { - "message": "Récupérer votre compte d'authentification à deux étapes" + "message": "Récupérer l'authentification à deux facteurs" }, "twoStepRecoverDisabled": { - "message": "Ce fournisseur de connexion en deux étapes a été désactivé sur votre compte." + "message": "Authentification à deux facteurs désactivée sur votre compte." }, "learnMore": { "message": "En savoir plus" @@ -3023,7 +3039,7 @@ "message": "Supprimer l'organisation" }, "deletingOrganizationContentWarning": { - "message": "Entrez le mot de passe maître pour confirmer la suppression de $ORGANIZATION$ et de toutes les données associées. Les données du coffre dans $ORGANIZATION$ inclut:", + "message": "Saisir le mot de passe principal pour confirmer la suppression de $ORGANIZATION$ et de toutes les données associées. Les données du coffre de $ORGANIZATION$ comprennent :", "placeholders": { "organization": { "content": "$1", @@ -3137,7 +3153,7 @@ "message": "Saisissez l'identifiant de votre installation" }, "limitSubscriptionDesc": { - "message": "Définissez un nombre de licences limite pour votre abonnement. Une fois cette limite atteinte, vous ne pourrez plus inviter de nouveaux utilisateurs." + "message": "Définissez une limite de licences pour votre abonnement. Une fois cette limite atteinte, vous ne pourrez plus inviter de nouveaux membres." }, "maxSeatLimit": { "message": "Nombre de licences maximum (optionnel)", @@ -3158,7 +3174,7 @@ "message": "Les ajustements apportés à votre abonnement entraîneront des modifications au prorata de vos totaux de facturation. Si les utilisateurs nouvellement invités dépassent votre nombre de licences, vous recevrez immédiatement des frais au prorata pour les utilisateurs supplémentaires." }, "subscriptionUserSeats": { - "message": "Votre abonnement permet un total de $COUNT$ utilisateurs.", + "message": "Votre abonnement permet un total de $COUNT$ membres.", "placeholders": { "count": { "content": "$1", @@ -3167,7 +3183,7 @@ } }, "limitSubscription": { - "message": "Limiter le nombre de licences (optionnel)" + "message": "Limiter l'abonnement (facultatif)" }, "subscriptionSeats": { "message": "Licences" @@ -3263,7 +3279,7 @@ "message": "Vous utilisez actuellement un moyen de chiffrement obsolète." }, "updateEncryptionKeyDesc": { - "message": "Nous sommes passés à des clés de chiffrement plus longues qui fournissent une meilleure sécurité et permettent l'accès à de nouvelles fonctionnalités. La mise à jour de votre clé de chiffrement est rapide et facile. Tapez simplement votre mot de passe maître ci-dessous. Cette mise à jour deviendra peut-être obligatoire." + "message": "Nous sommes passés à des clés de chiffrement plus grandes qui offrent une meilleure sécurité et un accès à des fonctionnalités plus récentes. La mise à jour de votre clé de chiffrement est rapide et facile. Il suffit de saisir votre mot de passe principal ci-dessous. Cette mise à jour deviendra éventuellement obligatoire." }, "updateEncryptionKeyWarning": { "message": "Après avoir mis à jour votre clé de chiffrement, vous devrez vous reconnecter sur toutes les applications Bitwarden que vous utilisez actuellement (comme par exemple l'application mobile ou les extensions de navigateur). Le fait de ne pas vous déconnecter et de vous reconnecter (ce qui télécharge votre nouvelle clé de chiffrement) pourrait entraîner une corruption des données. Nous allons essayer de vous déconnecter automatiquement, mais cela demande un peu de temps." @@ -3390,10 +3406,10 @@ "description": "ex. A very weak password. Scale: Very Weak -> Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Mot de passe maître faible" + "message": "Mot de passe principal faible" }, "weakMasterPasswordDesc": { - "message": "Le mot de passe maître que vous avez choisi est faible. Vous devriez utiliser un mot de passe (ou une phrase de passe) fort(e) pour protéger correctement votre compte Bitwarden. Êtes-vous sûr de vouloir utiliser ce mot de passe maître ?" + "message": "Le mot de passe principal que vous avez choisi est faible. Vous devriez utiliser un mot de passe principal fort (ou une phrase de passe) pour protéger correctement votre compte Bitwarden. Êtes-vous sûr de vouloir utiliser ce mot de passe principal ?" }, "rotateAccountEncKey": { "message": "Révoquer également la clé de chiffrement de mon compte" @@ -3489,31 +3505,31 @@ "message": "Cloner" }, "masterPassPolicyTitle": { - "message": "Exigences pour le mot de passe maître" + "message": "Exigences du mot de passe principal" }, "masterPassPolicyDesc": { - "message": "Définir les exigences minimales pour la force du mot de passe maître." + "message": "Définir les exigences de robustesse du mot de passe principal." }, "twoStepLoginPolicyTitle": { - "message": "Nécessite une connexion en deux étapes" + "message": "Exiger une authentification à deux facteurs" }, "twoStepLoginPolicyDesc": { - "message": "Imposer aux membres l'usage de la double authentification." + "message": "Exiger des membres qu'ils mettent en place une authentification à deux facteurs." }, "twoStepLoginPolicyWarning": { - "message": "Les membres de l'organisation (hors propriétaires et administrateurs) qui n'ont pas activé la connexion en deux étapes sur leur compte personnel seront supprimés de l'organisation et recevront un courriel les informant du changement." + "message": "Les membres de l'organisation qui ne sont pas propriétaires ou administrateurs et qui n'ont pas configuré l'authentification à deux facteurs pour leur compte seront supprimés de l'organisation et recevront un courriel les informant de ce changement." }, "twoStepLoginPolicyUserWarning": { - "message": "Vous êtes membre d'une organisation qui nécessite que la connexion en deux étapes soit activée sur votre compte utilisateur. Si vous désactivez tous les fournisseurs de connexion en deux étapes, vous serez automatiquement retiré de ces organisations." + "message": "Vous êtes un membre d'une organisation qui exige que l'authentification à deux facteurs soit configurée sur votre compte utilisateur. Si vous désactivez tous les fournisseurs d'authentification à deux facteurs, vous serez automatiquement retiré de ces organisations." }, "passwordGeneratorPolicyDesc": { - "message": "Définir les exigences minimales pour la configuration du générateur de mot de passe." + "message": "Définir les exigences pour le générateur de mot de passe." }, "passwordGeneratorPolicyInEffect": { "message": "Une ou plusieurs politiques d'organisation affectent les paramètres de votre générateur." }, "masterPasswordPolicyInEffect": { - "message": "Une ou plusieurs politiques de l'organisation exigent que votre mot de passe maître réponde aux exigences suivantes :" + "message": "Une ou plusieurs politiques de l'organisation exigent que votre mot de passe principal réponde aux exigences suivantes :" }, "policyInEffectMinComplexity": { "message": "Score de complexité minimum de $SCORE$", @@ -3552,7 +3568,7 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "Votre nouveau mot de passe maître ne répond pas aux exigences de la politique." + "message": "Votre nouveau mot de passe principal ne répond pas aux exigences en matière de politique de sécurité." }, "minimumNumberOfWords": { "message": "Nombre minimum de mots" @@ -3567,7 +3583,7 @@ "message": "Action lors de l'expiration du délai du coffre" }, "vaultTimeoutActionLockDesc": { - "message": "Un coffre verrouillé requiert la saisie de votre mot de passe maître pour y avoir à nouveau accès." + "message": "Un mot de passe principal ou une autre méthode de déverrouillage est nécessaire pour accéder à nouveau à votre coffre." }, "vaultTimeoutActionLogOutDesc": { "message": "Un coffre déconnecté nécessite que vous vous ré-authentifiez pour y accéder de nouveau." @@ -3659,7 +3675,7 @@ } }, "vaultTimeoutLogOutConfirmation": { - "message": "La déconnexion supprimera tous les accès à votre coffre et nécessite une authentification en ligne après la période d'expiration. Êtes-vous sûr de vouloir utiliser ce paramètre?" + "message": "La déconnexion supprimera tout accès à votre coffre et nécessitera une authentification en ligne après la période d'expiration. Êtes-vous sûr de vouloir utiliser ce paramètre ?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Confirmation de l'action lors de l'expiration du délai" @@ -3680,10 +3696,10 @@ "message": "Informations sur les taxes mises à jour." }, "setMasterPassword": { - "message": "Définir le mot de passe maître" + "message": "Définir le mot de passe principal" }, "ssoCompleteRegistration": { - "message": "Afin de terminer la connexion avec SSO, veuillez définir un mot de passe maître pour accéder à votre coffre et le protéger." + "message": "Afin de finaliser la connexion avec SSO, veuillez définir un mot de passe principal pour accéder et protéger votre coffre." }, "identifier": { "message": "Identifiant" @@ -3950,7 +3966,7 @@ "message": "Prise de contrôle" }, "takeoverDesc": { - "message": "Peut réinitialiser votre compte avec un nouveau mot de passe maître." + "message": "Peut réinitialiser votre compte avec un nouveau mot de passe principal." }, "waitTime": { "message": "Période d'attente" @@ -4387,7 +4403,7 @@ "message": "Réinitialiser le mot de passe" }, "resetPasswordLoggedOutWarning": { - "message": "En continuant, $NAME$ sera déconnecté de sa session actuelle, ce qui lui demandera de se reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives jusqu'à une heure.", + "message": "En poursuivant, $NAME$ sera déconnecté de sa session actuelle, ce qui l'obligera à se reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives pendant encore une heure.", "placeholders": { "name": { "content": "$1", @@ -4399,22 +4415,22 @@ "message": "cet utilisateur" }, "resetPasswordMasterPasswordPolicyInEffect": { - "message": "Une ou plusieurs politiques de l'organisation exigent que le mot de passe maître réponde aux exigences suivantes :" + "message": "Une ou plusieurs politiques de l'organisation exigent que le mot de passe principal réponde aux exigences suivantes :" }, "resetPasswordSuccess": { "message": "Mot de passe réinitialisé avec succès !" }, "resetPasswordEnrollmentWarning": { - "message": "L'inscription permettra aux administrateurs de l'organisation de changer votre mot de passe maître. Êtes-vous sûr de vouloir vous inscrire ?" + "message": "L'inscription permettra aux administrateurs de l'organisation de changer votre mot de passe principal" }, "resetPasswordPolicy": { - "message": "Réinitialisation du mot de passe maître" + "message": "Réinitialisation du mot de passe principal" }, "resetPasswordPolicyDescription": { - "message": "Permettre aux administrateurs de l'organisation de réinitialiser le mot de passe maître des utilisateurs de l'organisation." + "message": "Autoriser les administrateurs à réinitialiser les mots de passe principaux pour les membres." }, "resetPasswordPolicyWarning": { - "message": "Les utilisateurs de l'organisation devront s'inscrire personnellement ou être inscrits automatiquement avant que les administrateurs puissent réinitialiser leur mot de passe maître." + "message": "Les membres de l'organisation devront s'inscrire eux-mêmes ou être inscrits automatiquement avant que les administrateurs puissent réinitialiser leur mot de passe principal." }, "resetPasswordPolicyAutoEnroll": { "message": "Inscription automatique" @@ -4423,13 +4439,13 @@ "message": "Tous les utilisateurs seront automatiquement inscrits à la réinitialisation du mot de passe une fois leur invitation acceptée et n'auront pas la possibilité de refuser." }, "resetPasswordPolicyAutoEnrollWarning": { - "message": "Les utilisateurs déjà dans l'organisation ne seront pas inscrits rétroactivement à la réinitialisation du mot de passe. Ils devront s'inscrire personnellement avant que les administrateurs puissent réinitialiser leur mot de passe maître." + "message": "Les membres qui font déjà partie de l'organisation ne seront pas inscrits rétroactivement à la réinitialisation du mot de passe. Ils devront s'inscrire eux-mêmes avant que les administrateurs puissent réinitialiser leur mot de passe principal." }, "resetPasswordPolicyAutoEnrollCheckbox": { "message": "Exiger que les nouveaux utilisateurs soient inscrits automatiquement" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Cette organisation a une politique d'entreprise qui vous inscrira automatiquement à la réinitialisation du mot de passe. L'inscription permettra aux administrateurs de l'organisation de changer votre mot de passe maître." + "message": "Cette organisation dispose d'une politique d'entreprise qui vous inscrira automatiquement à la réinitialisation du mot de passe. L'inscription permettra aux administrateurs de l'organisation de changer votre mot de passe principal." }, "resetPasswordOrgKeysError": { "message": "Erreur lors du traitement de votre demande : \"Organization Keys response is null\"" @@ -4444,13 +4460,13 @@ "message": "Les éléments présents dans la corbeille depuis un moment seront automatiquement supprimés." }, "passwordPrompt": { - "message": "Ressaisie du mot de passe maître" + "message": "Ressaisir le mot de passe principal" }, "passwordConfirmation": { - "message": "Confirmation du mot de passe maître" + "message": "Confirmation du mot de passe principal" }, "passwordConfirmationDesc": { - "message": "Cette action est protégée. Pour continuer, veuillez ressaisir votre mot de passe maître pour vérifier votre identité." + "message": "Cette action est protégée. Pour continuer, veuillez saisir à nouveau votre mot de passe principal pour vérifier votre identité." }, "reinviteSelected": { "message": "Renvoyer les invitations" @@ -4650,16 +4666,16 @@ "message": "Ajouter" }, "updatedMasterPassword": { - "message": "Mot de passe maître mis à jour" + "message": "Mot de passe principal enregistré" }, "updateMasterPassword": { - "message": "Mettre à jour le mot de passe maître" + "message": "Mettre à jour le mot de passe principal" }, "updateMasterPasswordWarning": { - "message": "Votre mot de passe maître a récemment été modifié par un administrateur de votre organisation. Pour accéder au coffre, vous devez mettre à jour votre mot de passe maître dès maintenant. Cette action va vous déconnecter et vous obligera à vous reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives jusqu'à une heure." + "message": "Votre mot de passe principal a été récemment changé par un administrateur de votre organisation. Pour pouvoir accéder au coffre, vous devez mettre à jour votre mot de passe principal maintenant. En poursuivant, vous serez déconnecté de votre session actuelle et vous devrez vous reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives pendant encore une heure." }, "masterPasswordInvalidWarning": { - "message": "Votre mot de passe maître ne répond pas aux exigences des politiques de cette organisation. Afin de rejoindre l'organisation, vous devez mettre à jour votre mot de passe maître maintenant. En continuant, vous serez déconnecté de votre session actuelle et vous devrez vous reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives pendant encore une heure." + "message": "Votre mot de passe principal ne répond pas aux exigences de politique de sécurité de cette organisation. Afin de rejoindre l'organisation, vous devez mettre à jour votre mot de passe principal dès maintenant. En poursuivant, vous serez déconnecté de votre session actuelle et vous devrez vous reconnecter. Les sessions actives sur d'autres appareils peuvent rester actives pendant encore une heure." }, "maximumVaultTimeout": { "message": "Délai d'expiration du coffre" @@ -4699,7 +4715,7 @@ "message": "Le délai d'expiration de votre coffre-fort dépasse les restrictions définies par votre organisation." }, "vaultCustomTimeoutMinimum": { - "message": "Le délai minimum de mise en veille personnalisée est 1 minute." + "message": "Le délai d'expiration personnalisé minimum est de 1 minute." }, "vaultTimeoutRangeError": { "message": "Le délai de mise en veille du coffre n'est pas dans l'intervalle de temps autorisé." @@ -4735,7 +4751,7 @@ "message": "URL de callback" }, "signedOutCallbackPath": { - "message": "URL de callback pour la déconnexion" + "message": "Chemin de \"callback\" lorsque déconnecté" }, "authority": { "message": "Autorité" @@ -4810,7 +4826,7 @@ "message": "URL du service de connexion unique (SSO)" }, "idpSingleLogoutServiceUrl": { - "message": "URL du service de déconnexion unique (SLO)" + "message": "URL du service de déconnexion unique" }, "idpX509PublicCert": { "message": "Certificat public X.509" @@ -4843,7 +4859,7 @@ "message": "L'abonnement Bitwarden Familles inclut" }, "sponsoredFamiliesPremiumAccess": { - "message": "Accès premium pour un maximum de 6 utilisateurs" + "message": "Accès Premium jusqu'à 6 utilisateurs" }, "sponsoredFamiliesSharedCollections": { "message": "Collections partagées pour les secrets de la famille" @@ -4984,10 +5000,10 @@ "message": "Quitter l'organisation" }, "removeMasterPassword": { - "message": "Supprimer le mot de passe maître" + "message": "Supprimer le mot de passe principal" }, "removedMasterPassword": { - "message": "Mot de passe maître supprimé." + "message": "Mot de passe principal supprimé" }, "allowSso": { "message": "Autoriser l'authentification SSO" @@ -5014,13 +5030,13 @@ "message": "Options de déchiffrement des membres" }, "memberDecryptionPassDesc": { - "message": "Une fois authentifiés, les membres déchiffreront les données du coffre en utilisant leur mot de passe maître." + "message": "Une fois authentifiés, les membres déchiffreront les données du coffre à l'aide de leur mot de passe principal." }, "keyConnector": { "message": "Key Connector" }, "memberDecryptionKeyConnectorDesc": { - "message": "Connectez l'authentification SSO à votre serveur de clés de déchiffrement auto-hébergé. En utilisant cette option, les membres n'auront pas besoin d'utiliser leur mot de passe maître pour déchiffrer les données du coffre. Contactez le support Bitwarden pour une assistance à la configuration." + "message": "Connectez l'authentification par SSO à votre serveur de clés de déchiffrement auto-hébergé. En utilisant cette option, les membres n'auront pas besoin d'utiliser leurs mots de passe principaux pour déchiffrer les données du coffre. Contactez le support Bitwarden pour une assistance à la mise en place." }, "keyConnectorPolicyRestriction": { "message": "\"Connexion avec SSO et déchiffrement avec Key Connector\" est activé. Cette politique ne s'appliquera qu'aux propriétaires et aux administrateurs." @@ -5038,7 +5054,7 @@ "message": "Key Connector désactivé" }, "keyConnectorWarning": { - "message": "Dès que les membres de votre organisation commencent à utiliser Key Connector, votre organisation ne peut pas revenir au déchiffrement avec mot de passe maître. Ne continuez que si vous êtes à l'aise avec le déploiement et la maintenance d'un serveur de clés." + "message": "Une fois que les membres commencent à utiliser Key Connector, votre organisation ne peut plus revenir au déchiffrement par mot de passe principal. Ne procédez que si vous êtes à l'aise avec le déploiement et la gestion d'un serveur de clés." }, "migratedKeyConnector": { "message": "Migré vers Key Connector" @@ -5209,7 +5225,7 @@ "message": "Accès Refusé. Vous n'avez pas la permission de voir cette page." }, "masterPassword": { - "message": "Mot de Passe Maître" + "message": "Mot de passe principal" }, "security": { "message": "Sécurité" @@ -5565,10 +5581,10 @@ "message": "Sélectionnez les projets auxquels le secret sera associé. Seuls les utilisateurs de l'organisation ayant accès à ces projets pourront voir le secret." }, "typeOrSelectProjects": { - "message": "Écrivez ou sélectionnez des projets" + "message": "Écrivez ou sélectionnez des Projets" }, "typeOrSelectProject": { - "message": "Écrivez ou sélectionnez un projet" + "message": "Écrivez ou sélectionnez un Projet" }, "project": { "message": "Projet" diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index d30b2a737e6..64358b965c1 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -1609,7 +1609,7 @@ "message": "דו\"ח סיסמאות שנחשפו" }, "exposedPasswordsReportDesc": { - "message": "סיסמאות שנחשפו הם סיסמאות שנמצאו בפרצות אבטחה ידועות וגלויות לציבור או נמכרות בDark web על ידי האקרים." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "נמצאו סיסמאות שנחשפו" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "תוספים" }, diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index d25763308e0..51d332572b1 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 1e9ca68266b..ba82c526d8f 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -1609,7 +1609,7 @@ "message": "Izvještaj o izloženim lozinkama" }, "exposedPasswordsReportDesc": { - "message": "Izložene lozinke su su one otkrivene prilikom znanih krađa podataka pri čemu su javno objavljene ili su ih hakeri prodavali na dark webu." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "Pronađene izložene lozinke" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Dodaci" }, diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 04cd14754e6..719e0a8aae9 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -269,13 +269,13 @@ "message": "Szerkesztés" }, "searchCollection": { - "message": "Keresés a gyűjteményben" + "message": "Gyűjtemény keresése" }, "searchFolder": { "message": "Mappa keresése" }, "searchFavorites": { - "message": "Keresés a kedvencek közt" + "message": "Kedvencek keresése" }, "searchType": { "message": "Típus keresése", @@ -324,7 +324,7 @@ "message": "Gyűjtemények" }, "firstName": { - "message": "Keresztnév" + "message": "Személynév" }, "middleName": { "message": "Középső név" @@ -443,7 +443,7 @@ "message": "A kiválasztott áthelyezése szervezetbe" }, "deleteSelected": { - "message": "Kijelöltek törlése" + "message": "Kiválasztott törlése" }, "moveSelected": { "message": "Kijelöltek áthelyezése" @@ -452,13 +452,13 @@ "message": "Összes kijelölése" }, "unselectAll": { - "message": "Kijelölés megszüntetése" + "message": "Összes kijelölés megszüntetése" }, "launch": { "message": "Indítás" }, "newAttachment": { - "message": "Melléklet hozzáadása" + "message": "Új melléklet hozzáadása" }, "deletedAttachment": { "message": "A melléklet törlésre került." @@ -482,10 +482,10 @@ "message": "Ez a funkció nem használható a titkosítási kulcs frissítéséig." }, "addedItem": { - "message": "Elem hozzáadva" + "message": "Az elem hozzáadásra került." }, "editedItem": { - "message": "Elem szerkesztve" + "message": "Az elem szerkesztésre került." }, "movedItemToOrg": { "message": "$ITEMNAME$ átkerült $ORGNAME$ szervezethez", @@ -882,10 +882,10 @@ } }, "verificationCodeTotp": { - "message": "Ellenőrző kód (egyszeri időalapú)" + "message": "Verification Code (TOTP)" }, "copyVerificationCode": { - "message": "Ellenőrző kód másolása" + "message": "Copy Verification Code" }, "warning": { "message": "Figyelmeztetés" @@ -1003,7 +1003,7 @@ "description": "Make the first letter of a word uppercase." }, "includeNumber": { - "message": "Tartalmazzon számot" + "message": "Szám is" }, "passwordHistory": { "message": "Jelszóelőzmények" @@ -1043,7 +1043,7 @@ "message": "A folytatásban a felhasználó kiléptetésre kerül a jelenlegi munkamenetből, szükséges az ismételt bejelentkezés. Más eszközökön aktív munkamenetek akár egy órán keresztül is aktívak maradhatnak." }, "emailChanged": { - "message": "Email cím megváltoztatva" + "message": "Az email cím megváltozott." }, "logBackIn": { "message": "Ismételten be kell jelentkezni." @@ -1055,7 +1055,7 @@ "message": "Mesterjelszó módosítása" }, "masterPasswordChanged": { - "message": "Mesterjelszó megváltoztatva" + "message": "A mesterjelszó megváltozott." }, "currentMasterPass": { "message": "Jelenlegi mesterjelszó" @@ -1064,7 +1064,7 @@ "message": "Új mesterjelszó" }, "confirmNewMasterPass": { - "message": "Új jelszó megerősítése" + "message": "Új mesterjelszó megerősítése" }, "encKeySettings": { "message": "Kulcs beállítások titkosítása" @@ -1582,13 +1582,13 @@ "message": "A széfben nincs nem-biztonságos URI-val rendelkező elem." }, "inactive2faReport": { - "message": "2FA jelentés kikapcsolása" + "message": "Kétlépéses bejelentkezés kikapcsolása" }, "inactive2faReportDesc": { - "message": "A kétlépcsős hitelesítés (2FA) egy fontos biztonsági beállítás, amely biztosítja a fiókokat. Ha egy webhely felkínálja ezt, célszerű mindig engedélyezni a kétlépcsős hitelesítést." + "message": "A kétlépcsős bejelentkezés egy védelmi réteget ad a fiókoknak. Állítsunk be kétlépéses vejelentkezést a Bitwarden hitelesítővel ezekhez a fiókokhoz vagy használjunk alternatív módot." }, "inactive2faFound": { - "message": "2FA nélküli bejelentkezések találhatók." + "message": "Kétlépések bejelentkezés nélküli bejelentkezések találhatók." }, "inactive2faFoundDesc": { "message": "$COUNT$ olyan webhelyet találtunk a széfben, amely nincs kétlépcsős hitelesítéssel konfigurálva (a 2fa.directory adatbázisa alapján). Ezen fiókok további védelme érdekében, javasolt a kétlépcsős hitelesítés használata.", @@ -1762,7 +1762,7 @@ "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." }, "accountBalance": { - "message": "Számla egyenleg", + "message": "Fiók egyenleg", "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." }, "addCredit": { @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Prémium csomag mindössze $PRICE$/év áron, vagy szerezzünk be prémium fiókot $FAMILYPLANUSERCOUNT$ felhasználónak és korlátlan családi megosztást:", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden családi csomag." + }, "addons": { "message": "Kiegészítők" }, @@ -2438,7 +2454,7 @@ "message": "Megerősítve" }, "clientOwnerEmail": { - "message": "Ügyféltulajdonos e-mail cím" + "message": "Ügyféltulajdonos email cím" }, "owner": { "message": "Tulajdonos" @@ -3629,10 +3645,10 @@ "message": "Elem visszaállítása" }, "restoredItem": { - "message": "Visszaállított elem" + "message": "Az elem visszaállításra került." }, "restoredItems": { - "message": "Visszaállított elemek" + "message": "Az elemek visszaállításra kerültek." }, "restoreItemConfirmation": { "message": "Biztosan visszaállításra kerüljön ez az elem?" @@ -3767,7 +3783,7 @@ "message": "Szöveg" }, "createSend": { - "message": "Új küldés létrehozása", + "message": "Új Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -3775,15 +3791,15 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "A küldés létrejött.", + "message": "A Send mentésre került.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "A küldés szerkesztésre került.", + "message": "A Send szerkesztésre került.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "A küldés törlésre került.", + "message": "A Send törlésre került.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSend": { @@ -3841,7 +3857,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLink": { - "message": "Hivatkozás küldés másolása", + "message": "Send hivatkozás másolása", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { @@ -3857,7 +3873,7 @@ "message": "Saját email cím elrejtése a címzettek elől." }, "disableThisSend": { - "message": "A Küldés letiltásával mindenki hozzáférése megvonható.", + "message": "A Send letiltásával mindenki hozzáférése megvonható.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "allSends": { @@ -3882,11 +3898,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendProtectedPasswordDontKnow": { - "message": "Nem ismerjük a jelszót? Kérdezzünk rá a küldőnél a küldés elérésére szükséges jelszóért.", + "message": "Nem ismerjük a jelszót? Kérdezzünk rá a küldőnél a Send elérésére szükséges jelszóért.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendHiddenByDefault": { - "message": "Ez a küldés alapértelmezésben rejtett. Az alábbi gombbal átváltható a láthatósága.", + "message": "Ez a Send alapértelmezésben rejtett. Az alábbi gombbal átváltható a láthatósága.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "downloadFile": { @@ -4056,7 +4072,7 @@ "message": "A szervezet tulajdonosai és adminisztrátorai mentesek az irányelv végrehajtása alól." }, "personalOwnershipSubmitError": { - "message": "Egy vállalati házirend miatt korlátozásra került az elemek személyes tárolóba történő mentése. Módosítsa a Tulajdon opciót egy szervezetre és válasszon az elérhető gyűjtemények közül." + "message": "Egy vállalati házirend miatt korlátozásra került az elemek személyes tárolóba történő mentése. Módosítsuk a Tulajdon opciót egy szervezetre és válasszunk az elérhető gyűjtemények közül." }, "disableSend": { "message": "Send letiltása" @@ -4069,11 +4085,11 @@ "message": "A szervezet házirendjét kezeő szervezeti felhasználók, mentesülnek az irányelvek végrehajtása alól." }, "sendDisabled": { - "message": "A küldés kikapcsolásra került", + "message": "A Send eltávolításra került.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "A vállalati házirend miatt csak egy meglévő Küldés törölhető.", + "message": "A vállalati házirend miatt csak egy meglévő Send törölhető.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptions": { @@ -4429,7 +4445,7 @@ "message": "Új felhasználók automatikus regisztrálása" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Ennek a szervezetnek van egy vállalati házirendje, amely automatikusan regisztrál a jelszó alaphelyzetbe állítására. A regisztráció lehetővé teszi a szervezet adminisztrátorainak a mesterjelszó megváltoztatását." + "message": "Ennek a szervezetnek van egy vállalati házirendje, amely automatikusan regisztrál a jelszó alaphelyzetbe állítására. A regisztráció lehetővé teszi a szervezet adminisztrtorainak a mesterjelszó megváltoztatását." }, "resetPasswordOrgKeysError": { "message": "A szervezeti kulcs válasza null" @@ -4771,7 +4787,7 @@ "message": "Kért hitelesítési kontextusosztály referenciaértékek" }, "expectedReturnAcrValue": { - "message": "Elvárt „acr” követelésérték a válaszban (acr érvényesítés)" + "message": "Elvárt \"acr” követelésérték a válaszban" }, "spEntityId": { "message": "SP Szervezet AZ" @@ -4933,7 +4949,7 @@ "message": "A szponzorálás eltávolítása után felelősséget vállalunk az előfizetésért és a kapcsolódó számlákért. Biztosan folytatjuk?" }, "sponsorshipCreated": { - "message": "A szponzoráció létrejött" + "message": "A szponzoráció létrejött." }, "emailSent": { "message": "Az email elküldésre került." @@ -5026,10 +5042,10 @@ "message": "A „Bejelentkezés SSO szolgáltatással és a kulcskapcsoló visszafejtésével” engedélyezve van. Ez a szabály csak a tulajdonosokra és a rendszergazdákra vonatkozik." }, "enabledSso": { - "message": "Bekapcsolt SSO" + "message": "Az SSO bekapcsolásra került." }, "disabledSso": { - "message": "Kikapcsolt SEO" + "message": "Az SSO bekapcsolásra került." }, "enabledKeyConnector": { "message": "Bekapcsolt kulcskapcsoló" @@ -5562,13 +5578,13 @@ "message": "Titkos kódok törlése" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Válasszzuk ki azokat a projekteket, amelyekhez a titkos kód társítva lesz. Csak az ezekhez a projektekhez hozzáféréssel rendelkező szervezeti felhasználók láthatják a titkos kódot." }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "Projektek begépelése vagy kiválasztása" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "Projekt begépelése vagy kiválasztása" }, "project": { "message": "Projekt" diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 47fe61e0a8b..a2bba33fbd3 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -29,7 +29,7 @@ } }, "newUri": { - "message": "URl Baru" + "message": "URI Baru" }, "username": { "message": "Nama Pengguna" @@ -50,7 +50,7 @@ "message": "Kolom Ubahsuai" }, "cardholderName": { - "message": "Nama Pemilik Kartu" + "message": "Nama Pemegang Kartu" }, "number": { "message": "Nomor" @@ -125,13 +125,13 @@ "message": "Panggilan" }, "mr": { - "message": "Tuan" + "message": "Tn" }, "mrs": { - "message": "Nyonya" + "message": "Ny" }, "ms": { - "message": "Nona" + "message": "Nn" }, "dr": { "message": "Dr" @@ -146,7 +146,7 @@ "message": "Kunci Autentikasi (TOTP)" }, "folder": { - "message": "Folder" + "message": "Direktori" }, "newCustomField": { "message": "Kolom Ubahsuai Baru" @@ -187,7 +187,7 @@ "message": "Edit Folder" }, "baseDomain": { - "message": "Domain basis", + "message": "Domain dasar", "description": "Domain name. Example: website.com" }, "domainName": { @@ -199,10 +199,10 @@ "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." }, "exact": { - "message": "Tepat" + "message": "Persis" }, "startsWith": { - "message": "Mulai dengan" + "message": "Dimulai dengan" }, "regEx": { "message": "Ekspresi umum", @@ -233,7 +233,7 @@ "message": "Periksa apakah kata sandi telah terekspos." }, "passwordExposed": { - "message": "Sandi ini telah dibuka $VALUE$ kali karena pelanggaran data. Anda harus mengubahnya.", + "message": "Sandi ini telah terekspos $VALUE$ kali dalam kebocoran data. Sebaiknya, anda mengubahnya.", "placeholders": { "value": { "content": "$1", @@ -242,13 +242,13 @@ } }, "passwordSafe": { - "message": "Kata sandi ini tidak ditemukan dalam data pelanggaran yang dikenal. Kata sandi tersebut harusnya aman untuk digunakan." + "message": "Kata sandi ini tidak ditemukan dalam kebocoran data yang diketahui. Kata sandi tersebut seharusnya aman untuk digunakan." }, "save": { "message": "Simpan" }, "cancel": { - "message": "Batal" + "message": "Batalkan" }, "canceled": { "message": "Dibatalkan" @@ -266,7 +266,7 @@ "message": "Batalkan favorit" }, "edit": { - "message": "Ubah" + "message": "Sunting" }, "searchCollection": { "message": "Cari koleksi" @@ -375,7 +375,7 @@ "message": "Lihat Item" }, "ex": { - "message": "contoh", + "message": "cth.", "description": "Short abbreviation for 'example'." }, "other": { @@ -651,7 +651,7 @@ "message": "Diperlukan pengetikan ulang kata sandi utama." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "Kata sandi utama harus memiliki sekurang-kurangnya 8 karakter." }, "masterPassDoesntMatch": { "message": "Konfirmasi sandi utama tidak cocok." @@ -694,7 +694,7 @@ "message": "Sandi utama tidak valid" }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "Kata sandi berkas tidak valid, harap menggunakan kata sandi yang anda masukkan saat anda membuat berkas ekspor." }, "lockNow": { "message": "Kunci Sekarang" @@ -915,7 +915,7 @@ "message": "This file export will be password protected and require the file password to decrypt." }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "Kata sandi ini akan digunakan untuk mengekspor dan mengimpor berkas ini" }, "confirmMasterPassword": { "message": "Konfirmasi kata sandi utama" @@ -936,10 +936,10 @@ "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." }, "exportTypeHeading": { - "message": "Export type" + "message": "Jenis ekspor" }, "accountRestricted": { - "message": "Account restricted" + "message": "Akun dibatasi" }, "passwordProtected": { "message": "Dilindungi kata sandi" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Pengaya" }, @@ -1880,7 +1896,7 @@ "message": "bulan" }, "monthAbbr": { - "message": "mo.", + "message": "bln.", "description": "Short abbreviation for 'month'" }, "paymentChargedAnnually": { @@ -2597,7 +2613,7 @@ } }, "viewedCardNumberItemId": { - "message": "Viewed Card Number for item $ID$.", + "message": "Melihat nomor kartu untuk item $ID$.", "placeholders": { "id": { "content": "$1", @@ -3017,7 +3033,7 @@ "message": "Organisasi Saya" }, "organizationInfo": { - "message": "Organization info" + "message": "Info organisasi" }, "deleteOrganization": { "message": "Hapus Organisasi" @@ -3713,7 +3729,7 @@ "message": "Validasi SSO Gagal" }, "ssoIdentifierRequired": { - "message": "Pengenal Organisasi wajib diisi." + "message": "Pengenal SSO Organisasi wajib diisi." }, "ssoIdentifier": { "message": "SSO identifier" @@ -4138,7 +4154,7 @@ "message": "Izin" }, "permission": { - "message": "Permission" + "message": "Izin" }, "managerPermissions": { "message": "Pengelola Izin" @@ -4450,7 +4466,7 @@ "message": "Konfirmasi sandi utama" }, "passwordConfirmationDesc": { - "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + "message": "Tindakan ini dilindungi. Untuk melanjutkan, harap masukkan ulang sandi utama Anda untuk memverifikasi identitas Anda." }, "reinviteSelected": { "message": "Kirim Ulang Undangan" @@ -4723,13 +4739,13 @@ "message": "Tipe" }, "openIdConnectConfig": { - "message": "OpenID connect configuration" + "message": "Konfigurasi OpenID Connect" }, "samlSpConfig": { - "message": "SAML service provider configuration" + "message": "Konfigurasi penyedia layanan SAML" }, "samlIdpConfig": { - "message": "SAML identity provider configuration" + "message": "Konfigurasi penyedia identitas SAML" }, "callbackPath": { "message": "Callback path" @@ -4828,10 +4844,10 @@ "message": "Sign authentication requests" }, "ssoSettingsSaved": { - "message": "Single sign-on configuration saved" + "message": "Konfigurasi single sign-on tersimpan" }, "sponsoredFamilies": { - "message": "Free Bitwarden Families" + "message": "Bitwarden Families Gratis" }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." @@ -4855,7 +4871,7 @@ "message": "Reclaimed free plan" }, "redeem": { - "message": "Redeem" + "message": "Tukarkan" }, "sponsoredFamiliesSelectOffer": { "message": "Select the organization you would like sponsored" @@ -4894,7 +4910,7 @@ "message": "Free Bitwarden Families offer successfully redeemed" }, "redeemed": { - "message": "Redeemed" + "message": "Sudah ditukar" }, "redeemedAccount": { "message": "Account redeemed" @@ -5026,10 +5042,10 @@ "message": "\"Login with SSO and Key Connector Decryption\" is activated. This policy will only apply to owners and admins." }, "enabledSso": { - "message": "Aktifkan SSO" + "message": "SSO aktif" }, "disabledSso": { - "message": "Matikan SSO" + "message": "SSO tidak aktif" }, "enabledKeyConnector": { "message": "Konektor Kunci diaktifkan" @@ -5053,7 +5069,7 @@ "message": "New Families organization" }, "acceptOffer": { - "message": "Accept offer" + "message": "Terima penawaran" }, "sponsoringOrg": { "message": "Sponsoring organization" @@ -5128,13 +5144,13 @@ "message": "Aktif" }, "inactive": { - "message": "Inactive" + "message": "Tidak aktif" }, "sentAwaitingSync": { - "message": "Sent (awaiting sync)" + "message": "Terkirim (menunggu penyinkronan)" }, "sent": { - "message": "Sent" + "message": "Terkirim" }, "requestRemoved": { "message": "Removed (awaiting sync)" @@ -5143,7 +5159,7 @@ "message": "Requested" }, "formErrorSummaryPlural": { - "message": "$COUNT$ fields above need your attention.", + "message": "$COUNT$ isian diatas membutuhkan perhatian Anda.", "placeholders": { "count": { "content": "$1", @@ -5152,7 +5168,7 @@ } }, "formErrorSummarySingle": { - "message": "1 field above needs your attention." + "message": "Satu isian di atas membutuhkan perhatian Anda." }, "fieldRequiredError": { "message": "$FIELDNAME$ diperlukan.", @@ -5167,7 +5183,7 @@ "message": "diperlukan" }, "idpSingleSignOnServiceUrlRequired": { - "message": "Required if Entity ID is not a URL." + "message": "Diperlukan jika Entity ID bukan sebuah URL." }, "openIdOptionalCustomizations": { "message": "Kustomisasi Opsional" @@ -5176,7 +5192,7 @@ "message": "Required if Authority is not valid." }, "separateMultipleWithComma": { - "message": "Separate multiple with a comma." + "message": "Pisahkan beberapa nilai dengan tanda koma." }, "sessionTimeout": { "message": "Your session has timed out. Please go back and try logging in again." @@ -5206,7 +5222,7 @@ } }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "Akses ditolak. Anda tidak mempunyai izin untuk melihat halaman ini." }, "masterPassword": { "message": "Kata Sandi Utama" @@ -5240,7 +5256,7 @@ "message": "What would you like to generate?" }, "passwordType": { - "message": "Password type" + "message": "Jenis kata sandi" }, "regenerateUsername": { "message": "Regenerate username" @@ -5249,7 +5265,7 @@ "message": "Generate username" }, "usernameType": { - "message": "Username type" + "message": "Jenis nama pengguna" }, "plusAddressedEmail": { "message": "Plus addressed email", @@ -5265,11 +5281,11 @@ "message": "Use your domain's configured catch-all inbox." }, "random": { - "message": "Random", + "message": "Acak", "description": "Generates domain-based username using random letters" }, "randomWord": { - "message": "Random word" + "message": "Kata acak" }, "service": { "message": "Service" @@ -5315,7 +5331,7 @@ "message": "Self-hosted sponsorships synced." }, "billingManagedByProvider": { - "message": "Managed by $PROVIDER$", + "message": "Dikelola oleh $PROVIDER$", "placeholders": { "provider": { "content": "$1", @@ -5434,7 +5450,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "Panjang input tidak boleh melebihi $COUNT$ karakter.", "placeholders": { "count": { "content": "$1", @@ -5458,7 +5474,7 @@ "message": "On" }, "members": { - "message": "Members" + "message": "Anggota" }, "reporting": { "message": "Reporting" @@ -5473,7 +5489,7 @@ "message": "Logging in as" }, "notYou": { - "message": "Not you?" + "message": "Bukan Anda?" }, "pickAnAvatarColor": { "message": "Pick an avatar color" @@ -5488,16 +5504,16 @@ "message": "Bright Blue" }, "green": { - "message": "Green" + "message": "Hijau" }, "orange": { - "message": "Orange" + "message": "Oranye" }, "lavender": { "message": "Lavender" }, "yellow": { - "message": "Yellow" + "message": "Kuning" }, "indigo": { "message": "Indigo" @@ -5509,13 +5525,13 @@ "message": "Salmon" }, "pink": { - "message": "Pink" + "message": "Merah Muda" }, "customColor": { "message": "Custom Color" }, "multiSelectPlaceholder": { - "message": "-- Type to Filter --" + "message": "-- Ketik untuk menyaring --" }, "multiSelectLoading": { "message": "Retrieving options..." diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index b7a534388c4..fe3ff012077 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1609,7 +1609,7 @@ "message": "Resoconto sulle password esposte" }, "exposedPasswordsReportDesc": { - "message": "Le password esposte sono password che sono state scoperte in violazioni di dati note che sono state rilasciate pubblicamente o vendute sul dark web dagli hacker." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "Trovate password esposte" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Estensioni" }, diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 9b0190e3c78..b772fa97016 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -1609,7 +1609,7 @@ "message": "流出パスワード調査" }, "exposedPasswordsReportDesc": { - "message": "Exposed passwords are passwords have been uncovered in known data breaches that were released publicly or sold on the dark web by hackers." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "流出したパスワードが見つかりました" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "$PRICE$ /年でプレミアム会員になるか、下記のプランなら $FAMILYPLANUSERCOUNT$ 人分のプレミアムアカウントを取得して家族での共有を無制限にできるようになります:", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden ファミリープラン" + }, "addons": { "message": "アドオン" }, @@ -4783,7 +4799,7 @@ "message": "Assertion consumer service (ACS) URL" }, "spNameIdFormat": { - "message": "Name ID format" + "message": "名前IDの形式" }, "spOutboundSigningAlgorithm": { "message": "Outbound signing algorithm" @@ -5562,13 +5578,13 @@ "message": "シークレットを削除" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "シークレットを関連付けるプロジェクトを選択します。これらのプロジェクトにアクセスできる組織ユーザーのみがシークレットを表示できます。" }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "プロジェクトを入力または選択" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "プロジェクトを入力または選択" }, "project": { "message": "プロジェクト" @@ -5700,7 +5716,7 @@ } }, "deleteProjectInputLabel": { - "message": "Type \"$CONFIRM$\" to continue", + "message": "「$CONFIRM$」と入力して続行", "placeholders": { "confirm": { "content": "$1", @@ -5709,7 +5725,7 @@ } }, "deleteProjectConfirmMessage": { - "message": "Delete $PROJECT$", + "message": "$PROJECT$ を削除", "placeholders": { "project": { "content": "$1", @@ -5718,7 +5734,7 @@ } }, "deleteProjectsConfirmMessage": { - "message": "Delete $COUNT$ Projects", + "message": "$COUNT$ 個のプロジェクトを削除", "placeholders": { "count": { "content": "$1", @@ -5727,13 +5743,13 @@ } }, "deleteProjectsDialogMessage": { - "message": "Deleting projects is permanent and irreversible." + "message": "プロジェクトを削除すると元に戻せません。" }, "projectsNoItemsTitle": { - "message": "No projects to display" + "message": "表示するプロジェクトがありません" }, "projectsNoItemsMessage": { - "message": "Add a new project to get started organizing secrets." + "message": "秘密を始めるために新しいプロジェクトを追加する。" }, "smConfirmationRequired": { "message": "Confirmation required" @@ -5745,13 +5761,13 @@ "message": "Secret sent to trash" }, "searchProjects": { - "message": "Search Projects" + "message": "プロジェクトを検索" }, "accessTokens": { - "message": "Access tokens" + "message": "アクセストークン" }, "createAccessToken": { - "message": "Create access token" + "message": "アクセストークンを作成" }, "expires": { "message": "Expires" @@ -5760,13 +5776,13 @@ "message": "Can Read" }, "accessTokensNoItemsTitle": { - "message": "No access tokens to show" + "message": "表示するアクセス トークンはありません" }, "accessTokensNoItemsDesc": { "message": "To get started, create an access token" }, "downloadAccessToken": { - "message": "Download or copy before closing." + "message": "閉じる前にダウンロードまたはコピーする。" }, "expiresOnAccessToken": { "message": "Expires on:" @@ -5775,10 +5791,10 @@ "message": "Access tokens are not stored and cannot be retrieved" }, "copyToken": { - "message": "Copy token" + "message": "トークンをコピー" }, "accessToken": { - "message": "Access token" + "message": "アクセストークン" }, "accessTokenExpirationRequired": { "message": "Expiration date required" @@ -5790,10 +5806,10 @@ "message": "Permissions management is unavailable for beta." }, "revokeAccessToken": { - "message": "Revoke Access Token" + "message": "アクセストークンの取り消し" }, "submenu": { - "message": "Submenu" + "message": "サブメニュー" }, "from": { "message": "From" diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 048495a67be..721d86a092a 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 048495a67be..721d86a092a 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index d63c525441d..18871b6935a 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "ಆಡ್ಸಾನ್ಗಳು" }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 6ecdfbd9c3f..c06269e2521 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "부가 기능" }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index c0b515685dc..9fdad726205 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -1582,7 +1582,7 @@ "message": "Nevienam glabātavas vienumam nav nedrošu URI." }, "inactive2faReport": { - "message": "Neizmantoto 2FA pārskats" + "message": "Pierakstīšanās vienumi bez divpakāpju pārbaudes" }, "inactive2faReportDesc": { "message": "Divfaktoru autentificēšanās (2FA) ir būtisks drošības iestatījums, kas palīdz nodrošināt kontus. Ja tīmekļa vietne to piedāvā, vienmēr vajadzētu iespējot divfaktoru autentificēšanos." @@ -1690,13 +1690,13 @@ } }, "dataBreachReport": { - "message": "Datu pārākumu pārskats" + "message": "Datu noplūde" }, "breachDesc": { - "message": "\"Pārkāpums\" ir notikums, kurā urķi ir nelikumīgi piekļuvuši tās datiem un tad tos ir publicējuši. Jāpārskata datu veidi, kas ir ietekmēti (e-pasta adreses, paroles, bankas kartes utt.) un jāveic atbilstošas darbības, piemēram, paroļu nomaiņa." + "message": "Kontu noplūde var atklāt personisko informāciju. Noplūdušos kontus var padarīt drošus, iespējojot divpakāpju pārbaudi vai izveidojot spēcīgāku paroli." }, "breachCheckUsernameEmail": { - "message": "Jāpārbauda jebkurš lietotājvārds vai e-pasta adrese, kas tiek izmantota." + "message": "Pārbaudīt jebkuru lietotājvārdu vai e-pasta adresi, kas tiek izmantota." }, "checkBreaches": { "message": "Pārbaudīt datu pārkāpumus" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Iegūsti premium tikai par $PRICE$ gadā vai iegūsti premium kontus lietotājiem $FAMILYPLANUSERCOUNT$ un neierobežotu ģimenes koplietošanu ar ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plāns." + }, "addons": { "message": "Papildinājumi" }, @@ -5419,7 +5435,7 @@ "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "inputRequired": { - "message": "Ievade ir nepieciešama." + "message": "Jāievada vērtība." }, "inputEmail": { "message": "Ievadītā vērtība nav e-pasta adrese." @@ -5476,43 +5492,43 @@ "message": "Tas neesi Tu?" }, "pickAnAvatarColor": { - "message": "Pick an avatar color" + "message": "Izvēlies avatāra krāsu" }, "customizeAvatar": { - "message": "Customize avatar" + "message": "Pielāgot avatāru" }, "avatarUpdated": { - "message": "Avatar updated" + "message": "Avatārs atjaunināts" }, "brightBlue": { - "message": "Bright Blue" + "message": "Spilgti Zils" }, "green": { - "message": "Green" + "message": "Zaļš" }, "orange": { - "message": "Orange" + "message": "Oranžs" }, "lavender": { - "message": "Lavender" + "message": "Lavanda" }, "yellow": { - "message": "Yellow" + "message": "Dzeltens" }, "indigo": { "message": "Indigo" }, "teal": { - "message": "Teal" + "message": "Zilganzaļa" }, "salmon": { - "message": "Salmon" + "message": "Lasis" }, "pink": { - "message": "Pink" + "message": "Rozā" }, "customColor": { - "message": "Custom Color" + "message": "Pielāgota Krāsa" }, "multiSelectPlaceholder": { "message": "-- Rakstīt, lai atlasītu --" @@ -5562,13 +5578,13 @@ "message": "Izdzēst noslēpumus" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Atlasi projektus, ar kuriem noslēpums tiks saistīts. Noslēpumu varēs redzēt tikai tie organizācijas lietotāji, kuriem ir piekļuve šiem projektiem." }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "Ieraksti vai atlasi Projekti" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "Ieraksti vai atlasi Projekts" }, "project": { "message": "Projekts" diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index b2c3c43546b..42e18183a30 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -1609,7 +1609,7 @@ "message": "Exposed Passwords Report" }, "exposedPasswordsReportDesc": { - "message": "Exposed passwords are passwords that have been uncovered in known data breaches that were released publicly or sold on the dark web by hackers." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "Exposed Passwords Found" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index d2084c7ccc0..ef71edce918 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Utvidelser" }, diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json new file mode 100644 index 00000000000..721d86a092a --- /dev/null +++ b/apps/web/src/locales/ne/messages.json @@ -0,0 +1,5892 @@ +{ + "pageTitle": { + "message": "$APP_NAME$ Web Vault", + "description": "The title of the website in the browser window.", + "placeholders": { + "app_name": { + "content": "$1", + "example": "Bitwarden" + } + } + }, + "whatTypeOfItem": { + "message": "What type of item is this?" + }, + "name": { + "message": "Name" + }, + "uri": { + "message": "URI" + }, + "uriPosition": { + "message": "URI $POSITION$", + "description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.", + "placeholders": { + "position": { + "content": "$1", + "example": "2" + } + } + }, + "newUri": { + "message": "New URI" + }, + "username": { + "message": "Username" + }, + "password": { + "message": "Password" + }, + "newPassword": { + "message": "New password" + }, + "passphrase": { + "message": "Passphrase" + }, + "notes": { + "message": "Notes" + }, + "customFields": { + "message": "Custom fields" + }, + "cardholderName": { + "message": "Cardholder name" + }, + "number": { + "message": "Number" + }, + "brand": { + "message": "Brand" + }, + "expiration": { + "message": "Expiration" + }, + "securityCode": { + "message": "Security code (CVV)" + }, + "identityName": { + "message": "Identity name" + }, + "company": { + "message": "Company" + }, + "ssn": { + "message": "Social Security number" + }, + "passportNumber": { + "message": "Passport number" + }, + "licenseNumber": { + "message": "License number" + }, + "email": { + "message": "Email" + }, + "phone": { + "message": "Phone" + }, + "january": { + "message": "January" + }, + "february": { + "message": "February" + }, + "march": { + "message": "March" + }, + "april": { + "message": "April" + }, + "may": { + "message": "May" + }, + "june": { + "message": "June" + }, + "july": { + "message": "July" + }, + "august": { + "message": "August" + }, + "september": { + "message": "September" + }, + "october": { + "message": "October" + }, + "november": { + "message": "November" + }, + "december": { + "message": "December" + }, + "title": { + "message": "Title" + }, + "mr": { + "message": "Mr" + }, + "mrs": { + "message": "Mrs" + }, + "ms": { + "message": "Ms" + }, + "dr": { + "message": "Dr" + }, + "expirationMonth": { + "message": "Expiration month" + }, + "expirationYear": { + "message": "Expiration year" + }, + "authenticatorKeyTotp": { + "message": "Authenticator key (TOTP)" + }, + "folder": { + "message": "Folder" + }, + "newCustomField": { + "message": "New custom field" + }, + "value": { + "message": "Value" + }, + "dragToSort": { + "message": "Drag to sort" + }, + "cfTypeText": { + "message": "Text" + }, + "cfTypeHidden": { + "message": "Hidden" + }, + "cfTypeBoolean": { + "message": "Boolean" + }, + "cfTypeLinked": { + "message": "Linked", + "description": "This describes a field that is 'linked' (related) to another field." + }, + "remove": { + "message": "Remove" + }, + "unassigned": { + "message": "Unassigned" + }, + "noneFolder": { + "message": "No folder", + "description": "This is the folder for uncategorized items" + }, + "addFolder": { + "message": "Add folder" + }, + "editFolder": { + "message": "Edit folder" + }, + "baseDomain": { + "message": "Base domain", + "description": "Domain name. Example: website.com" + }, + "domainName": { + "message": "Domain name", + "description": "Domain name. Example: website.com" + }, + "host": { + "message": "Host", + "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." + }, + "exact": { + "message": "Exact" + }, + "startsWith": { + "message": "Starts with" + }, + "regEx": { + "message": "Regular expression", + "description": "A programming term, also known as 'RegEx'." + }, + "matchDetection": { + "message": "Match detection", + "description": "URI match detection for auto-fill." + }, + "defaultMatchDetection": { + "message": "Default match detection", + "description": "Default URI match detection for auto-fill." + }, + "never": { + "message": "Never" + }, + "toggleVisibility": { + "message": "Toggle visibility" + }, + "toggleCollapse": { + "message": "Toggle collapse", + "description": "Toggling an expand/collapse state." + }, + "generatePassword": { + "message": "Generate password" + }, + "checkPassword": { + "message": "Check if password has been exposed." + }, + "passwordExposed": { + "message": "This password has been exposed $VALUE$ time(s) in data breaches. You should change it.", + "placeholders": { + "value": { + "content": "$1", + "example": "2" + } + } + }, + "passwordSafe": { + "message": "This password was not found in any known data breaches. It should be safe to use." + }, + "save": { + "message": "Save" + }, + "cancel": { + "message": "Cancel" + }, + "canceled": { + "message": "Canceled" + }, + "close": { + "message": "Close" + }, + "delete": { + "message": "Delete" + }, + "favorite": { + "message": "Favorite" + }, + "unfavorite": { + "message": "Unfavorite" + }, + "edit": { + "message": "Edit" + }, + "searchCollection": { + "message": "Search collection" + }, + "searchFolder": { + "message": "Search folder" + }, + "searchFavorites": { + "message": "Search favorites" + }, + "searchType": { + "message": "Search type", + "description": "Search item type" + }, + "searchVault": { + "message": "Search vault" + }, + "allItems": { + "message": "All items" + }, + "favorites": { + "message": "Favorites" + }, + "types": { + "message": "Types" + }, + "typeLogin": { + "message": "Login" + }, + "typeCard": { + "message": "Card" + }, + "typeIdentity": { + "message": "Identity" + }, + "typeSecureNote": { + "message": "Secure note" + }, + "typeLoginPlural": { + "message": "Logins" + }, + "typeCardPlural": { + "message": "Cards" + }, + "typeIdentityPlural": { + "message": "Identities" + }, + "typeSecureNotePlural": { + "message": "Secure notes" + }, + "folders": { + "message": "Folders" + }, + "collections": { + "message": "Collections" + }, + "firstName": { + "message": "First name" + }, + "middleName": { + "message": "Middle name" + }, + "lastName": { + "message": "Last name" + }, + "fullName": { + "message": "Full name" + }, + "address1": { + "message": "Address 1" + }, + "address2": { + "message": "Address 2" + }, + "address3": { + "message": "Address 3" + }, + "cityTown": { + "message": "City / Town" + }, + "stateProvince": { + "message": "State / Province" + }, + "zipPostalCode": { + "message": "Zip / Postal code" + }, + "country": { + "message": "Country" + }, + "shared": { + "message": "Shared" + }, + "attachments": { + "message": "Attachments" + }, + "select": { + "message": "Select" + }, + "addItem": { + "message": "Add item" + }, + "editItem": { + "message": "Edit item" + }, + "viewItem": { + "message": "View item" + }, + "ex": { + "message": "ex.", + "description": "Short abbreviation for 'example'." + }, + "other": { + "message": "Other" + }, + "share": { + "message": "Share" + }, + "moveToOrganization": { + "message": "Move to organization" + }, + "valueCopied": { + "message": "$VALUE$ copied", + "description": "Value has been copied to the clipboard.", + "placeholders": { + "value": { + "content": "$1", + "example": "Password" + } + } + }, + "copyValue": { + "message": "Copy value", + "description": "Copy value to clipboard" + }, + "copyPassword": { + "message": "Copy password", + "description": "Copy password to clipboard" + }, + "copyUsername": { + "message": "Copy username", + "description": "Copy username to clipboard" + }, + "copyNumber": { + "message": "Copy number", + "description": "Copy credit card number" + }, + "copySecurityCode": { + "message": "Copy security code", + "description": "Copy credit card security code (CVV)" + }, + "copyUri": { + "message": "Copy URI", + "description": "Copy URI to clipboard" + }, + "me": { + "message": "Me" + }, + "myVault": { + "message": "My vault" + }, + "allVaults": { + "message": "All vaults" + }, + "vault": { + "message": "Vault" + }, + "vaults": { + "message": "Vaults" + }, + "vaultItems": { + "message": "Vault items" + }, + "moveSelectedToOrg": { + "message": "Move selected to organization" + }, + "deleteSelected": { + "message": "Delete selected" + }, + "moveSelected": { + "message": "Move selected" + }, + "selectAll": { + "message": "Select all" + }, + "unselectAll": { + "message": "Unselect all" + }, + "launch": { + "message": "Launch" + }, + "newAttachment": { + "message": "Add new attachment" + }, + "deletedAttachment": { + "message": "Deleted attachment" + }, + "deleteAttachmentConfirmation": { + "message": "Are you sure you want to delete this attachment?" + }, + "attachmentSaved": { + "message": "Attachment saved" + }, + "file": { + "message": "File" + }, + "selectFile": { + "message": "Select a file." + }, + "maxFileSize": { + "message": "Maximum file size is 500 MB." + }, + "updateKey": { + "message": "You cannot use this feature until you update your encryption key." + }, + "addedItem": { + "message": "Item added" + }, + "editedItem": { + "message": "Item saved" + }, + "movedItemToOrg": { + "message": "$ITEMNAME$ moved to $ORGNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + }, + "orgname": { + "content": "$2", + "example": "Company Name" + } + } + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "deleteItem": { + "message": "Delete item" + }, + "deleteFolder": { + "message": "Delete folder" + }, + "deleteAttachment": { + "message": "Delete attachment" + }, + "deleteItemConfirmation": { + "message": "Do you really want to send to the trash?" + }, + "deletedItem": { + "message": "Item sent to trash" + }, + "deletedItems": { + "message": "Items sent to trash" + }, + "movedItems": { + "message": "Items moved" + }, + "overwritePasswordConfirmation": { + "message": "Are you sure you want to overwrite the current password?" + }, + "editedFolder": { + "message": "Folder saved" + }, + "addedFolder": { + "message": "Folder added" + }, + "deleteFolderConfirmation": { + "message": "Are you sure you want to delete this folder?" + }, + "deletedFolder": { + "message": "Folder deleted" + }, + "loggedOut": { + "message": "Logged out" + }, + "loginExpired": { + "message": "Your login session has expired." + }, + "logOutConfirmation": { + "message": "Are you sure you want to log out?" + }, + "logOut": { + "message": "Log out" + }, + "ok": { + "message": "Ok" + }, + "yes": { + "message": "Yes" + }, + "no": { + "message": "No" + }, + "loginOrCreateNewAccount": { + "message": "Log in or create a new account to access your secure vault." + }, + "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" + }, + "newAroundHere": { + "message": "New around here?" + }, + "startTrial": { + "message": "Start trial" + }, + "logIn": { + "message": "Log in" + }, + "logInInitiated": { + "message": "Log in initiated" + }, + "submit": { + "message": "Submit" + }, + "emailAddressDesc": { + "message": "You'll use your email address to log in." + }, + "yourName": { + "message": "Your name" + }, + "yourNameDesc": { + "message": "What should we call you?" + }, + "masterPass": { + "message": "Master password" + }, + "masterPassDesc": { + "message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it." + }, + "masterPassImportant": { + "message": "Master passwords cannot be recovered if you forget it!" + }, + "masterPassHintDesc": { + "message": "A master password hint can help you remember your password if you forget it." + }, + "reTypeMasterPass": { + "message": "Re-type master password" + }, + "masterPassHint": { + "message": "Master password hint (optional)" + }, + "masterPassHintLabel": { + "message": "Master password hint" + }, + "settings": { + "message": "Settings" + }, + "passwordHint": { + "message": "Password hint" + }, + "enterEmailToGetHint": { + "message": "Enter your account email address to receive your master password hint." + }, + "getMasterPasswordHint": { + "message": "Get master password hint" + }, + "emailRequired": { + "message": "Email address is required." + }, + "invalidEmail": { + "message": "Invalid email address." + }, + "masterPasswordRequired": { + "message": "Master password is required." + }, + "confirmMasterPasswordRequired": { + "message": "Master password retype is required." + }, + "masterPasswordMinlength": { + "message": "Master password must be at least 8 characters long." + }, + "masterPassDoesntMatch": { + "message": "Master password confirmation does not match." + }, + "newAccountCreated": { + "message": "Your new account has been created! You may now log in." + }, + "trialAccountCreated": { + "message": "Account created successfully." + }, + "masterPassSent": { + "message": "We've sent you an email with your master password hint." + }, + "unexpectedError": { + "message": "An unexpected error has occurred." + }, + "emailAddress": { + "message": "Email address" + }, + "yourVaultIsLocked": { + "message": "Your vault is locked. Verify your master password to continue." + }, + "unlock": { + "message": "Unlock" + }, + "loggedInAsEmailOn": { + "message": "Logged in as $EMAIL$ on $HOSTNAME$.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "hostname": { + "content": "$2", + "example": "bitwarden.com" + } + } + }, + "invalidMasterPassword": { + "message": "Invalid master password" + }, + "invalidFilePassword": { + "message": "Invalid file password, please use the password you entered when you created the export file." + }, + "lockNow": { + "message": "Lock now" + }, + "noItemsInList": { + "message": "There are no items to list." + }, + "noCollectionsInList": { + "message": "There are no collections to list." + }, + "noGroupsInList": { + "message": "There are no groups to list." + }, + "noUsersInList": { + "message": "There are no users to list." + }, + "noEventsInList": { + "message": "There are no events to list." + }, + "newOrganization": { + "message": "New organization" + }, + "noOrganizationsList": { + "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." + }, + "notificationSentDevice": { + "message": "A notification has been sent to your device." + }, + "versionNumber": { + "message": "Version $VERSION_NUMBER$", + "placeholders": { + "version_number": { + "content": "$1", + "example": "1.2.3" + } + } + }, + "enterVerificationCodeApp": { + "message": "Enter the 6 digit verification code from your authenticator app." + }, + "enterVerificationCodeEmail": { + "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", + "placeholders": { + "email": { + "content": "$1", + "example": "example@gmail.com" + } + } + }, + "verificationCodeEmailSent": { + "message": "Verification email sent to $EMAIL$.", + "placeholders": { + "email": { + "content": "$1", + "example": "example@gmail.com" + } + } + }, + "rememberMe": { + "message": "Remember me" + }, + "sendVerificationCodeEmailAgain": { + "message": "Send verification code email again" + }, + "useAnotherTwoStepMethod": { + "message": "Use another two-step login method" + }, + "insertYubiKey": { + "message": "Insert your YubiKey into your computer's USB port, then touch its button." + }, + "insertU2f": { + "message": "Insert your security key into your computer's USB port. If it has a button, touch it." + }, + "loginUnavailable": { + "message": "Login unavailable" + }, + "noTwoStepProviders": { + "message": "This account has two-step login set up, however, none of the configured two-step providers are supported by this web browser." + }, + "noTwoStepProviders2": { + "message": "Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported across web browsers (such as an authenticator app)." + }, + "twoStepOptions": { + "message": "Two-step login options" + }, + "recoveryCodeDesc": { + "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." + }, + "recoveryCodeTitle": { + "message": "Recovery code" + }, + "authenticatorAppTitle": { + "message": "Authenticator app" + }, + "authenticatorAppDesc": { + "message": "Use an authenticator app (such as Authy or Google Authenticator) to generate time-based verification codes.", + "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." + }, + "yubiKeyTitle": { + "message": "YubiKey OTP security key" + }, + "yubiKeyDesc": { + "message": "Use a YubiKey to access your account. Works with YubiKey 4 series, 5 series, and NEO devices." + }, + "duoDesc": { + "message": "Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.", + "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." + }, + "duoOrganizationDesc": { + "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", + "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." + }, + "u2fDesc": { + "message": "Use any FIDO U2F compatible security key to access your account." + }, + "u2fTitle": { + "message": "FIDO U2F security key" + }, + "webAuthnTitle": { + "message": "FIDO2 WebAuthn" + }, + "webAuthnDesc": { + "message": "Use any WebAuthn compatible security key to access your account." + }, + "webAuthnMigrated": { + "message": "(Migrated from FIDO)" + }, + "emailTitle": { + "message": "Email" + }, + "emailDesc": { + "message": "Verification codes will be emailed to you." + }, + "continue": { + "message": "Continue" + }, + "organization": { + "message": "Organization" + }, + "organizations": { + "message": "Organizations" + }, + "moveToOrgDesc": { + "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + }, + "moveManyToOrgDesc": { + "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." + }, + "collectionsDesc": { + "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." + }, + "deleteSelectedItemsDesc": { + "message": "You have selected $COUNT$ item(s) to delete. Are you sure you want to delete all of these items?", + "placeholders": { + "count": { + "content": "$1", + "example": "150" + } + } + }, + "moveSelectedItemsDesc": { + "message": "Choose a folder that you would like to move the $COUNT$ selected item(s) to.", + "placeholders": { + "count": { + "content": "$1", + "example": "150" + } + } + }, + "moveSelectedItemsCountDesc": { + "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", + "placeholders": { + "count": { + "content": "$1", + "example": "10" + }, + "moveable_count": { + "content": "$2", + "example": "8" + }, + "nonmoveable_count": { + "content": "$3", + "example": "2" + } + } + }, + "verificationCodeTotp": { + "message": "Verification code (TOTP)" + }, + "copyVerificationCode": { + "message": "Copy verification code" + }, + "warning": { + "message": "Warning" + }, + "confirmVaultExport": { + "message": "Confirm vault export" + }, + "exportWarningDesc": { + "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + }, + "encExportKeyWarningDesc": { + "message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." + }, + "encExportAccountWarningDesc": { + "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + }, + "export": { + "message": "Export" + }, + "exportVault": { + "message": "Export vault" + }, + "fileFormat": { + "message": "File format" + }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "confirmMasterPassword": { + "message": "Confirm master password" + }, + "confirmFormat": { + "message": "Confirm format" + }, + "filePassword": { + "message": "File password" + }, + "confirmFilePassword": { + "message": "Confirm file password" + }, + "accountRestrictedOptionDescription": { + "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + }, + "passwordProtectedOptionDescription": { + "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + }, + "exportTypeHeading": { + "message": "Export type" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "passwordProtected": { + "message": "Password protected" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, + "confirmVaultImport": { + "message": "Confirm vault import" + }, + "confirmVaultImportDesc": { + "message": "This file is password-protected. Please enter the file password to import data." + }, + "exportSuccess": { + "message": "Vault data exported" + }, + "passwordGenerator": { + "message": "Password generator" + }, + "minComplexityScore": { + "message": "Minimum complexity score" + }, + "minNumbers": { + "message": "Minimum numbers" + }, + "minSpecial": { + "message": "Minimum special", + "description": "Minimum special characters" + }, + "ambiguous": { + "message": "Avoid ambiguous characters" + }, + "regeneratePassword": { + "message": "Regenerate password" + }, + "length": { + "message": "Length" + }, + "uppercase": { + "message": "Uppercase (A-Z)", + "description": "Include uppercase letters in the password generator." + }, + "lowercase": { + "message": "Lowercase (a-z)", + "description": "Include lowercase letters in the password generator." + }, + "numbers": { + "message": "Numbers (0-9)" + }, + "specialCharacters": { + "message": "Special characters (!@#$%^&*)" + }, + "numWords": { + "message": "Number of words" + }, + "wordSeparator": { + "message": "Word separator" + }, + "capitalize": { + "message": "Capitalize", + "description": "Make the first letter of a word uppercase." + }, + "includeNumber": { + "message": "Include number" + }, + "passwordHistory": { + "message": "Password history" + }, + "noPasswordsInList": { + "message": "There are no passwords to list." + }, + "clear": { + "message": "Clear", + "description": "To clear something out. Example: To clear browser history." + }, + "accountUpdated": { + "message": "Account saved" + }, + "changeEmail": { + "message": "Change email" + }, + "changeEmailTwoFactorWarning": { + "message": "Proceeding will change your account email address. It will not change the email address used for two-step login authentication. You can change this email address in the two-step login settings." + }, + "newEmail": { + "message": "New email" + }, + "code": { + "message": "Code" + }, + "changeEmailDesc": { + "message": "We have emailed a verification code to $EMAIL$. Please check your email for this code and enter it below to finalize the email address change.", + "placeholders": { + "email": { + "content": "$1", + "example": "john.smith@example.com" + } + } + }, + "loggedOutWarning": { + "message": "Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + }, + "emailChanged": { + "message": "Email saved" + }, + "logBackIn": { + "message": "Please log back in." + }, + "logBackInOthersToo": { + "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." + }, + "changeMasterPassword": { + "message": "Change master password" + }, + "masterPasswordChanged": { + "message": "Master password saved" + }, + "currentMasterPass": { + "message": "Current master password" + }, + "newMasterPass": { + "message": "New master password" + }, + "confirmNewMasterPass": { + "message": "Confirm new master password" + }, + "encKeySettings": { + "message": "Encryption key settings" + }, + "kdfAlgorithm": { + "message": "KDF algorithm" + }, + "kdfIterations": { + "message": "KDF iterations" + }, + "kdfIterationsDesc": { + "message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker. We recommend a value of $VALUE$ or more.", + "placeholders": { + "value": { + "content": "$1", + "example": "100,000" + } + } + }, + "kdfIterationsWarning": { + "message": "Setting your KDF iterations too high could result in poor performance when logging into (and unlocking) Bitwarden on devices with slower CPUs. We recommend that you increase the value in increments of $INCREMENT$ and then test all of your devices.", + "placeholders": { + "increment": { + "content": "$1", + "example": "50,000" + } + } + }, + "changeKdf": { + "message": "Change KDF" + }, + "encKeySettingsChanged": { + "message": "Encryption key settings saved" + }, + "dangerZone": { + "message": "Danger zone" + }, + "dangerZoneDesc": { + "message": "Careful, these actions are not reversible!" + }, + "deauthorizeSessions": { + "message": "Deauthorize sessions" + }, + "deauthorizeSessionsDesc": { + "message": "Concerned your account is logged in on another device? Proceed below to deauthorize all computers or devices that you have previously used. This security step is recommended if you previously used a public computer or accidentally saved your password on a device that isn't yours. This step will also clear all previously remembered two-step login sessions." + }, + "deauthorizeSessionsWarning": { + "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." + }, + "sessionsDeauthorized": { + "message": "All sessions deauthorized" + }, + "purgeVault": { + "message": "Purge vault" + }, + "purgedOrganizationVault": { + "message": "Purged organization vault." + }, + "vaultAccessedByProvider": { + "message": "Vault accessed by Provider." + }, + "purgeVaultDesc": { + "message": "Proceed below to delete all items and folders in your vault. Items that belong to an organization that you share with will not be deleted." + }, + "purgeOrgVaultDesc": { + "message": "Proceed below to delete all items in the organization's vault." + }, + "purgeVaultWarning": { + "message": "Purging your vault is permanent. It cannot be undone." + }, + "vaultPurged": { + "message": "Vault purged." + }, + "deleteAccount": { + "message": "Delete account" + }, + "deleteAccountDesc": { + "message": "Proceed below to delete your account and all vault data." + }, + "deleteAccountWarning": { + "message": "Deleting your account is permanent. It cannot be undone." + }, + "accountDeleted": { + "message": "Account deleted" + }, + "accountDeletedDesc": { + "message": "Your account has been closed and all associated data has been deleted." + }, + "myAccount": { + "message": "My account" + }, + "tools": { + "message": "Tools" + }, + "importData": { + "message": "Import data" + }, + "importError": { + "message": "Import error" + }, + "importErrorDesc": { + "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + }, + "importSuccess": { + "message": "Data successfully imported" + }, + "importWarning": { + "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "importFormatError": { + "message": "Data is not formatted correctly. Please check your import file and try again." + }, + "importNothingError": { + "message": "Nothing was imported." + }, + "importEncKeyError": { + "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + }, + "selectFormat": { + "message": "Select the format of the import file" + }, + "selectImportFile": { + "message": "Select the import file" + }, + "chooseFile": { + "message": "Choose File" + }, + "noFileChosen": { + "message": "No file chosen" + }, + "orCopyPasteFileContents": { + "message": "or copy/paste the import file contents" + }, + "instructionsFor": { + "message": "$NAME$ Instructions", + "description": "The title for the import tool instructions.", + "placeholders": { + "name": { + "content": "$1", + "example": "LastPass (csv)" + } + } + }, + "options": { + "message": "Options" + }, + "preferences": { + "message": "Preferences" + }, + "preferencesDesc": { + "message": "Customize your web vault experience." + }, + "preferencesUpdated": { + "message": "Preferences saved" + }, + "language": { + "message": "Language" + }, + "languageDesc": { + "message": "Change the language used by the web vault." + }, + "enableFavicon": { + "message": "Show website icons" + }, + "faviconDesc": { + "message": "Show a recognizable image next to each login." + }, + "enableFullWidth": { + "message": "Display full width layout", + "description": "Allows scaling the web vault UI's width" + }, + "enableFullWidthDesc": { + "message": "Allow the web vault to expand the full width of the browser window." + }, + "default": { + "message": "Default" + }, + "domainRules": { + "message": "Domain rules" + }, + "domainRulesDesc": { + "message": "If you have the same login across multiple different website domains, you can mark the website as \"equivalent\". \"Global\" domains are ones already created for you by Bitwarden." + }, + "globalEqDomains": { + "message": "Global equivalent domains" + }, + "customEqDomains": { + "message": "Custom equivalent domains" + }, + "exclude": { + "message": "Exclude" + }, + "include": { + "message": "Include" + }, + "customize": { + "message": "Customize" + }, + "newCustomDomain": { + "message": "New custom domain" + }, + "newCustomDomainDesc": { + "message": "Enter a list of domains separated by commas. Only \"base\" domains are allowed. Do not enter subdomains. For example, enter \"google.com\" instead of \"www.google.com\". You can also enter \"androidapp://package.name\" to associate an android app with other website domains." + }, + "customDomainX": { + "message": "Custom domain $INDEX$", + "placeholders": { + "index": { + "content": "$1", + "example": "2" + } + } + }, + "domainsUpdated": { + "message": "Domains saved" + }, + "twoStepLogin": { + "message": "Two-step login" + }, + "twoStepLoginEnforcement": { + "message": "Two-step Login Enforcement" + }, + "twoStepLoginDesc": { + "message": "Secure your account by requiring an additional step when logging in." + }, + "twoStepLoginOrganizationDescStart": { + "message": "Enforce Bitwarden Two-step Login options for members by using the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" + }, + "twoStepLoginPolicy": { + "message": "Two-step Login Policy" + }, + "twoStepLoginOrganizationDuoDesc": { + "message": "To enforce Two-step Login through Duo, use the options below." + }, + "twoStepLoginOrganizationSsoDesc": { + "message": "If you have setup SSO or plan to, Two-step Login may already be enforced through your Identity Provider." + }, + "twoStepLoginRecoveryWarning": { + "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." + }, + "viewRecoveryCode": { + "message": "View recovery code" + }, + "providers": { + "message": "Providers", + "description": "Two-step login providers such as YubiKey, Duo, Authenticator apps, Email, etc." + }, + "enable": { + "message": "Turn on" + }, + "enabled": { + "message": "Turned on" + }, + "restoreAccess": { + "message": "Restore access" + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "premiumMembership": { + "message": "Premium membership" + }, + "premiumRequired": { + "message": "Premium required" + }, + "premiumRequiredDesc": { + "message": "A Premium membership is required to use this feature." + }, + "youHavePremiumAccess": { + "message": "You have Premium access" + }, + "alreadyPremiumFromOrg": { + "message": "You already have access to Premium features because of an organization you are a member of." + }, + "manage": { + "message": "Manage" + }, + "disable": { + "message": "Turn off" + }, + "revokeAccess": { + "message": "Revoke access" + }, + "twoStepLoginProviderEnabled": { + "message": "This two-step login provider is active on your account." + }, + "twoStepLoginAuthDesc": { + "message": "Enter your master password to modify two-step login settings." + }, + "twoStepAuthenticatorDesc": { + "message": "Follow these steps to set up two-step login with an authenticator app:" + }, + "twoStepAuthenticatorDownloadApp": { + "message": "Download a two-step authenticator app" + }, + "twoStepAuthenticatorNeedApp": { + "message": "Need a two-step authenticator app? Download one of the following" + }, + "iosDevices": { + "message": "iOS devices" + }, + "androidDevices": { + "message": "Android devices" + }, + "windowsDevices": { + "message": "Windows devices" + }, + "twoStepAuthenticatorAppsRecommended": { + "message": "These apps are recommended, however, other authenticator apps will also work." + }, + "twoStepAuthenticatorScanCode": { + "message": "Scan this QR code with your authenticator app" + }, + "key": { + "message": "Key" + }, + "twoStepAuthenticatorEnterCode": { + "message": "Enter the resulting 6 digit verification code from the app" + }, + "twoStepAuthenticatorReaddDesc": { + "message": "In case you need to add it to another device, below is the QR code (or key) required by your authenticator app." + }, + "twoStepDisableDesc": { + "message": "Are you sure you want to turn off this two-step login provider?" + }, + "twoStepDisabled": { + "message": "Two-step login provider turned off." + }, + "twoFactorYubikeyAdd": { + "message": "Add a new YubiKey to your account" + }, + "twoFactorYubikeyPlugIn": { + "message": "Plug the YubiKey into your computer's USB port." + }, + "twoFactorYubikeySelectKey": { + "message": "Select the first empty YubiKey input field below." + }, + "twoFactorYubikeyTouchButton": { + "message": "Touch the YubiKey's button." + }, + "twoFactorYubikeySaveForm": { + "message": "Save the form." + }, + "twoFactorYubikeyWarning": { + "message": "Due to platform limitations, YubiKeys cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when YubiKeys cannot be used. Supported platforms:" + }, + "twoFactorYubikeySupportUsb": { + "message": "Web vault, desktop application, CLI, and all browser extensions on a device with a USB port that can accept your YubiKey." + }, + "twoFactorYubikeySupportMobile": { + "message": "Mobile apps on a device with NFC capabilities or a data port that can accept your YubiKey." + }, + "yubikeyX": { + "message": "YubiKey $INDEX$", + "placeholders": { + "index": { + "content": "$1", + "example": "2" + } + } + }, + "u2fkeyX": { + "message": "U2F Key $INDEX$", + "placeholders": { + "index": { + "content": "$1", + "example": "2" + } + } + }, + "webAuthnkeyX": { + "message": "WebAuthn Key $INDEX$", + "placeholders": { + "index": { + "content": "$1", + "example": "2" + } + } + }, + "nfcSupport": { + "message": "NFC Support" + }, + "twoFactorYubikeySupportsNfc": { + "message": "One of my keys supports NFC." + }, + "twoFactorYubikeySupportsNfcDesc": { + "message": "If one of your YubiKeys supports NFC (such as a YubiKey NEO), you will be prompted on mobile devices whenever NFC availability is detected." + }, + "yubikeysUpdated": { + "message": "YubiKeys updated" + }, + "disableAllKeys": { + "message": "Deactivate all keys" + }, + "twoFactorDuoDesc": { + "message": "Enter the Bitwarden application information from your Duo Admin panel." + }, + "twoFactorDuoIntegrationKey": { + "message": "Integration key" + }, + "twoFactorDuoSecretKey": { + "message": "Secret key" + }, + "twoFactorDuoApiHostname": { + "message": "API hostname" + }, + "twoFactorEmailDesc": { + "message": "Follow these steps to set up two-step login with email:" + }, + "twoFactorEmailEnterEmail": { + "message": "Enter the email that you wish to receive verification codes" + }, + "twoFactorEmailEnterCode": { + "message": "Enter the resulting 6 digit verification code from the email" + }, + "sendEmail": { + "message": "Send email" + }, + "twoFactorU2fAdd": { + "message": "Add a FIDO U2F security key to your account" + }, + "removeU2fConfirmation": { + "message": "Are you sure you want to remove this security key?" + }, + "twoFactorWebAuthnAdd": { + "message": "Add a WebAuthn security key to your account" + }, + "readKey": { + "message": "Read key" + }, + "keyCompromised": { + "message": "Key is compromised." + }, + "twoFactorU2fGiveName": { + "message": "Give the security key a friendly name to identify it." + }, + "twoFactorU2fPlugInReadKey": { + "message": "Plug the security key into your computer's USB port and click the \"Read Key\" button." + }, + "twoFactorU2fTouchButton": { + "message": "If the security key has a button, touch it." + }, + "twoFactorU2fSaveForm": { + "message": "Save the form." + }, + "twoFactorU2fWarning": { + "message": "Due to platform limitations, FIDO U2F cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when FIDO U2F cannot be used. Supported platforms:" + }, + "twoFactorU2fSupportWeb": { + "message": "Web vault and browser extensions on a desktop/laptop with a U2F supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + }, + "twoFactorU2fWaiting": { + "message": "Waiting for you to touch the button on your security key" + }, + "twoFactorU2fClickSave": { + "message": "Use the \"Save\" button below to activate this security key for two-step login." + }, + "twoFactorU2fProblemReadingTryAgain": { + "message": "There was a problem reading the security key. Try again." + }, + "twoFactorWebAuthnWarning": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" + }, + "twoFactorWebAuthnSupportWeb": { + "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + }, + "twoFactorRecoveryYourCode": { + "message": "Your Bitwarden two-step login recovery code" + }, + "twoFactorRecoveryNoCode": { + "message": "You have not set up any two-step login providers yet. After you have set up a two-step login provider you can check back here for your recovery code." + }, + "printCode": { + "message": "Print code", + "description": "Print 2FA recovery code" + }, + "reports": { + "message": "Reports" + }, + "reportsDesc": { + "message": "Identify and close security gaps in your online accounts by clicking the reports below.", + "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." + }, + "orgsReportsDesc": { + "message": "Identify and close security gaps in your organization's accounts by clicking the reports below.", + "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization Vault." + }, + "unsecuredWebsitesReport": { + "message": "Unsecure websites" + }, + "unsecuredWebsitesReportDesc": { + "message": "URLs that start with http:// don’t use the best available encryption. Change the login URIs for these accounts to https:// for safer browsing." + }, + "unsecuredWebsitesFound": { + "message": "Unsecured websites found" + }, + "unsecuredWebsitesFoundDesc": { + "message": "We found $COUNT$ items in your vault with unsecured URIs. You should change their URI scheme to https:// if the website allows it.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noUnsecuredWebsites": { + "message": "No items in your vault have unsecured URIs." + }, + "inactive2faReport": { + "message": "Inactive two-step login" + }, + "inactive2faReportDesc": { + "message": "Two-step login adds a layer of protection to your accounts. Set up two-step login using Bitwarden authenticator for these accounts or use an alternative method." + }, + "inactive2faFound": { + "message": "Logins without two-step login found" + }, + "inactive2faFoundDesc": { + "message": "We found $COUNT$ website(s) in your vault that may not be configured with two-step login (according to 2fa.directory). To further protect these accounts, you should set up two-step login.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noInactive2fa": { + "message": "No websites were found in your vault with a missing two-step login configuration." + }, + "instructions": { + "message": "Instructions" + }, + "exposedPasswordsReport": { + "message": "Exposed passwords" + }, + "exposedPasswordsReportDesc": { + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." + }, + "exposedPasswordsFound": { + "message": "Exposed passwords found" + }, + "exposedPasswordsFoundDesc": { + "message": "We found $COUNT$ items in your vault that have passwords that were exposed in known data breaches. You should change them to use a new password.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noExposedPasswords": { + "message": "No items in your vault have passwords that have been exposed in known data breaches." + }, + "checkExposedPasswords": { + "message": "Check exposed passwords" + }, + "exposedXTimes": { + "message": "Exposed $COUNT$ time(s)", + "placeholders": { + "count": { + "content": "$1", + "example": "52" + } + } + }, + "weakPasswordsReport": { + "message": "Weak passwords" + }, + "weakPasswordsReportDesc": { + "message": "Weak passwords can be easily guessed by attackers. Change these passwords to strong ones using the password generator." + }, + "weakPasswordsFound": { + "message": "Weak passwords found" + }, + "weakPasswordsFoundDesc": { + "message": "We found $COUNT$ items in your vault with passwords that are not strong. You should update them to use stronger passwords.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noWeakPasswords": { + "message": "No items in your vault have weak passwords." + }, + "reusedPasswordsReport": { + "message": "Reused passwords" + }, + "reusedPasswordsReportDesc": { + "message": "Reusing passwords makes it easier for attackers to break into multiple accounts. Change these passwords so that each is unique." + }, + "reusedPasswordsFound": { + "message": "Reused passwords found" + }, + "reusedPasswordsFoundDesc": { + "message": "We found $COUNT$ passwords that are being reused in your vault. You should change them to a unique value.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "noReusedPasswords": { + "message": "No logins in your vault have passwords that are being reused." + }, + "reusedXTimes": { + "message": "Reused $COUNT$ times", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "dataBreachReport": { + "message": "Data breach" + }, + "breachDesc": { + "message": "Breached accounts can expose your personal information. Secure breached accounts by enabling 2FA or creating a stronger password." + }, + "breachCheckUsernameEmail": { + "message": "Check any usernames or email addresses that you use." + }, + "checkBreaches": { + "message": "Check breaches" + }, + "breachUsernameNotFound": { + "message": "$USERNAME$ was not found in any known data breaches.", + "placeholders": { + "username": { + "content": "$1", + "example": "user@example.com" + } + } + }, + "goodNews": { + "message": "Good news", + "description": "ex. Good News, No Breached Accounts Found!" + }, + "breachUsernameFound": { + "message": "$USERNAME$ was found in $COUNT$ different data breaches online.", + "placeholders": { + "username": { + "content": "$1", + "example": "user@example.com" + }, + "count": { + "content": "$2", + "example": "7" + } + } + }, + "breachFound": { + "message": "Breached accounts found" + }, + "compromisedData": { + "message": "Compromised data" + }, + "website": { + "message": "Website" + }, + "affectedUsers": { + "message": "Affected users" + }, + "breachOccurred": { + "message": "Breach occurred" + }, + "breachReported": { + "message": "Breach reported" + }, + "reportError": { + "message": "An error occurred trying to load the report. Try again" + }, + "billing": { + "message": "Billing" + }, + "billingPlanLabel": { + "message": "Billing plan" + }, + "paymentType": { + "message": "Payment type" + }, + "accountCredit": { + "message": "Account credit", + "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." + }, + "accountBalance": { + "message": "Account balance", + "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." + }, + "addCredit": { + "message": "Add credit", + "description": "Add more credit to your account's balance." + }, + "amount": { + "message": "Amount", + "description": "Dollar amount, or quantity." + }, + "creditDelayed": { + "message": "Added credit will appear on your account after the payment has been fully processed. Some payment methods are delayed and can take longer to process than others." + }, + "makeSureEnoughCredit": { + "message": "Please make sure that your account has enough credit available for this purchase. If your account does not have enough credit available, your default payment method on file will be used for the difference. You can add credit to your account from the Billing page." + }, + "creditAppliedDesc": { + "message": "Your account's credit can be used to make purchases. Any available credit will be automatically applied towards invoices generated for this account." + }, + "goPremium": { + "message": "Go Premium", + "description": "Another way of saying \"Get a Premium membership\"" + }, + "premiumUpdated": { + "message": "You've upgraded to Premium." + }, + "premiumUpgradeUnlockFeatures": { + "message": "Upgrade your account to a Premium membership and unlock some great additional features." + }, + "premiumSignUpStorage": { + "message": "1 GB encrypted storage for file attachments." + }, + "premiumSignUpTwoStep": { + "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + }, + "premiumSignUpEmergency": { + "message": "Emergency access" + }, + "premiumSignUpReports": { + "message": "Password hygiene, account health, and data breach reports to keep your vault safe." + }, + "premiumSignUpTotp": { + "message": "TOTP verification code (2FA) generator for logins in your vault." + }, + "premiumSignUpSupport": { + "message": "Priority customer support." + }, + "premiumSignUpFuture": { + "message": "All future Premium features. More coming soon!" + }, + "premiumPrice": { + "message": "All for just $PRICE$ /year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, + "addons": { + "message": "Addons" + }, + "premiumAccess": { + "message": "Premium access" + }, + "premiumAccessDesc": { + "message": "You can add Premium access to all members of your organization for $PRICE$ /$INTERVAL$.", + "placeholders": { + "price": { + "content": "$1", + "example": "$3.33" + }, + "interval": { + "content": "$2", + "example": "'month' or 'year'" + } + } + }, + "additionalStorageGb": { + "message": "Additional storage (GB)" + }, + "additionalStorageGbDesc": { + "message": "# of additional GB" + }, + "additionalStorageIntervalDesc": { + "message": "Your plan comes with $SIZE$ of encrypted file storage. You can add additional storage for $PRICE$ per GB /$INTERVAL$.", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + }, + "price": { + "content": "$2", + "example": "$4.00" + }, + "interval": { + "content": "$3", + "example": "'month' or 'year'" + } + } + }, + "summary": { + "message": "Summary" + }, + "total": { + "message": "Total" + }, + "year": { + "message": "year" + }, + "yr": { + "message": "yr" + }, + "month": { + "message": "month" + }, + "monthAbbr": { + "message": "mo.", + "description": "Short abbreviation for 'month'" + }, + "paymentChargedAnnually": { + "message": "Your payment method will be charged immediately and then on a recurring basis each year. You may cancel at any time." + }, + "paymentCharged": { + "message": "Your payment method will be charged immediately and then on a recurring basis each $INTERVAL$. You may cancel at any time.", + "placeholders": { + "interval": { + "content": "$1", + "example": "month or year" + } + } + }, + "paymentChargedWithTrial": { + "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." + }, + "paymentInformation": { + "message": "Payment information" + }, + "billingInformation": { + "message": "Billing information" + }, + "billingTrialSubLabel": { + "message": "Your payment method will not be charged during the 7 day free trial." + }, + "creditCard": { + "message": "Credit card" + }, + "paypalClickSubmit": { + "message": "Select the PayPal button to log into your PayPal account, then click the Submit button below to continue." + }, + "cancelSubscription": { + "message": "Cancel subscription" + }, + "subscriptionCanceled": { + "message": "The subscription has been canceled." + }, + "pendingCancellation": { + "message": "Pending cancellation" + }, + "subscriptionPendingCanceled": { + "message": "The subscription has been marked for cancellation at the end of the current billing period." + }, + "reinstateSubscription": { + "message": "Reinstate subscription" + }, + "reinstateConfirmation": { + "message": "Are you sure you want to remove the pending cancellation request and reinstate your subscription?" + }, + "reinstated": { + "message": "The subscription has been reinstated." + }, + "cancelConfirmation": { + "message": "Are you sure you want to cancel? You will lose access to all of this subscription's features at the end of this billing cycle." + }, + "canceledSubscription": { + "message": "Subscription canceled" + }, + "neverExpires": { + "message": "Never expires" + }, + "status": { + "message": "Status" + }, + "nextCharge": { + "message": "Next charge" + }, + "details": { + "message": "Details" + }, + "downloadLicense": { + "message": "Download license" + }, + "updateLicense": { + "message": "Update license" + }, + "updatedLicense": { + "message": "Updated license" + }, + "manageSubscription": { + "message": "Manage subscription" + }, + "storage": { + "message": "Storage" + }, + "addStorage": { + "message": "Add storage" + }, + "removeStorage": { + "message": "Remove storage" + }, + "subscriptionStorage": { + "message": "Your subscription has a total of $MAX_STORAGE$ GB of encrypted file storage. You are currently using $USED_STORAGE$.", + "placeholders": { + "max_storage": { + "content": "$1", + "example": "4" + }, + "used_storage": { + "content": "$2", + "example": "65 MB" + } + } + }, + "paymentMethod": { + "message": "Payment method" + }, + "noPaymentMethod": { + "message": "No payment method on file." + }, + "addPaymentMethod": { + "message": "Add payment method" + }, + "changePaymentMethod": { + "message": "Change payment method" + }, + "invoices": { + "message": "Invoices" + }, + "noInvoices": { + "message": "No invoices." + }, + "paid": { + "message": "Paid", + "description": "Past tense status of an invoice. ex. Paid or unpaid." + }, + "unpaid": { + "message": "Unpaid", + "description": "Past tense status of an invoice. ex. Paid or unpaid." + }, + "transactions": { + "message": "Transactions", + "description": "Payment/credit transactions." + }, + "noTransactions": { + "message": "No transactions." + }, + "chargeNoun": { + "message": "Charge", + "description": "Noun. A charge from a payment method." + }, + "refundNoun": { + "message": "Refund", + "description": "Noun. A refunded payment that was charged." + }, + "chargesStatement": { + "message": "Any charges will appear on your statement as $STATEMENT_NAME$.", + "placeholders": { + "statement_name": { + "content": "$1", + "example": "BITWARDEN" + } + } + }, + "gbStorageAdd": { + "message": "GB of storage to add" + }, + "gbStorageRemove": { + "message": "GB of storage to remove" + }, + "storageAddNote": { + "message": "Adding storage will result in adjustments to your billing totals and immediately charge your payment method on file. The first charge will be prorated for the remainder of the current billing cycle." + }, + "storageRemoveNote": { + "message": "Removing storage will result in adjustments to your billing totals that will be prorated as credits toward your next billing charge." + }, + "adjustedStorage": { + "message": "Adjusted $AMOUNT$ GB of storage.", + "placeholders": { + "amount": { + "content": "$1", + "example": "5" + } + } + }, + "contactSupport": { + "message": "Contact customer support" + }, + "updatedPaymentMethod": { + "message": "Updated payment method." + }, + "purchasePremium": { + "message": "Purchase Premium" + }, + "licenseFile": { + "message": "License file" + }, + "licenseFileDesc": { + "message": "Your license file will be named something like $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_premium_license.json" + } + } + }, + "uploadLicenseFilePremium": { + "message": "To upgrade your account to a Premium membership you need to upload a valid license file." + }, + "uploadLicenseFileOrg": { + "message": "To create an on-premises hosted organization you need to upload a valid license file." + }, + "accountEmailMustBeVerified": { + "message": "Your account's email address must be verified." + }, + "newOrganizationDesc": { + "message": "Organizations allow you to share parts of your vault with others as well as manage related users for a specific entity such as a family, small team, or large company." + }, + "generalInformation": { + "message": "General information" + }, + "organizationName": { + "message": "Organization name" + }, + "accountOwnedBusiness": { + "message": "This account is owned by a business." + }, + "billingEmail": { + "message": "Billing email" + }, + "businessName": { + "message": "Business name" + }, + "chooseYourPlan": { + "message": "Choose your plan" + }, + "users": { + "message": "Users" + }, + "userSeats": { + "message": "User seats" + }, + "additionalUserSeats": { + "message": "Additional user seats" + }, + "userSeatsDesc": { + "message": "# of user seats" + }, + "userSeatsAdditionalDesc": { + "message": "Your plan comes with $BASE_SEATS$ user seats. You can add additional users for $SEAT_PRICE$ per user /month.", + "placeholders": { + "base_seats": { + "content": "$1", + "example": "5" + }, + "seat_price": { + "content": "$2", + "example": "$2.00" + } + } + }, + "userSeatsHowManyDesc": { + "message": "How many user seats do you need? You can also add additional seats later if needed." + }, + "planNameFree": { + "message": "Free", + "description": "Free as in 'free beer'." + }, + "planDescFree": { + "message": "For testing or personal users to share with $COUNT$ other user.", + "placeholders": { + "count": { + "content": "$1", + "example": "1" + } + } + }, + "planNameFamilies": { + "message": "Families" + }, + "planDescFamilies": { + "message": "For personal use, to share with family & friends." + }, + "planNameTeams": { + "message": "Teams" + }, + "planDescTeams": { + "message": "For businesses and other team organizations." + }, + "planNameEnterprise": { + "message": "Enterprise" + }, + "planDescEnterprise": { + "message": "For businesses and other large organizations." + }, + "freeForever": { + "message": "Free forever" + }, + "includesXUsers": { + "message": "includes $COUNT$ users", + "placeholders": { + "count": { + "content": "$1", + "example": "5" + } + } + }, + "additionalUsers": { + "message": "Additional users" + }, + "costPerUser": { + "message": "$COST$ per user", + "placeholders": { + "cost": { + "content": "$1", + "example": "$3" + } + } + }, + "limitedUsers": { + "message": "Limited to $COUNT$ users (including you)", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "limitedCollections": { + "message": "Limited to $COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "addShareLimitedUsers": { + "message": "Add and share with up to $COUNT$ users", + "placeholders": { + "count": { + "content": "$1", + "example": "5" + } + } + }, + "addShareUnlimitedUsers": { + "message": "Add and share with unlimited users" + }, + "createUnlimitedCollections": { + "message": "Create unlimited collections" + }, + "gbEncryptedFileStorage": { + "message": "$SIZE$ encrypted file storage", + "placeholders": { + "size": { + "content": "$1", + "example": "1 GB" + } + } + }, + "onPremHostingOptional": { + "message": "On-premise hosting (optional)" + }, + "usersGetPremium": { + "message": "Users get access to Premium features" + }, + "controlAccessWithGroups": { + "message": "Control user access with groups" + }, + "syncUsersFromDirectory": { + "message": "Sync your users and groups from a directory" + }, + "trackAuditLogs": { + "message": "Track user actions with audit logs" + }, + "enforce2faDuo": { + "message": "Enforce 2FA with Duo" + }, + "priorityCustomerSupport": { + "message": "Priority customer support" + }, + "xDayFreeTrial": { + "message": "$COUNT$ day free trial, cancel anytime", + "placeholders": { + "count": { + "content": "$1", + "example": "7" + } + } + }, + "trialThankYou": { + "message": "Thanks for signing up for Bitwarden for $PLAN$!", + "placeholders": { + "plan": { + "content": "$1", + "example": "Teams" + } + } + }, + "trialPaidInfoMessage": { + "message": "Your $PLAN$ 7 day free trial will be converted to a paid subscription after 7 days.", + "placeholders": { + "plan": { + "content": "$1", + "example": "Teams" + } + } + }, + "trialConfirmationEmail": { + "message": "We've sent a confirmation email to your team's billing email at " + }, + "monthly": { + "message": "Monthly" + }, + "annually": { + "message": "Annually" + }, + "annual": { + "message": "Annual" + }, + "basePrice": { + "message": "Base price" + }, + "organizationCreated": { + "message": "Organization created" + }, + "organizationReadyToGo": { + "message": "Your new organization is ready to go!" + }, + "organizationUpgraded": { + "message": "Organization upgraded" + }, + "leave": { + "message": "Leave" + }, + "leaveOrganizationConfirmation": { + "message": "Are you sure you want to leave this organization?" + }, + "leftOrganization": { + "message": "You left the organization" + }, + "defaultCollection": { + "message": "Default collection" + }, + "getHelp": { + "message": "Get help" + }, + "getApps": { + "message": "Get the apps" + }, + "loggedInAs": { + "message": "Logged in as" + }, + "eventLogs": { + "message": "Event logs" + }, + "people": { + "message": "People" + }, + "policies": { + "message": "Policies" + }, + "singleSignOn": { + "message": "Single sign-on" + }, + "editPolicy": { + "message": "Edit policy" + }, + "groups": { + "message": "Groups" + }, + "newGroup": { + "message": "New group" + }, + "addGroup": { + "message": "Add group" + }, + "editGroup": { + "message": "Edit group" + }, + "deleteGroupConfirmation": { + "message": "Are you sure you want to delete this group?" + }, + "removeUserConfirmation": { + "message": "Are you sure you want to remove this user?" + }, + "removeOrgUserConfirmation": { + "message": "When a member is removed, they no longer have access to organization data and this action is irreversible. To add the member back to the organization, they must be invited and onboarded again." + }, + "revokeUserConfirmation": { + "message": "When a member is revoked, they no longer have access to organization data. To quickly restore member access, go to the Revoked tab." + }, + "removeUserConfirmationKeyConnector": { + "message": "Warning! This user requires Key Connector to manage their encryption. Removing this user from your organization will permanently deactivate their account. This action cannot be undone. Do you want to proceed?" + }, + "externalId": { + "message": "External id" + }, + "externalIdDesc": { + "message": "The external id can be used as a reference or to link this resource to an external system such as a user directory." + }, + "accessControl": { + "message": "Access control" + }, + "groupAccessAllItems": { + "message": "This group can access and modify all items." + }, + "groupAccessSelectedCollections": { + "message": "This group can access only the selected collections." + }, + "readOnly": { + "message": "Read only" + }, + "newCollection": { + "message": "New collection" + }, + "addCollection": { + "message": "Add collection" + }, + "editCollection": { + "message": "Edit collection" + }, + "deleteCollectionConfirmation": { + "message": "Are you sure you want to delete this collection?" + }, + "editUser": { + "message": "Edit user" + }, + "inviteUser": { + "message": "Invite user" + }, + "inviteUserDesc": { + "message": "Invite a new user to your organization by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + }, + "inviteMultipleEmailDesc": { + "message": "You can invite up to $COUNT$ users at a time by comma separating a list of email addresses.", + "placeholders": { + "count": { + "content": "$1", + "example": "20" + } + } + }, + "userUsingTwoStep": { + "message": "This user is using two-step login to protect their account." + }, + "userAccessAllItems": { + "message": "This user can access and modify all items." + }, + "userAccessSelectedCollections": { + "message": "This user can access only the selected collections." + }, + "search": { + "message": "Search" + }, + "invited": { + "message": "Invited" + }, + "accepted": { + "message": "Accepted" + }, + "confirmed": { + "message": "Confirmed" + }, + "clientOwnerEmail": { + "message": "Client owner email" + }, + "owner": { + "message": "Owner" + }, + "ownerDesc": { + "message": "Manage all aspects of your organization, including billing and subscriptions" + }, + "clientOwnerDesc": { + "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." + }, + "admin": { + "message": "Admin" + }, + "adminDesc": { + "message": "Manage organization access, all collections, members, reporting, and security settings" + }, + "user": { + "message": "User" + }, + "userDesc": { + "message": "Access and add items to assigned collections" + }, + "manager": { + "message": "Manager" + }, + "managerDesc": { + "message": "Create, delete, and manage access in assigned collections" + }, + "all": { + "message": "All" + }, + "refresh": { + "message": "Refresh" + }, + "timestamp": { + "message": "Timestamp" + }, + "event": { + "message": "Event" + }, + "unknown": { + "message": "Unknown" + }, + "loadMore": { + "message": "Load more" + }, + "mobile": { + "message": "Mobile", + "description": "Mobile app" + }, + "extension": { + "message": "Extension", + "description": "Browser extension/addon" + }, + "desktop": { + "message": "Desktop", + "description": "Desktop app" + }, + "webVault": { + "message": "Web vault" + }, + "loggedIn": { + "message": "Logged in" + }, + "changedPassword": { + "message": "Changed account password" + }, + "enabledUpdated2fa": { + "message": "Two-step login saved" + }, + "disabled2fa": { + "message": "Two-step login turned off" + }, + "recovered2fa": { + "message": "Recovered account from two-step login." + }, + "failedLogin": { + "message": "Login attempt failed with incorrect password." + }, + "failedLogin2fa": { + "message": "Login attempt failed with incorrect two-step login." + }, + "exportedVault": { + "message": "Vault exported" + }, + "exportedOrganizationVault": { + "message": "Exported organization vault." + }, + "editedOrgSettings": { + "message": "Edited organization settings." + }, + "createdItemId": { + "message": "Created item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "editedItemId": { + "message": "Edited item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "deletedItemId": { + "message": "Sent item $ID$ to trash.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "movedItemIdToOrg": { + "message": "Moved item $ID$ to an organization.", + "placeholders": { + "id": { + "content": "$1", + "example": "'Google'" + } + } + }, + "viewAllLoginOptions": { + "message": "View all log in options" + }, + "viewedItemId": { + "message": "Viewed item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "viewedPasswordItemId": { + "message": "Viewed password for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "viewedHiddenFieldItemId": { + "message": "Viewed hidden field for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "viewedCardNumberItemId": { + "message": "Viewed Card Number for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Unique ID" + } + } + }, + "viewedSecurityCodeItemId": { + "message": "Viewed security code for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "copiedPasswordItemId": { + "message": "Copied password for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "copiedHiddenFieldItemId": { + "message": "Copied hidden field for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "copiedSecurityCodeItemId": { + "message": "Copied security code for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "autofilledItemId": { + "message": "Auto-filled item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "createdCollectionId": { + "message": "Created collection $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Server Passwords" + } + } + }, + "editedCollectionId": { + "message": "Edited collection $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Server Passwords" + } + } + }, + "deletedCollectionId": { + "message": "Deleted collection $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Server Passwords" + } + } + }, + "editedPolicyId": { + "message": "Edited policy $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Master Password" + } + } + }, + "createdGroupId": { + "message": "Created group $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Developers" + } + } + }, + "editedGroupId": { + "message": "Edited group $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Developers" + } + } + }, + "deletedGroupId": { + "message": "Deleted group $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Developers" + } + } + }, + "removedUserId": { + "message": "Removed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "removeUserIdAccess": { + "message": "Remove $ID$ access", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "revokedUserId": { + "message": "Revoked organization access for $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "restoredUserId": { + "message": "Restored organization access for $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "revokeUserId": { + "message": "Revoke $ID$ access", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "createdAttachmentForItem": { + "message": "Created attachment for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "deletedAttachmentForItem": { + "message": "Deleted attachment for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "editedCollectionsForItem": { + "message": "Edited collections for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "invitedUserId": { + "message": "Invited user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "confirmedUserId": { + "message": "Confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "editedUserId": { + "message": "Edited user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "editedGroupsForUser": { + "message": "Edited groups for user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "unlinkedSsoUser": { + "message": "Unlinked SSO for user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "createdOrganizationId": { + "message": "Created organization $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "addedOrganizationId": { + "message": "Added organization $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "removedOrganizationId": { + "message": "Removed organization $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "accessedClientVault": { + "message": "Accessed $ID$ organization vault.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "device": { + "message": "Device" + }, + "view": { + "message": "View" + }, + "invalidDateRange": { + "message": "Invalid date range." + }, + "errorOccurred": { + "message": "An error has occurred." + }, + "userAccess": { + "message": "User access" + }, + "userType": { + "message": "User type" + }, + "groupAccess": { + "message": "Group access" + }, + "groupAccessUserDesc": { + "message": "Edit the groups that this user belongs to." + }, + "invitedUsers": { + "message": "User(s) invited" + }, + "resendInvitation": { + "message": "Resend invitation" + }, + "resendEmail": { + "message": "Resend email" + }, + "hasBeenReinvited": { + "message": "$USER$ reinvited", + "placeholders": { + "user": { + "content": "$1", + "example": "John Smith" + } + } + }, + "confirm": { + "message": "Confirm" + }, + "confirmUser": { + "message": "Confirm user" + }, + "hasBeenConfirmed": { + "message": "$USER$ confirmed.", + "placeholders": { + "user": { + "content": "$1", + "example": "John Smith" + } + } + }, + "confirmUsers": { + "message": "Confirm users" + }, + "usersNeedConfirmed": { + "message": "You have users that have accepted their invitation, but still need to be confirmed. Users will not have access to the organization until they are confirmed." + }, + "startDate": { + "message": "Start date" + }, + "endDate": { + "message": "End date" + }, + "verifyEmail": { + "message": "Verify email" + }, + "verifyEmailDesc": { + "message": "Verify your account's email address to unlock access to all features." + }, + "verifyEmailFirst": { + "message": "Your account's email address first must be verified." + }, + "checkInboxForVerification": { + "message": "Check your email inbox for a verification link." + }, + "emailVerified": { + "message": "Account email verified" + }, + "emailVerifiedFailed": { + "message": "Unable to verify your email. Try sending a new verification email." + }, + "emailVerificationRequired": { + "message": "Email verification required" + }, + "emailVerificationRequiredDesc": { + "message": "You must verify your email to use this feature." + }, + "updateBrowser": { + "message": "Update browser" + }, + "updateBrowserDesc": { + "message": "You are using an unsupported web browser. The web vault may not function properly." + }, + "joinOrganization": { + "message": "Join organization" + }, + "joinOrganizationDesc": { + "message": "You've been invited to join the organization listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + }, + "inviteAccepted": { + "message": "Invitation accepted" + }, + "inviteAcceptedDesc": { + "message": "You can access this organization once an administrator confirms your membership. We'll send you an email when that happens." + }, + "inviteAcceptFailed": { + "message": "Unable to accept invitation. Ask an organization admin to send a new invitation." + }, + "inviteAcceptFailedShort": { + "message": "Unable to accept invitation. $DESCRIPTION$", + "placeholders": { + "description": { + "content": "$1", + "example": "You must set up 2FA on your user account before you can join this organization." + } + } + }, + "rememberEmail": { + "message": "Remember email" + }, + "recoverAccountTwoStepDesc": { + "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." + }, + "recoverAccountTwoStep": { + "message": "Recover account two-step login" + }, + "twoStepRecoverDisabled": { + "message": "Two-step login turned off on your account." + }, + "learnMore": { + "message": "Learn more" + }, + "deleteRecoverDesc": { + "message": "Enter your email address below to recover and delete your account." + }, + "deleteRecoverEmailSent": { + "message": "If your account exists, we've sent you an email with further instructions." + }, + "deleteRecoverConfirmDesc": { + "message": "You have requested to delete your Bitwarden account. Use the button below to confirm." + }, + "myOrganization": { + "message": "My organization" + }, + "organizationInfo": { + "message": "Organization info" + }, + "deleteOrganization": { + "message": "Delete organization" + }, + "deletingOrganizationContentWarning": { + "message": "Enter the master password to confirm deletion of $ORGANIZATION$ and all associated data. Vault data in $ORGANIZATION$ includes:", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "deletingOrganizationActiveUserAccountsWarning": { + "message": "User accounts will remain active after deletion but will no longer be associated to this organization." + }, + "deletingOrganizationIsPermanentWarning": { + "message": "Deleting $ORGANIZATION$ is permanent and irreversible.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "organizationDeleted": { + "message": "Organization deleted" + }, + "organizationDeletedDesc": { + "message": "The organization and all associated data has been deleted." + }, + "organizationUpdated": { + "message": "Organization saved" + }, + "taxInformation": { + "message": "Tax information" + }, + "taxInformationDesc": { + "message": "For customers within the US, ZIP code is required to satisfy sales tax requirements, for other countries you may optionally provide a tax identification number (VAT/GST) and/or address to appear on your invoices." + }, + "billingPlan": { + "message": "Plan", + "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." + }, + "changeBillingPlan": { + "message": "Upgrade plan", + "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." + }, + "changeBillingPlanUpgrade": { + "message": "Upgrade your account to another plan by providing the information below. Please ensure that you have an active payment method added to the account.", + "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." + }, + "invoiceNumber": { + "message": "Invoice #$NUMBER$", + "description": "ex. Invoice #79C66F0-0001", + "placeholders": { + "number": { + "content": "$1", + "example": "79C66F0-0001" + } + } + }, + "viewInvoice": { + "message": "View invoice" + }, + "downloadInvoice": { + "message": "Download invoice" + }, + "verifyBankAccount": { + "message": "Verify bank account" + }, + "verifyBankAccountDesc": { + "message": "We have made two micro-deposits to your bank account (it may take 1-2 business days to show up). Enter these amounts to verify the bank account." + }, + "verifyBankAccountInitialDesc": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make two micro-deposits within the next 1-2 business days. Enter these amounts on the organization's billing page to verify the bank account." + }, + "verifyBankAccountFailureWarning": { + "message": "Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "verifiedBankAccount": { + "message": "Bank account verified" + }, + "bankAccount": { + "message": "Bank account" + }, + "amountX": { + "message": "Amount $COUNT$", + "description": "Used in bank account verification of micro-deposits. Amount, as in a currency amount. Ex. Amount 1 is $2.00, Amount 2 is $1.50", + "placeholders": { + "count": { + "content": "$1", + "example": "1" + } + } + }, + "routingNumber": { + "message": "Routing number", + "description": "Bank account routing number" + }, + "accountNumber": { + "message": "Account number" + }, + "accountHolderName": { + "message": "Account holder name" + }, + "bankAccountType": { + "message": "Account type" + }, + "bankAccountTypeCompany": { + "message": "Company (business)" + }, + "bankAccountTypeIndividual": { + "message": "Individual (personal)" + }, + "enterInstallationId": { + "message": "Enter your installation id" + }, + "limitSubscriptionDesc": { + "message": "Set a seat limit for your subscription. Once this limit is reached, you will not be able to invite new members." + }, + "maxSeatLimit": { + "message": "Seat Limit (optional)", + "description": "Upper limit of seats to allow through autoscaling" + }, + "maxSeatCost": { + "message": "Max potential seat cost" + }, + "addSeats": { + "message": "Add seats", + "description": "Seat = User Seat" + }, + "removeSeats": { + "message": "Remove seats", + "description": "Seat = User Seat" + }, + "subscriptionDesc": { + "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited users exceed your subscription seats, you will immediately receive a prorated charge for the additional users." + }, + "subscriptionUserSeats": { + "message": "Your subscription allows for a total of $COUNT$ members.", + "placeholders": { + "count": { + "content": "$1", + "example": "50" + } + } + }, + "limitSubscription": { + "message": "Limit subscription (optional)" + }, + "subscriptionSeats": { + "message": "Subscription seats" + }, + "subscriptionUpdated": { + "message": "Subscription updated" + }, + "additionalOptions": { + "message": "Additional options" + }, + "additionalOptionsDesc": { + "message": "For additional help in managing your subscription, please contact Customer Support." + }, + "subscriptionUserSeatsUnlimitedAutoscale": { + "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members." + }, + "subscriptionUserSeatsLimitedAutoscale": { + "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members until your $MAX$ seat limit is reached.", + "placeholders": { + "max": { + "content": "$1", + "example": "50" + } + } + }, + "subscriptionFreePlan": { + "message": "You cannot invite more than $COUNT$ members without upgrading your plan.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "subscriptionFamiliesPlan": { + "message": "You cannot invite more than $COUNT$ members without upgrading your plan. Please contact Customer Support to upgrade.", + "placeholders": { + "count": { + "content": "$1", + "example": "6" + } + } + }, + "subscriptionSponsoredFamiliesPlan": { + "message": "Your subscription allows for a total of $COUNT$ members. Your plan is sponsored and billed to an external organization.", + "placeholders": { + "count": { + "content": "$1", + "example": "6" + } + } + }, + "subscriptionMaxReached": { + "message": "Adjustments to your subscription will result in prorated changes to your billing totals. You cannot invite more than $COUNT$ members without increasing your subscription seats.", + "placeholders": { + "count": { + "content": "$1", + "example": "50" + } + } + }, + "seatsToAdd": { + "message": "Seats to add" + }, + "seatsToRemove": { + "message": "Seats to remove" + }, + "seatsAddNote": { + "message": "Adding user seats will result in adjustments to your billing totals and immediately charge your payment method on file. The first charge will be prorated for the remainder of the current billing cycle." + }, + "seatsRemoveNote": { + "message": "Removing user seats will result in adjustments to your billing totals that will be prorated as credits toward your next billing charge." + }, + "adjustedSeats": { + "message": "Adjusted $AMOUNT$ user seats.", + "placeholders": { + "amount": { + "content": "$1", + "example": "15" + } + } + }, + "keyUpdated": { + "message": "Key updated" + }, + "updateKeyTitle": { + "message": "Update key" + }, + "updateEncryptionKey": { + "message": "Update encryption key" + }, + "updateEncryptionKeyShortDesc": { + "message": "You are currently using an outdated encryption scheme." + }, + "updateEncryptionKeyDesc": { + "message": "We've moved to larger encryption keys that provide better security and access to newer features. Updating your encryption key is quick and easy. Just type your master password below. This update will eventually become mandatory." + }, + "updateEncryptionKeyWarning": { + "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." + }, + "updateEncryptionKeyExportWarning": { + "message": "Any encrypted exports that you have saved will also become invalid." + }, + "subscription": { + "message": "Subscription" + }, + "loading": { + "message": "Loading" + }, + "upgrade": { + "message": "Upgrade" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." + }, + "createOrganizationStep1": { + "message": "Create organization: Step 1" + }, + "createOrganizationCreatePersonalAccount": { + "message": "Before creating your organization, you first need to create a free personal account." + }, + "refunded": { + "message": "Refunded" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "acceptPolicies": { + "message": "By checking this box you agree to the following:" + }, + "acceptPoliciesRequired": { + "message": "Terms of Service and Privacy Policy have not been acknowledged." + }, + "termsOfService": { + "message": "Terms of Service" + }, + "privacyPolicy": { + "message": "Privacy Policy" + }, + "filters": { + "message": "Filters" + }, + "vaultTimeout": { + "message": "Vault timeout" + }, + "vaultTimeoutDesc": { + "message": "Choose when your vault will take the vault timeout action." + }, + "oneMinute": { + "message": "1 minute" + }, + "fiveMinutes": { + "message": "5 minutes" + }, + "fifteenMinutes": { + "message": "15 minutes" + }, + "thirtyMinutes": { + "message": "30 minutes" + }, + "oneHour": { + "message": "1 hour" + }, + "fourHours": { + "message": "4 hours" + }, + "onRefresh": { + "message": "On browser refresh" + }, + "dateUpdated": { + "message": "Updated", + "description": "ex. Date this item was updated" + }, + "dateCreated": { + "message": "Created", + "description": "ex. Date this item was created" + }, + "datePasswordUpdated": { + "message": "Password updated", + "description": "ex. Date this password was updated" + }, + "organizationIsDisabled": { + "message": "Organization suspended" + }, + "disabledOrganizationFilterError": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, + "licenseIsExpired": { + "message": "License is expired." + }, + "updatedUsers": { + "message": "Updated users" + }, + "selected": { + "message": "Selected" + }, + "ownership": { + "message": "Ownership" + }, + "whoOwnsThisItem": { + "message": "Who owns this item?" + }, + "strong": { + "message": "Strong", + "description": "ex. A strong password. Scale: Very Weak -> Weak -> Good -> Strong" + }, + "good": { + "message": "Good", + "description": "ex. A good password. Scale: Very Weak -> Weak -> Good -> Strong" + }, + "weak": { + "message": "Weak", + "description": "ex. A weak password. Scale: Very Weak -> Weak -> Good -> Strong" + }, + "veryWeak": { + "message": "Very Weak", + "description": "ex. A very weak password. Scale: Very Weak -> Weak -> Good -> Strong" + }, + "weakMasterPassword": { + "message": "Weak master password" + }, + "weakMasterPasswordDesc": { + "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + }, + "rotateAccountEncKey": { + "message": "Also rotate my account's encryption key" + }, + "rotateEncKeyTitle": { + "message": "Rotate encryption key" + }, + "rotateEncKeyConfirmation": { + "message": "Are you sure you want to rotate your account's encryption key?" + }, + "attachmentsNeedFix": { + "message": "This item has old file attachments that need to be fixed." + }, + "attachmentFixDesc": { + "message": "This is an old file attachment the needs to be fixed. Click to learn more." + }, + "fix": { + "message": "Fix", + "description": "This is a verb. ex. 'Fix The Car'" + }, + "oldAttachmentsNeedFixDesc": { + "message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key." + }, + "yourAccountsFingerprint": { + "message": "Your account's fingerprint phrase", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "fingerprintEnsureIntegrityVerify": { + "message": "To ensure the integrity of your encryption keys, please verify the user's fingerprint phrase before continuing.", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "fingerprintMatchInfo": { + "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." + }, + "fingerprintPhraseHeader": { + "message": "Fingerprint phrase" + }, + "dontAskFingerprintAgain": { + "message": "Never prompt to verify fingerprint phrases for invited users (not recommended)", + "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": { + "message": "Free", + "description": "Free, as in 'Free beer'" + }, + "apiKey": { + "message": "API Key" + }, + "apiKeyDesc": { + "message": "Your API key can be used to authenticate to the Bitwarden public API." + }, + "apiKeyRotateDesc": { + "message": "Rotating the API key will invalidate the previous key. You can rotate your API key if you believe that the current key is no longer safe to use." + }, + "apiKeyWarning": { + "message": "Your API key has full access to the organization. It should be kept secret." + }, + "userApiKeyDesc": { + "message": "Your API key can be used to authenticate in the Bitwarden CLI." + }, + "userApiKeyWarning": { + "message": "Your API key is an alternative authentication mechanism. It should be kept secret." + }, + "oauth2ClientCredentials": { + "message": "OAuth 2.0 Client Credentials", + "description": "'OAuth 2.0' is a programming protocol. It should probably not be translated." + }, + "viewApiKey": { + "message": "View API key" + }, + "rotateApiKey": { + "message": "Rotate API key" + }, + "selectOneCollection": { + "message": "You must select at least one collection." + }, + "couldNotChargeCardPayInvoice": { + "message": "We were not able to charge your card. Please view and pay the unpaid invoice listed below." + }, + "inAppPurchase": { + "message": "In-app purchase" + }, + "cannotPerformInAppPurchase": { + "message": "You cannot perform this action while using an in-app purchase payment method." + }, + "manageSubscriptionFromStore": { + "message": "You must manage your subscription from the store where your in-app purchase was made." + }, + "minLength": { + "message": "Minimum length" + }, + "clone": { + "message": "Clone" + }, + "masterPassPolicyTitle": { + "message": "Master password requirements" + }, + "masterPassPolicyDesc": { + "message": "Set requirements for master password strength." + }, + "twoStepLoginPolicyTitle": { + "message": "Require two-step login" + }, + "twoStepLoginPolicyDesc": { + "message": "Require members to set up two-step login." + }, + "twoStepLoginPolicyWarning": { + "message": "Organization members who are not owners or admins and do not have two-step login setup for their account will be removed from the organization and will receive an email notifying them about the change." + }, + "twoStepLoginPolicyUserWarning": { + "message": "You are a member of an organization that requires two-step login to be setup on your user account. If you turn off all two-step login providers you will be automatically removed from these organizations." + }, + "passwordGeneratorPolicyDesc": { + "message": "Set requirements for password generator." + }, + "passwordGeneratorPolicyInEffect": { + "message": "One or more organization policies are affecting your generator settings." + }, + "masterPasswordPolicyInEffect": { + "message": "One or more organization policies require your master password to meet the following requirements:" + }, + "policyInEffectMinComplexity": { + "message": "Minimum complexity score of $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, + "policyInEffectMinLength": { + "message": "Minimum length of $LENGTH$", + "placeholders": { + "length": { + "content": "$1", + "example": "14" + } + } + }, + "policyInEffectUppercase": { + "message": "Contain one or more uppercase characters" + }, + "policyInEffectLowercase": { + "message": "Contain one or more lowercase characters" + }, + "policyInEffectNumbers": { + "message": "Contain one or more numbers" + }, + "policyInEffectSpecial": { + "message": "Contain one or more of the following special characters $CHARS$", + "placeholders": { + "chars": { + "content": "$1", + "example": "!@#$%^&*" + } + } + }, + "masterPasswordPolicyRequirementsNotMet": { + "message": "Your new master password does not meet the policy requirements." + }, + "minimumNumberOfWords": { + "message": "Minimum number of words" + }, + "defaultType": { + "message": "Default type" + }, + "userPreference": { + "message": "User preference" + }, + "vaultTimeoutAction": { + "message": "Vault timeout action" + }, + "vaultTimeoutActionLockDesc": { + "message": "Master password or other unlock method is required to access your vault again." + }, + "vaultTimeoutActionLogOutDesc": { + "message": "Re-authentication is required to access your vault again." + }, + "lock": { + "message": "Lock", + "description": "Verb form: to make secure or inaccesible by" + }, + "trash": { + "message": "Trash", + "description": "Noun: A special folder for holding deleted items that have not yet been permanently deleted" + }, + "searchTrash": { + "message": "Search trash" + }, + "permanentlyDelete": { + "message": "Permanently delete" + }, + "permanentlyDeleteSelected": { + "message": "Permanently delete selected" + }, + "permanentlyDeleteItem": { + "message": "Permanently delete item" + }, + "permanentlyDeleteItemConfirmation": { + "message": "Are you sure you want to permanently delete this item?" + }, + "permanentlyDeletedItem": { + "message": "Item permanently deleted" + }, + "permanentlyDeletedItems": { + "message": "Items permanently deleted" + }, + "permanentlyDeleteSelectedItemsDesc": { + "message": "You have selected $COUNT$ item(s) to permanently delete. Are you sure you want to permanently delete all of these items?", + "placeholders": { + "count": { + "content": "$1", + "example": "150" + } + } + }, + "permanentlyDeletedItemId": { + "message": "Item $ID$ permanently deleted", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "restore": { + "message": "Restore" + }, + "restoreSelected": { + "message": "Restore selected" + }, + "restoreItem": { + "message": "Restore item" + }, + "restoredItem": { + "message": "Item restored" + }, + "restoredItems": { + "message": "Items restored" + }, + "restoreItemConfirmation": { + "message": "Are you sure you want to restore this item?" + }, + "restoreItems": { + "message": "Restore items" + }, + "restoreSelectedItemsDesc": { + "message": "You have selected $COUNT$ item(s) to restore. Are you sure you want to restore all of these items?", + "placeholders": { + "count": { + "content": "$1", + "example": "150" + } + } + }, + "restoredItemId": { + "message": "Item $ID$ restored", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "vaultTimeoutLogOutConfirmation": { + "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" + }, + "vaultTimeoutLogOutConfirmationTitle": { + "message": "Timeout action confirmation" + }, + "hidePasswords": { + "message": "Hide passwords" + }, + "countryPostalCodeRequiredDesc": { + "message": "We require this information for calculating sales tax and financial reporting only." + }, + "includeVAT": { + "message": "Include VAT/GST Information (optional)" + }, + "taxIdNumber": { + "message": "VAT/GST Tax ID" + }, + "taxInfoUpdated": { + "message": "Tax information updated." + }, + "setMasterPassword": { + "message": "Set master password" + }, + "ssoCompleteRegistration": { + "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." + }, + "identifier": { + "message": "Identifier" + }, + "organizationIdentifier": { + "message": "Organization identifier" + }, + "ssoLogInWithOrgIdentifier": { + "message": "Log in using your organization's single sign-on portal. Please enter your organization's SSO identifier to begin." + }, + "enterpriseSingleSignOn": { + "message": "Enterprise single sign-on" + }, + "ssoHandOff": { + "message": "You may now close this tab and continue in the extension." + }, + "includeAllTeamsFeatures": { + "message": "All Teams features, plus:" + }, + "includeSsoAuthentication": { + "message": "SSO Authentication via SAML2.0 and OpenID Connect" + }, + "includeEnterprisePolicies": { + "message": "Enterprise policies" + }, + "ssoValidationFailed": { + "message": "SSO validation failed" + }, + "ssoIdentifierRequired": { + "message": "Organization SSO identifier is required." + }, + "ssoIdentifier": { + "message": "SSO identifier" + }, + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO." + }, + "unlinkSso": { + "message": "Unlink SSO" + }, + "unlinkSsoConfirmation": { + "message": "Are you sure you want to unlink SSO for this organization?" + }, + "linkSso": { + "message": "Link SSO" + }, + "singleOrg": { + "message": "Single organization" + }, + "singleOrgDesc": { + "message": "Restrict members from joining other organizations." + }, + "singleOrgBlockCreateMessage": { + "message": "Your current organization has a policy that does not allow you to join more than one organization. Please contact your organization admins or sign up from a different Bitwarden account." + }, + "singleOrgPolicyWarning": { + "message": "Organization members who are not owners or admins and are already a member of another organization will be removed from your organization." + }, + "requireSso": { + "message": "Require single sign-on authentication" + }, + "requireSsoPolicyDesc": { + "message": "Require members to log in with the Enterprise single sign-on method." + }, + "prerequisite": { + "message": "Prerequisite" + }, + "requireSsoPolicyReq": { + "message": "The single organization Enterprise policy must be turned on before activating this policy." + }, + "requireSsoPolicyReqError": { + "message": "Single organization policy not set up." + }, + "requireSsoExemption": { + "message": "Organization owners and admins are exempt from this policy's enforcement." + }, + "sendTypeFile": { + "message": "File" + }, + "sendTypeText": { + "message": "Text" + }, + "createSend": { + "message": "New Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editSend": { + "message": "Edit Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "createdSend": { + "message": "Send saved", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editedSend": { + "message": "Send saved", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deletedSend": { + "message": "Send deleted", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deleteSend": { + "message": "Delete Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deleteSendConfirmation": { + "message": "Are you sure you want to delete this Send?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "whatTypeOfSend": { + "message": "What type of Send is this?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deletionDate": { + "message": "Deletion date" + }, + "deletionDateDesc": { + "message": "The Send will be permanently deleted on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "expirationDate": { + "message": "Expiration date" + }, + "expirationDateDesc": { + "message": "If set, access to this Send will expire on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "maxAccessCount": { + "message": "Maximum access count" + }, + "maxAccessCountDesc": { + "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "currentAccessCount": { + "message": "Current access count" + }, + "sendPasswordDesc": { + "message": "Optionally require a password for users to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendNotesDesc": { + "message": "Private notes about this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "disabled": { + "message": "Disabled" + }, + "revoked": { + "message": "Revoked" + }, + "sendLink": { + "message": "Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "copySendLink": { + "message": "Copy Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, + "hideEmail": { + "message": "Hide my email address from recipients." + }, + "disableThisSend": { + "message": "Deactivate this Send so that no one can access it.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "allSends": { + "message": "All Sends" + }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, + "pendingDeletion": { + "message": "Pending deletion" + }, + "expired": { + "message": "Expired" + }, + "searchSends": { + "message": "Search Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendProtectedPassword": { + "message": "This Send is protected with a password. Please type the password below to continue.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendProtectedPasswordDontKnow": { + "message": "Don't know the password? Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendHiddenByDefault": { + "message": "This Send is hidden by default. You can toggle its visibility using the button below.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "downloadFile": { + "message": "Download file" + }, + "sendAccessUnavailable": { + "message": "The Send you are trying to access does not exist or is no longer available.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "missingSendFile": { + "message": "The file associated with this Send could not be found.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "noSendsInList": { + "message": "There are no Sends to list.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "emergencyAccessDesc": { + "message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of an emergency. Visit our help page for more information and details into how zero knowledge sharing works." + }, + "emergencyAccessOwnerWarning": { + "message": "You are an owner of one or more organizations. If you give takeover access to an emergency contact, they will be able to use all your permissions as owner after a takeover." + }, + "trustedEmergencyContacts": { + "message": "Trusted emergency contacts" + }, + "noTrustedContacts": { + "message": "You have not added any emergency contacts yet, invite a trusted contact to get started." + }, + "addEmergencyContact": { + "message": "Add emergency contact" + }, + "designatedEmergencyContacts": { + "message": "Designated as emergency contact" + }, + "noGrantedAccess": { + "message": "You have not been designated as an emergency contact for anyone yet." + }, + "inviteEmergencyContact": { + "message": "Invite emergency contact" + }, + "editEmergencyContact": { + "message": "Edit emergency contact" + }, + "inviteEmergencyContactDesc": { + "message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + }, + "emergencyAccessRecoveryInitiated": { + "message": "Emergency access initiated" + }, + "emergencyAccessRecoveryApproved": { + "message": "Emergency access approved" + }, + "viewDesc": { + "message": "Can view all items in your own vault." + }, + "takeover": { + "message": "Takeover" + }, + "takeoverDesc": { + "message": "Can reset your account with a new master password." + }, + "waitTime": { + "message": "Wait time" + }, + "waitTimeDesc": { + "message": "Time required before automatically granting access." + }, + "oneDay": { + "message": "1 day" + }, + "days": { + "message": "$DAYS$ days", + "placeholders": { + "days": { + "content": "$1", + "example": "1" + } + } + }, + "invitedUser": { + "message": "Invited user." + }, + "acceptEmergencyAccess": { + "message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + }, + "emergencyInviteAcceptFailed": { + "message": "Unable to accept invitation. Ask the user to send a new invitation." + }, + "emergencyInviteAcceptFailedShort": { + "message": "Unable to accept invitation. $DESCRIPTION$", + "placeholders": { + "description": { + "content": "$1", + "example": "You must set up 2FA on your user account before you can join this organization." + } + } + }, + "emergencyInviteAcceptedDesc": { + "message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens." + }, + "requestAccess": { + "message": "Request Access" + }, + "requestAccessConfirmation": { + "message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.", + "placeholders": { + "waittime": { + "content": "$1", + "example": "1" + } + } + }, + "requestSent": { + "message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.", + "placeholders": { + "user": { + "content": "$1", + "example": "John Smith" + } + } + }, + "approve": { + "message": "Approve" + }, + "reject": { + "message": "Reject" + }, + "approveAccessConfirmation": { + "message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.", + "placeholders": { + "user": { + "content": "$1", + "example": "John Smith" + }, + "action": { + "content": "$2", + "example": "View" + } + } + }, + "emergencyApproved": { + "message": "Emergency access approved" + }, + "emergencyRejected": { + "message": "Emergency access rejected" + }, + "passwordResetFor": { + "message": "Password reset for $USER$. You can now login using the new password.", + "placeholders": { + "user": { + "content": "$1", + "example": "John Smith" + } + } + }, + "personalOwnership": { + "message": "Remove individual vault" + }, + "personalOwnershipPolicyDesc": { + "message": "Require members to save items to an organization by removing the individual vault option." + }, + "personalOwnershipExemption": { + "message": "Organization owners and administrators are exempt from this policy's enforcement." + }, + "personalOwnershipSubmitError": { + "message": "Due to an Enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." + }, + "disableSend": { + "message": "Remove Send" + }, + "disableSendPolicyDesc": { + "message": "Do not allow members to create or edit Sends.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "disableSendExemption": { + "message": "Organization members that can manage the organization's policies are exempt from this policy's enforcement." + }, + "sendDisabled": { + "message": "Send removed", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendDisabledWarning": { + "message": "Due to an Enterprise policy, you are only able to delete an existing Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendOptions": { + "message": "Send options", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendOptionsPolicyDesc": { + "message": "Set options for creating and editing Sends.", + "description": "'Sends' is a plural noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendOptionsExemption": { + "message": "Organization members that can manage the organization's policies are exempt from this policy's enforcement." + }, + "disableHideEmail": { + "message": "Always show member’s email address with recipients when creating or editing a Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendOptionsPolicyInEffect": { + "message": "The following organization policies are currently in effect:" + }, + "sendDisableHideEmailInEffect": { + "message": "Users are not allowed to hide their email address from recipients when creating or editing a Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "modifiedPolicyId": { + "message": "Modified policy $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Master Password" + } + } + }, + "planPrice": { + "message": "Plan price" + }, + "estimatedTax": { + "message": "Estimated tax" + }, + "custom": { + "message": "Custom" + }, + "customDesc": { + "message": "Grant customized permissions to members" + }, + "customDescNonEnterpriseStart": { + "message": "Custom roles is an ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseLink": { + "message": "enterprise feature", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customDescNonEnterpriseEnd": { + "message": ". Contact our support team to upgrade your subscription", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" + }, + "customNonEnterpriseError": { + "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + }, + "permissions": { + "message": "Permissions" + }, + "permission": { + "message": "Permission" + }, + "managerPermissions": { + "message": "Manager Permissions" + }, + "adminPermissions": { + "message": "Admin Permissions" + }, + "accessEventLogs": { + "message": "Access event logs" + }, + "accessImportExport": { + "message": "Access import/export" + }, + "accessReports": { + "message": "Access reports" + }, + "missingPermissions": { + "message": "You lack the necessary permissions to perform this action." + }, + "manageAllCollections": { + "message": "Manage all collections" + }, + "createNewCollections": { + "message": "Create new collections" + }, + "editAnyCollection": { + "message": "Edit any collection" + }, + "deleteAnyCollection": { + "message": "Delete any collection" + }, + "manageAssignedCollections": { + "message": "Manage assigned collections" + }, + "editAssignedCollections": { + "message": "Edit assigned collections" + }, + "deleteAssignedCollections": { + "message": "Delete assigned collections" + }, + "manageGroups": { + "message": "Manage groups" + }, + "managePolicies": { + "message": "Manage policies" + }, + "manageSso": { + "message": "Manage SSO" + }, + "manageUsers": { + "message": "Manage users" + }, + "manageResetPassword": { + "message": "Manage password reset" + }, + "disableRequiredError": { + "message": "You must manually turn the $POLICYNAME$ policy before this policy can be turned off.", + "placeholders": { + "policyName": { + "content": "$1", + "example": "Single Sign-On Authentication" + } + } + }, + "personalOwnershipPolicyInEffect": { + "message": "An organization policy is affecting your ownership options." + }, + "personalOwnershipPolicyInEffectImports": { + "message": "An organization policy has blocked importing items into your individual vault." + }, + "personalOwnershipCheckboxDesc": { + "message": "Remove individual ownership for organization users" + }, + "textHiddenByDefault": { + "message": "When accessing the Send, hide the text by default", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendNameDesc": { + "message": "A friendly name to describe this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTextDesc": { + "message": "The text you want to Send." + }, + "sendFileDesc": { + "message": "The file you want to Send." + }, + "copySendLinkOnSave": { + "message": "Copy the link to share this Send to my clipboard upon save." + }, + "sendLinkLabel": { + "message": "Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "send": { + "message": "Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendAccessTaglineProductDesc": { + "message": "Bitwarden Send transmits sensitive, temporary information to others easily and securely.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendAccessTaglineLearnMore": { + "message": "Learn more about", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**Learn more about** Bitwarden Send or sign up to try it today.'" + }, + "sendVaultCardProductDesc": { + "message": "Share text or files directly with anyone." + }, + "sendVaultCardLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**Learn more**, see how it works, or try it now. '" + }, + "sendVaultCardSee": { + "message": "see", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, **see** how it works, or try it now.'" + }, + "sendVaultCardHowItWorks": { + "message": "how it works", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see **how it works**, or try it now.'" + }, + "sendVaultCardOr": { + "message": "or", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, **or** try it now.'" + }, + "sendVaultCardTryItNow": { + "message": "try it now", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, or **try it now**.'" + }, + "sendAccessTaglineOr": { + "message": "or", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send **or** sign up to try it today.'" + }, + "sendAccessTaglineSignUp": { + "message": "sign up", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send or **sign up** to try it today.'" + }, + "sendAccessTaglineTryToday": { + "message": "to try it today.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send or sign up to **try it today.**'" + }, + "sendCreatorIdentifier": { + "message": "Bitwarden user $USER_IDENTIFIER$ shared the following with you", + "placeholders": { + "user_identifier": { + "content": "$1", + "example": "An email address" + } + } + }, + "viewSendHiddenEmailWarning": { + "message": "The Bitwarden user who created this Send has chosen to hide their email address. You should ensure you trust the source of this link before using or downloading its content.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "expirationDateIsInvalid": { + "message": "The expiration date provided is not valid." + }, + "deletionDateIsInvalid": { + "message": "The deletion date provided is not valid." + }, + "expirationDateAndTimeRequired": { + "message": "An expiration date and time are required." + }, + "deletionDateAndTimeRequired": { + "message": "A deletion date and time are required." + }, + "dateParsingError": { + "message": "There was an error saving your deletion and expiration dates." + }, + "webAuthnFallbackMsg": { + "message": "To verify your 2FA please click the button below." + }, + "webAuthnAuthenticate": { + "message": "Authenticate WebAuthn" + }, + "webAuthnNotSupported": { + "message": "WebAuthn is not supported in this browser." + }, + "webAuthnSuccess": { + "message": "WebAuthn verified successfully! You may close this tab." + }, + "hintEqualsPassword": { + "message": "Your password hint cannot be the same as your password." + }, + "enrollPasswordReset": { + "message": "Enroll in password reset" + }, + "enrolledPasswordReset": { + "message": "Enrolled in password reset" + }, + "withdrawPasswordReset": { + "message": "Withdraw from password reset" + }, + "enrollPasswordResetSuccess": { + "message": "Enrollment success!" + }, + "withdrawPasswordResetSuccess": { + "message": "Withdrawal success!" + }, + "eventEnrollPasswordReset": { + "message": "User $ID$ enrolled in password reset.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "eventWithdrawPasswordReset": { + "message": "User $ID$ withdrew from password reset.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "eventAdminPasswordReset": { + "message": "Master password reset for user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "eventResetSsoLink": { + "message": "Reset SSO link for user $ID$", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "firstSsoLogin": { + "message": "$ID$ logged in using Sso for the first time", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, + "resetPassword": { + "message": "Reset password" + }, + "resetPasswordLoggedOutWarning": { + "message": "Proceeding will log $NAME$ out of their current session, requiring them to log back in. Active sessions on other devices may continue to remain active for up to one hour.", + "placeholders": { + "name": { + "content": "$1", + "example": "John Smith" + } + } + }, + "thisUser": { + "message": "this user" + }, + "resetPasswordMasterPasswordPolicyInEffect": { + "message": "One or more organization policies require the master password to meet the following requirements:" + }, + "resetPasswordSuccess": { + "message": "Password reset success!" + }, + "resetPasswordEnrollmentWarning": { + "message": "Enrollment will allow organization administrators to change your master password" + }, + "resetPasswordPolicy": { + "message": "Master password reset" + }, + "resetPasswordPolicyDescription": { + "message": "Allow admins to reset master passwords for members." + }, + "resetPasswordPolicyWarning": { + "message": "Members in the organization will need to self-enroll or be auto-enrolled before administrators can reset their master password." + }, + "resetPasswordPolicyAutoEnroll": { + "message": "Automatic enrollment" + }, + "resetPasswordPolicyAutoEnrollDescription": { + "message": "All members will be automatically enrolled in password reset once their invite is accepted and will not be allowed to withdraw." + }, + "resetPasswordPolicyAutoEnrollWarning": { + "message": "Members already in the organization will not be retroactively enrolled in password reset. They will need to self-enroll before administrators can reset their master password." + }, + "resetPasswordPolicyAutoEnrollCheckbox": { + "message": "Require new members to be enrolled automatically" + }, + "resetPasswordAutoEnrollInviteWarning": { + "message": "This organization has an Enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + }, + "resetPasswordOrgKeysError": { + "message": "Organization keys response is null" + }, + "resetPasswordDetailsError": { + "message": "Reset password details response is null" + }, + "trashCleanupWarning": { + "message": "Items that have been in trash more than 30 days will be automatically deleted." + }, + "trashCleanupWarningSelfHosted": { + "message": "Items that have been in trash for a while will be automatically deleted." + }, + "passwordPrompt": { + "message": "Master password re-prompt" + }, + "passwordConfirmation": { + "message": "Master password confirmation" + }, + "passwordConfirmationDesc": { + "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + }, + "reinviteSelected": { + "message": "Resend invitations" + }, + "resendNotification": { + "message": "Resend notification" + }, + "noSelectedUsersApplicable": { + "message": "This action is not applicable to any of the selected users." + }, + "removeUsersWarning": { + "message": "Are you sure you want to remove the following users? The process may take a few seconds to complete and cannot be interrupted or canceled." + }, + "removeOrgUsersConfirmation": { + "message": "When member(s) are removed, they no longer have access to organization data and this action is irreversible. To add the member back to the organization, they must be invited and onboarded again. The process may take a few seconds to complete and cannot be interrupted or canceled." + }, + "revokeUsersWarning": { + "message": "When member(s) are revoked, they no longer have access to organization data. To quickly restore member access, go to the Revoked tab. The process may take a few seconds to complete and cannot be interrupted or canceled." + }, + "theme": { + "message": "Theme" + }, + "themeDesc": { + "message": "Choose a theme for your web vault." + }, + "themeSystem": { + "message": "Use system theme" + }, + "themeDark": { + "message": "Dark" + }, + "themeLight": { + "message": "Light" + }, + "confirmSelected": { + "message": "Confirm selected" + }, + "bulkConfirmStatus": { + "message": "Bulk action status" + }, + "bulkConfirmMessage": { + "message": "Confirmed successfully" + }, + "bulkReinviteMessage": { + "message": "Reinvited successfully" + }, + "bulkRemovedMessage": { + "message": "Removed successfully" + }, + "bulkRevokedMessage": { + "message": "Revoked organization access successfully" + }, + "bulkRestoredMessage": { + "message": "Restored organization access successfully" + }, + "bulkFilteredMessage": { + "message": "Excluded, not applicable for this action" + }, + "fingerprint": { + "message": "Fingerprint" + }, + "removeUsers": { + "message": "Remove users" + }, + "revokeUsers": { + "message": "Revoke users" + }, + "restoreUsers": { + "message": "Restore users" + }, + "error": { + "message": "Error" + }, + "resetPasswordManageUsers": { + "message": "Manage users must also be granted with the manage password reset permission" + }, + "setupProvider": { + "message": "Provider setup" + }, + "setupProviderLoginDesc": { + "message": "You've been invited to setup a new Provider. To continue, you need to log in or create a new Bitwarden account." + }, + "setupProviderDesc": { + "message": "Please enter the details below to complete the Provider setup. Contact Customer Support if you have any questions." + }, + "providerName": { + "message": "Provider name" + }, + "providerSetup": { + "message": "Provider successfully set up" + }, + "clients": { + "message": "Clients" + }, + "client": { + "message": "Client", + "description": "This is used as a table header to describe which client application created an event log." + }, + "providerAdmin": { + "message": "Provider admin" + }, + "providerAdminDesc": { + "message": "The highest access user that can manage all aspects of your Provider as well as access and manage client organizations." + }, + "serviceUser": { + "message": "Service user" + }, + "serviceUserDesc": { + "message": "Service users can access and manage all client organizations." + }, + "providerInviteUserDesc": { + "message": "Invite a new user to your Provider by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + }, + "joinProvider": { + "message": "Join Provider" + }, + "joinProviderDesc": { + "message": "You've been invited to join the Provider listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + }, + "providerInviteAcceptFailed": { + "message": "Unable to accept invitation. Ask a Provider admin to send a new invitation." + }, + "providerInviteAcceptedDesc": { + "message": "You can access this Provider once an administrator confirms your membership. We'll send you an email when that happens." + }, + "providerUsersNeedConfirmed": { + "message": "You have users that have accepted their invitation, but still need to be confirmed. Users will not have access to the Provider until they are confirmed." + }, + "provider": { + "message": "Provider" + }, + "newClientOrganization": { + "message": "New client organization" + }, + "newClientOrganizationDesc": { + "message": "Create a new client organization that will be associated with you as the Provider. You will be able to access and manage this organization." + }, + "addExistingOrganization": { + "message": "Add existing organization" + }, + "myProvider": { + "message": "My Provider" + }, + "addOrganizationConfirmation": { + "message": "Are you sure you want to add $ORGANIZATION$ as a client to $PROVIDER$?", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + }, + "provider": { + "content": "$2", + "example": "My Provider Name" + } + } + }, + "organizationJoinedProvider": { + "message": "Organization was successfully added to the Provider" + }, + "accessingUsingProvider": { + "message": "Accessing organization using Provider $PROVIDER$", + "placeholders": { + "provider": { + "content": "$1", + "example": "My Provider Name" + } + } + }, + "providerIsDisabled": { + "message": "Provider suspended" + }, + "providerUpdated": { + "message": "Provider saved" + }, + "yourProviderIs": { + "message": "Your Provider is $PROVIDER$. They have administrative and billing privileges for your organization.", + "placeholders": { + "provider": { + "content": "$1", + "example": "My Provider Name" + } + } + }, + "detachedOrganization": { + "message": "The organization $ORGANIZATION$ has been detached from your Provider.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "detachOrganizationConfirmation": { + "message": "Are you sure you want to detach this organization? The organization will continue to exist but will no longer be managed by the Provider." + }, + "add": { + "message": "Add" + }, + "updatedMasterPassword": { + "message": "Master password saved" + }, + "updateMasterPassword": { + "message": "Update master password" + }, + "updateMasterPasswordWarning": { + "message": "Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + }, + "masterPasswordInvalidWarning": { + "message": "Your master password does not meet the policy requirements of this organization. In order to join the organization, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + }, + "maximumVaultTimeout": { + "message": "Vault timeout" + }, + "maximumVaultTimeoutDesc": { + "message": "Set a maximum vault timeout for members." + }, + "maximumVaultTimeoutLabel": { + "message": "Maximum vault timeout" + }, + "invalidMaximumVaultTimeout": { + "message": "Invalid maximum vault timeout." + }, + "hours": { + "message": "Hours" + }, + "minutes": { + "message": "Minutes" + }, + "vaultTimeoutPolicyInEffect": { + "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "customVaultTimeout": { + "message": "Custom vault timeout" + }, + "vaultTimeoutToLarge": { + "message": "Your vault timeout exceeds the restriction set by your organization." + }, + "vaultCustomTimeoutMinimum": { + "message": "Minimum custom timeout is 1 minute." + }, + "vaultTimeoutRangeError": { + "message": "Vault timeout is not within allowed range." + }, + "disablePersonalVaultExport": { + "message": "Remove individual vault export" + }, + "disablePersonalVaultExportDesc": { + "message": "Do not allow members to export their individual vault data." + }, + "vaultExportDisabled": { + "message": "Vault export removed" + }, + "personalVaultExportPolicyInEffect": { + "message": "One or more organization policies prevents you from exporting your individual vault." + }, + "selectType": { + "message": "Select SSO type" + }, + "type": { + "message": "Type" + }, + "openIdConnectConfig": { + "message": "OpenID connect configuration" + }, + "samlSpConfig": { + "message": "SAML service provider configuration" + }, + "samlIdpConfig": { + "message": "SAML identity provider configuration" + }, + "callbackPath": { + "message": "Callback path" + }, + "signedOutCallbackPath": { + "message": "Signed out callback path" + }, + "authority": { + "message": "Authority" + }, + "clientId": { + "message": "Client ID" + }, + "clientSecret": { + "message": "Client secret" + }, + "metadataAddress": { + "message": "Metadata address" + }, + "oidcRedirectBehavior": { + "message": "OIDC redirect behavior" + }, + "getClaimsFromUserInfoEndpoint": { + "message": "Get claims from user info endpoint" + }, + "additionalScopes": { + "message": "Custom scopes" + }, + "additionalUserIdClaimTypes": { + "message": "Custom user ID claim types" + }, + "additionalEmailClaimTypes": { + "message": "Email claim types" + }, + "additionalNameClaimTypes": { + "message": "Custom name claim types" + }, + "acrValues": { + "message": "Requested authentication context class reference values" + }, + "expectedReturnAcrValue": { + "message": "Expected \"acr\" claim value in response" + }, + "spEntityId": { + "message": "SP entity ID" + }, + "spMetadataUrl": { + "message": "SAML 2.0 metadata URL" + }, + "spAcsUrl": { + "message": "Assertion consumer service (ACS) URL" + }, + "spNameIdFormat": { + "message": "Name ID format" + }, + "spOutboundSigningAlgorithm": { + "message": "Outbound signing algorithm" + }, + "spSigningBehavior": { + "message": "Signing behavior" + }, + "spMinIncomingSigningAlgorithm": { + "message": "Minimum incoming signing algorithm" + }, + "spWantAssertionsSigned": { + "message": "Expect signed assertions" + }, + "spValidateCertificates": { + "message": "Validate certificates" + }, + "idpEntityId": { + "message": "Entity ID" + }, + "idpBindingType": { + "message": "Binding type" + }, + "idpSingleSignOnServiceUrl": { + "message": "Single sign-on service URL" + }, + "idpSingleLogoutServiceUrl": { + "message": "Single log-out service URL" + }, + "idpX509PublicCert": { + "message": "X509 public certificate" + }, + "idpOutboundSigningAlgorithm": { + "message": "Outbound signing algorithm" + }, + "idpAllowUnsolicitedAuthnResponse": { + "message": "Allow unsolicited authentication response" + }, + "idpAllowOutboundLogoutRequests": { + "message": "Allow outbound logout requests" + }, + "idpSignAuthenticationRequests": { + "message": "Sign authentication requests" + }, + "ssoSettingsSaved": { + "message": "Single sign-on configuration saved" + }, + "sponsoredFamilies": { + "message": "Free Bitwarden Families" + }, + "sponsoredFamiliesEligible": { + "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." + }, + "sponsoredFamiliesEligibleCard": { + "message": "Redeem your Free Bitwarden for Families plan today to keep your data secure even when you are not at work." + }, + "sponsoredFamiliesInclude": { + "message": "The Bitwarden for Families plan include" + }, + "sponsoredFamiliesPremiumAccess": { + "message": "Premium access for up to 6 users" + }, + "sponsoredFamiliesSharedCollections": { + "message": "Shared collections for Family secrets" + }, + "badToken": { + "message": "The link is no longer valid. Please have the sponsor resend the offer." + }, + "reclaimedFreePlan": { + "message": "Reclaimed free plan" + }, + "redeem": { + "message": "Redeem" + }, + "sponsoredFamiliesSelectOffer": { + "message": "Select the organization you would like sponsored" + }, + "familiesSponsoringOrgSelect": { + "message": "Which Free Families offer would you like to redeem?" + }, + "sponsoredFamiliesEmail": { + "message": "Enter your personal email to redeem Bitwarden Families" + }, + "sponsoredFamiliesLeaveCopy": { + "message": "If you remove an offer or are removed from the sponsoring organization, your Families sponsorship will expire at the next renewal date." + }, + "acceptBitwardenFamiliesHelp": { + "message": "Accept offer for an existing organization or create a new Families organization." + }, + "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." + }, + "sponsoredFamiliesAcceptFailed": { + "message": "Unable to accept offer. Please resend the offer email from your Enterprise account and try again." + }, + "sponsoredFamiliesAcceptFailedShort": { + "message": "Unable to accept offer. $DESCRIPTION$", + "placeholders": { + "description": { + "content": "$1", + "example": "You must have at least one existing Families organization." + } + } + }, + "sponsoredFamiliesOffer": { + "message": "Accept Free Bitwarden Families" + }, + "sponsoredFamiliesOfferRedeemed": { + "message": "Free Bitwarden Families offer successfully redeemed" + }, + "redeemed": { + "message": "Redeemed" + }, + "redeemedAccount": { + "message": "Account redeemed" + }, + "revokeAccount": { + "message": "Revoke account $NAME$", + "placeholders": { + "name": { + "content": "$1", + "example": "My Sponsorship Name" + } + } + }, + "resendEmailLabel": { + "message": "Resend sponsorship email to $NAME$ sponsorship", + "placeholders": { + "name": { + "content": "$1", + "example": "My Sponsorship Name" + } + } + }, + "freeFamiliesPlan": { + "message": "Free Families plan" + }, + "redeemNow": { + "message": "Redeem now" + }, + "recipient": { + "message": "Recipient" + }, + "removeSponsorship": { + "message": "Remove sponsorship" + }, + "removeSponsorshipConfirmation": { + "message": "After removing a sponsorship, you will be responsible for this subscription and related invoices. Are you sure you want to continue?" + }, + "sponsorshipCreated": { + "message": "Sponsorship created" + }, + "emailSent": { + "message": "Email sent" + }, + "revokeSponsorshipConfirmation": { + "message": "After removing this account, the Families plan sponsorship will expire at the end of the billing period. You will not be able to redeem a new sponsorship offer until the existing one expires. Are you sure you want to continue?" + }, + "removeSponsorshipSuccess": { + "message": "Sponsorship removed" + }, + "ssoKeyConnectorError": { + "message": "Key Connector error: make sure Key Connector is available and working correctly." + }, + "keyConnectorUrl": { + "message": "Key Connector URL" + }, + "sendVerificationCode": { + "message": "Send a verification code to your email" + }, + "sendCode": { + "message": "Send code" + }, + "codeSent": { + "message": "Code sent" + }, + "verificationCode": { + "message": "Verification code" + }, + "confirmIdentity": { + "message": "Confirm your identity to continue." + }, + "verificationCodeRequired": { + "message": "Verification code is required." + }, + "invalidVerificationCode": { + "message": "Invalid verification code" + }, + "convertOrganizationEncryptionDesc": { + "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "leaveOrganization": { + "message": "Leave organization" + }, + "removeMasterPassword": { + "message": "Remove master password" + }, + "removedMasterPassword": { + "message": "Master password removed" + }, + "allowSso": { + "message": "Allow SSO authentication" + }, + "allowSsoDesc": { + "message": "Once set up, your configuration will be saved and members will be able to authenticate using their Identity Provider credentials." + }, + "ssoPolicyHelpStart": { + "message": "Use the", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" + }, + "ssoPolicyHelpLink": { + "message": "require single-sign-on authentication policy", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" + }, + "ssoPolicyHelpEnd": { + "message": "to require all members to log in with SSO.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" + }, + "ssoPolicyHelpKeyConnector": { + "message": "The require SSO authentication and single organization policies are required to set up Key Connector decryption." + }, + "memberDecryptionOption": { + "message": "Member decryption options" + }, + "memberDecryptionPassDesc": { + "message": "Once authenticated, members will decrypt vault data using their master passwords." + }, + "keyConnector": { + "message": "Key Connector" + }, + "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." + }, + "keyConnectorPolicyRestriction": { + "message": "\"Login with SSO and Key Connector Decryption\" is activated. This policy will only apply to owners and admins." + }, + "enabledSso": { + "message": "SSO turned on" + }, + "disabledSso": { + "message": "SSO turned on" + }, + "enabledKeyConnector": { + "message": "Key Connector activated" + }, + "disabledKeyConnector": { + "message": "Key Connector deactivated" + }, + "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." + }, + "migratedKeyConnector": { + "message": "Migrated to Key Connector" + }, + "paymentSponsored": { + "message": "Please provide a payment method to associate with the organization. Don't worry, we won't charge you anything unless you select additional features or your sponsorship expires. " + }, + "orgCreatedSponsorshipInvalid": { + "message": "The sponsorship offer has expired. You may delete the organization you created to avoid a charge at the end of your 7 day trial. Otherwise you may close this prompt to keep the organization and assume billing responsibility." + }, + "newFamiliesOrganization": { + "message": "New Families organization" + }, + "acceptOffer": { + "message": "Accept offer" + }, + "sponsoringOrg": { + "message": "Sponsoring organization" + }, + "keyConnectorTest": { + "message": "Test" + }, + "keyConnectorTestSuccess": { + "message": "Success! Key Connector reached." + }, + "keyConnectorTestFail": { + "message": "Cannot reach Key Connector. Check URL." + }, + "sponsorshipTokenHasExpired": { + "message": "The sponsorship offer has expired." + }, + "freeWithSponsorship": { + "message": "FREE with sponsorship" + }, + "viewBillingSyncToken": { + "message": "View billing sync token" + }, + "generateBillingSyncToken": { + "message": "Generate billing sync token" + }, + "copyPasteBillingSync": { + "message": "Copy and paste this token into the billing sync settings of your self-hosted organization." + }, + "billingSyncCanAccess": { + "message": "Your billing sync token can access and edit this organization's subscription settings." + }, + "manageBillingSync": { + "message": "Manage billing sync" + }, + "setUpBillingSync": { + "message": "Set up billing sync" + }, + "generateToken": { + "message": "Generate token" + }, + "rotateToken": { + "message": "Rotate token" + }, + "rotateBillingSyncTokenWarning": { + "message": "If you proceed, you will need to re-setup billing sync on your self-hosted server." + }, + "rotateBillingSyncTokenTitle": { + "message": "Rotating the billing sync token will invalidate the previous token." + }, + "selfHostingTitle": { + "message": "Self-hosting" + }, + "selfHostingEnterpriseOrganizationSectionCopy": { + "message": "To set-up your organization on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up billing sync." + }, + "billingSyncApiKeyRotated": { + "message": "Token rotated" + }, + "billingSync": { + "message": "Billing sync" + }, + "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." + }, + "billingSyncKeyDesc": { + "message": "A billing sync token from your cloud organization's subscription settings is required to complete this form." + }, + "billingSyncKey": { + "message": "Billing sync token" + }, + "active": { + "message": "Active" + }, + "inactive": { + "message": "Inactive" + }, + "sentAwaitingSync": { + "message": "Sent (awaiting sync)" + }, + "sent": { + "message": "Sent" + }, + "requestRemoved": { + "message": "Removed (awaiting sync)" + }, + "requested": { + "message": "Requested" + }, + "formErrorSummaryPlural": { + "message": "$COUNT$ fields above need your attention.", + "placeholders": { + "count": { + "content": "$1", + "example": "5" + } + } + }, + "formErrorSummarySingle": { + "message": "1 field above needs your attention." + }, + "fieldRequiredError": { + "message": "$FIELDNAME$ is required.", + "placeholders": { + "fieldname": { + "content": "$1", + "example": "Full name" + } + } + }, + "required": { + "message": "required" + }, + "idpSingleSignOnServiceUrlRequired": { + "message": "Required if Entity ID is not a URL." + }, + "openIdOptionalCustomizations": { + "message": "Optional customizations" + }, + "openIdAuthorityRequired": { + "message": "Required if Authority is not valid." + }, + "separateMultipleWithComma": { + "message": "Separate multiple with a comma." + }, + "sessionTimeout": { + "message": "Your session has timed out. Please go back and try logging in again." + }, + "exportingPersonalVaultTitle": { + "message": "Exporting individual vault" + }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingPersonalVaultDescription": { + "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated password history or attachments.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "exportingOrganizationVaultDescription": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Individual vault items and items from other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "accessDenied": { + "message": "Access denied. You do not have permission to view this page." + }, + "masterPassword": { + "message": "Master password" + }, + "security": { + "message": "Security" + }, + "keys": { + "message": "Keys" + }, + "billingHistory": { + "message": "Billing history" + }, + "backToReports": { + "message": "Back to reports" + }, + "organizationPicker": { + "message": "Organization picker" + }, + "currentOrganization": { + "message": "Current organization", + "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." + }, + "accountSettings": { + "message": "Account settings" + }, + "generator": { + "message": "Generator" + }, + "whatWouldYouLikeToGenerate": { + "message": "What would you like to generate?" + }, + "passwordType": { + "message": "Password type" + }, + "regenerateUsername": { + "message": "Regenerate username" + }, + "generateUsername": { + "message": "Generate username" + }, + "usernameType": { + "message": "Username type" + }, + "plusAddressedEmail": { + "message": "Plus addressed email", + "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" + }, + "plusAddressedEmailDesc": { + "message": "Use your email provider's sub-addressing capabilities." + }, + "catchallEmail": { + "message": "Catch-all email" + }, + "catchallEmailDesc": { + "message": "Use your domain's configured catch-all inbox." + }, + "random": { + "message": "Random", + "description": "Generates domain-based username using random letters" + }, + "randomWord": { + "message": "Random word" + }, + "service": { + "message": "Service" + }, + "unknownCipher": { + "message": "Unknown item, you may need to request permission to access this item." + }, + "cannotSponsorSelf": { + "message": "You cannot redeem for the active account. Enter a different email." + }, + "revokeWhenExpired": { + "message": "Expires $DATE$", + "placeholders": { + "date": { + "content": "$1", + "example": "12/31/2020" + } + } + }, + "awaitingSyncSingular": { + "message": "Token rotated $DAYS$ day ago. Update the billing sync token in your self-hosted organization settings.", + "placeholders": { + "days": { + "content": "$1", + "example": "1" + } + } + }, + "awaitingSyncPlural": { + "message": "Token rotated $DAYS$ days ago. Update the billing sync token in your self-hosted organization settings.", + "placeholders": { + "days": { + "content": "$1", + "example": "1" + } + } + }, + "lastSync": { + "message": "Last sync", + "Description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\"" + }, + "sponsorshipsSynced": { + "message": "Self-hosted sponsorships synced." + }, + "billingManagedByProvider": { + "message": "Managed by $PROVIDER$", + "placeholders": { + "provider": { + "content": "$1", + "example": "Managed Services Company" + } + } + }, + "billingContactProviderForAssistance": { + "message": "Please reach out to them for further assistance", + "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" + }, + "forwardedEmailDesc": { + "message": "Generate an email alias with an external forwarding service." + }, + "hostname": { + "message": "Hostname", + "description": "Part of a URL." + }, + "apiAccessToken": { + "message": "API access token" + }, + "deviceVerification": { + "message": "Device verification" + }, + "enableDeviceVerification": { + "message": "Turn on device verification" + }, + "deviceVerificationDesc": { + "message": "Verification codes are sent to your email address when logging in from an unrecognized device" + }, + "updatedDeviceVerification": { + "message": "Updated device verification" + }, + "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { + "message": "Are you sure you want to turn on device verification? The verification code emails will arrive at: $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "My Email" + } + } + }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, + "scim": { + "message": "SCIM provisioning", + "description": "The text, 'SCIM', is an acronymn and should not be translated." + }, + "scimDescription": { + "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", + "description": "the text, 'SCIM', is an acronymn and should not be translated." + }, + "scimEnabledCheckboxDesc": { + "message": "Enable SCIM", + "description": "the text, 'SCIM', is an acronymn and should not be translated." + }, + "scimEnabledCheckboxDescHelpText": { + "message": "Set up your preferred identity provider by configuring the URL and SCIM API Key", + "description": "the text, 'SCIM', is an acronymn and should not be translated." + }, + "scimApiKeyHelperText": { + "message": "This API key has access to manage users within your organization. It should be kept secret." + }, + "copyScimKey": { + "message": "Copy the SCIM API key to your clipboard", + "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." + }, + "rotateScimKey": { + "message": "Rotate the SCIM API key", + "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." + }, + "rotateScimKeyWarning": { + "message": "Are you sure you want to rotate the SCIM API Key? The current key will no longer work for any existing integrations.", + "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." + }, + "rotateKey": { + "message": "Rotate key" + }, + "scimApiKey": { + "message": "SCIM API key", + "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." + }, + "copyScimUrl": { + "message": "Copy the SCIM endpoint URL to your clipboard", + "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." + }, + "scimUrl": { + "message": "SCIM URL", + "description": "the text, 'SCIM' and 'URL', are acronymns and should not be translated." + }, + "scimApiKeyRotated": { + "message": "SCIM API key successfully rotated", + "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." + }, + "scimSettingsSaved": { + "message": "SCIM settings saved", + "description": "the text, 'SCIM', is an acronymn and should not be translated." + }, + "inputRequired": { + "message": "Input is required." + }, + "inputEmail": { + "message": "Input is not an email address." + }, + "inputMinLength": { + "message": "Input must be at least $COUNT$ characters long.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "inputMaxLength": { + "message": "Input must not exceed $COUNT$ characters in length.", + "placeholders": { + "count": { + "content": "$1", + "example": "20" + } + } + }, + "fieldsNeedAttention": { + "message": "$COUNT$ field(s) above need your attention.", + "placeholders": { + "count": { + "content": "$1", + "example": "4" + } + } + }, + "turnOn": { + "message": "Turn on" + }, + "on": { + "message": "On" + }, + "members": { + "message": "Members" + }, + "reporting": { + "message": "Reporting" + }, + "cardBrandMir": { + "message": "Mir" + }, + "numberOfUsers": { + "message": "Number of users" + }, + "loggingInAs": { + "message": "Logging in as" + }, + "notYou": { + "message": "Not you?" + }, + "pickAnAvatarColor": { + "message": "Pick an avatar color" + }, + "customizeAvatar": { + "message": "Customize avatar" + }, + "avatarUpdated": { + "message": "Avatar updated" + }, + "brightBlue": { + "message": "Bright Blue" + }, + "green": { + "message": "Green" + }, + "orange": { + "message": "Orange" + }, + "lavender": { + "message": "Lavender" + }, + "yellow": { + "message": "Yellow" + }, + "indigo": { + "message": "Indigo" + }, + "teal": { + "message": "Teal" + }, + "salmon": { + "message": "Salmon" + }, + "pink": { + "message": "Pink" + }, + "customColor": { + "message": "Custom Color" + }, + "multiSelectPlaceholder": { + "message": "-- Type to Filter --" + }, + "multiSelectLoading": { + "message": "Retrieving options..." + }, + "multiSelectNotFound": { + "message": "No items found" + }, + "multiSelectClearAll": { + "message": "Clear all" + }, + "toggleCharacterCount": { + "message": "Toggle character count", + "description": "'Character count' describes a feature that displays a number next to each character of the password." + }, + "passwordCharacterCount": { + "message": "Password character count", + "description": "'Character count' describes a feature that displays a number next to each character of the password." + }, + "hide": { + "message": "Hide" + }, + "projects": { + "message": "Projects" + }, + "lastEdited": { + "message": "Last Edited" + }, + "editSecret": { + "message": "Edit Secret" + }, + "addSecret": { + "message": "Add Secret" + }, + "copySecretName": { + "message": "Copy Secret Name" + }, + "copySecretValue": { + "message": "Copy Secret Value" + }, + "deleteSecret": { + "message": "Delete Secret" + }, + "deleteSecrets": { + "message": "Delete Secrets" + }, + "secretProjectAssociationDescription": { + "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + }, + "typeOrSelectProjects": { + "message": "Type or select Projects" + }, + "typeOrSelectProject": { + "message": "Type or select Project" + }, + "project": { + "message": "Project" + }, + "editProject": { + "message": "Edit Project" + }, + "viewProject": { + "message": "View Project" + }, + "deleteProject": { + "message": "Delete Project" + }, + "deleteProjects": { + "message": "Delete Projects" + }, + "secret": { + "message": "Secret" + }, + "serviceAccount": { + "message": "Service Account" + }, + "serviceAccounts": { + "message": "Service Accounts" + }, + "new": { + "message": "New" + }, + "secrets": { + "message": "Secrets" + }, + "nameValuePair": { + "message": "Name/Value Pair" + }, + "secretEdited": { + "message": "Secret edited" + }, + "secretCreated": { + "message": "Secret created" + }, + "newSecret": { + "message": "New Secret" + }, + "newServiceAccount": { + "message": "New Service Account" + }, + "secretsNoItemsTitle": { + "message": "No secrets to show" + }, + "secretsNoItemsMessage": { + "message": "To get started, add a new secret or import secrets." + }, + "serviceAccountsNoItemsTitle": { + "message": "Nothing to show yet" + }, + "serviceAccountsNoItemsMessage": { + "message": "Create a new Service Account to get started automating secret access." + }, + "searchSecrets": { + "message": "Search Secrets" + }, + "deleteServiceAccounts": { + "message": "Delete Service Accounts" + }, + "deleteServiceAccount": { + "message": "Delete Service Account" + }, + "viewServiceAccount": { + "message": "View Service Account" + }, + "searchServiceAccounts": { + "message": "Search Service Accounts" + }, + "addProject": { + "message": "Add Project" + }, + "projectEdited": { + "message": "Project edited" + }, + "projectSaved": { + "message": "Project saved" + }, + "projectCreated": { + "message": "Project created" + }, + "projectName": { + "message": "Project Name" + }, + "newProject": { + "message": "New Project" + }, + "softDeleteSecretWarning": { + "message": "Deleting secrets can affect existing integrations." + }, + "softDeletesSuccessToast": { + "message": "Secrets sent to trash" + }, + "serviceAccountCreated": { + "message": "Service Account Created" + }, + "smAccess": { + "message": "Access" + }, + "projectCommaSecret": { + "message": "Project, Secret" + }, + "serviceAccountName": { + "message": "Service account name" + }, + "newSaSelectAccess": { + "message": "Type or Select Projects or Secrets" + }, + "newSaTypeToFilter": { + "message": "Type to Filter" + }, + "deleteProjectsToast": { + "message": "Projects deleted" + }, + "deleteProjectToast": { + "message": "The project and all associated secrets have been deleted" + }, + "deleteProjectDialogMessage": { + "message": "Deleting project $PROJECT$ is permanent and irreversible.", + "placeholders": { + "project": { + "content": "$1", + "example": "project name" + } + } + }, + "deleteProjectInputLabel": { + "message": "Type \"$CONFIRM$\" to continue", + "placeholders": { + "confirm": { + "content": "$1", + "example": "Delete 3 Projects" + } + } + }, + "deleteProjectConfirmMessage": { + "message": "Delete $PROJECT$", + "placeholders": { + "project": { + "content": "$1", + "example": "project name" + } + } + }, + "deleteProjectsConfirmMessage": { + "message": "Delete $COUNT$ Projects", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "deleteProjectsDialogMessage": { + "message": "Deleting projects is permanent and irreversible." + }, + "projectsNoItemsTitle": { + "message": "No projects to display" + }, + "projectsNoItemsMessage": { + "message": "Add a new project to get started organizing secrets." + }, + "smConfirmationRequired": { + "message": "Confirmation required" + }, + "bulkDeleteProjectsErrorMessage": { + "message": "The following projects could not be deleted:" + }, + "softDeleteSuccessToast": { + "message": "Secret sent to trash" + }, + "searchProjects": { + "message": "Search Projects" + }, + "accessTokens": { + "message": "Access tokens" + }, + "createAccessToken": { + "message": "Create access token" + }, + "expires": { + "message": "Expires" + }, + "canRead": { + "message": "Can Read" + }, + "accessTokensNoItemsTitle": { + "message": "No access tokens to show" + }, + "accessTokensNoItemsDesc": { + "message": "To get started, create an access token" + }, + "downloadAccessToken": { + "message": "Download or copy before closing." + }, + "expiresOnAccessToken": { + "message": "Expires on:" + }, + "accessTokenCallOutTitle": { + "message": "Access tokens are not stored and cannot be retrieved" + }, + "copyToken": { + "message": "Copy token" + }, + "accessToken": { + "message": "Access token" + }, + "accessTokenExpirationRequired": { + "message": "Expiration date required" + }, + "accessTokenCreatedAndCopied": { + "message": "Access token created and copied to clipboard" + }, + "accessTokenPermissionsBetaNotification": { + "message": "Permissions management is unavailable for beta." + }, + "revokeAccessToken": { + "message": "Revoke Access Token" + }, + "submenu": { + "message": "Submenu" + }, + "from": { + "message": "From" + }, + "to": { + "message": "To" + }, + "member": { + "message": "Member" + }, + "update": { + "message": "Update" + }, + "role": { + "message": "Role" + }, + "canView": { + "message": "Can view" + }, + "canViewExceptPass": { + "message": "Can view, except passwords" + }, + "canEdit": { + "message": "Can edit" + }, + "canEditExceptPass": { + "message": "Can edit, except passwords" + }, + "group": { + "message": "Group" + }, + "groupAccessAll": { + "message": "This group can access and modify all items." + }, + "memberAccessAll": { + "message": "This member can access and modify all items." + }, + "moreFromBitwarden": { + "message": "More from Bitwarden" + }, + "switchProducts": { + "message": "Switch Products" + }, + "freeOrgInvLimitReachedManageBilling": { + "message": "Free organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.", + "placeholders": { + "seatcount": { + "content": "$1", + "example": "2" + } + } + }, + "freeOrgInvLimitReachedNoManageBilling": { + "message": "Free organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.", + "placeholders": { + "seatcount": { + "content": "$1", + "example": "2" + } + } + }, + "freeOrgMaxCollectionReachedManageBilling": { + "message": "Free organizations may have up to $COLLECTIONCOUNT$ collections. Upgrade to a paid plan to add more collections.", + "placeholders": { + "COLLECTIONCOUNT": { + "content": "$1", + "example": "2" + } + } + }, + "freeOrgMaxCollectionReachedNoManageBilling": { + "message": "Free organizations may have up to $COLLECTIONCOUNT$ collections. Contact your organization owner to upgrade.", + "placeholders": { + "COLLECTIONCOUNT": { + "content": "$1", + "example": "2" + } + } + } +} diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index dea950e7f7f..6787e87517e 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -1609,7 +1609,7 @@ "message": "Gelekte wachtwoorden" }, "exposedPasswordsReportDesc": { - "message": "Gelekte wachtwoorden zijn wachtwoorden die zijn ontdekt in bekende datalekken die publiekelijk zijn vrijgegeven of door hackers op het dark web worden verkocht." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "Gelekte wachtwoorden gevonden" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Neem Premium voor maar $PRICE$ /jaar of krijg Premium-accounts voor $FAMILYPLANUSERCOUNT$ gebruikers en ongelimiteerd delen met de family met ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families." + }, "addons": { "message": "Add-ons" }, @@ -5538,28 +5554,28 @@ "message": "Verbergen" }, "projects": { - "message": "Projects" + "message": "Projecten" }, "lastEdited": { - "message": "Last Edited" + "message": "Laatst bewerkt" }, "editSecret": { - "message": "Edit Secret" + "message": "Bewerk geheim" }, "addSecret": { - "message": "Add Secret" + "message": "Geheim toevoegen" }, "copySecretName": { - "message": "Copy Secret Name" + "message": "Kopieer geheime naam" }, "copySecretValue": { - "message": "Copy Secret Value" + "message": "Geheime waarde kopiëren" }, "deleteSecret": { - "message": "Delete Secret" + "message": "Geheim verwijderen" }, "deleteSecrets": { - "message": "Delete Secrets" + "message": "Geheimen verwijderen" }, "secretProjectAssociationDescription": { "message": "Kies projecten om het geheim aan te koppelen. Alleen gebruikers uit de organisatie met toegang tot deze projecten kunnen het geheim zien." @@ -5574,19 +5590,19 @@ "message": "Project" }, "editProject": { - "message": "Edit Project" + "message": "Bewerk project" }, "viewProject": { - "message": "View Project" + "message": "Bekijk project" }, "deleteProject": { - "message": "Delete Project" + "message": "Verwijder project" }, "deleteProjects": { - "message": "Delete Projects" + "message": "Verwijder projecten" }, "secret": { - "message": "Secret" + "message": "Geheim" }, "serviceAccount": { "message": "Service Account" diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 627569b951b..923071e32d3 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 320989e9644..e3d5367cf8c 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Przejdź na konto Premium za jedyne $PRICE$ rocznie lub kup konta Premium dla $FAMILYPLANUSERCOUNT$ użytkowników i nieograniczone udostępnianie rodzinne dzięki ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "planowi rodzinnemu Bitwarden." + }, "addons": { "message": "Dodatki" }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index d4af2e03e74..63882bd7bd7 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1609,7 +1609,7 @@ "message": "Relatório de Senhas Expostas" }, "exposedPasswordsReportDesc": { - "message": "Senhas expostas são senhas que foram descobertas em violações de dados conhecidas que foram divulgadas publicamente ou vendidas na \"dark web\" por hackers." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "Senhas Expostas Encontradas" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Complementos" }, diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 1a1a4a7a353..e6b9ff27842 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1609,7 +1609,7 @@ "message": "Relatório de palavras-passe expostas" }, "exposedPasswordsReportDesc": { - "message": "Exposed passwords are passwords have been uncovered in known data breaches that were released publicly or sold on the dark web by hackers." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "Palavras-passe expostas encontradas" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Extras" }, diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 1addf12605e..c4310196a79 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Add-on-uri" }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 480d8b24717..bf0b8fa9c8b 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Перейдите на Премиум всего за $PRICE$ /год или получите Премиум для $FAMILYPLANUSERCOUNT$ пользователей и неограниченный семейный обмен с ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "планом Bitwarden Families." + }, "addons": { "message": "Дополнения" }, @@ -5262,7 +5278,7 @@ "message": "Общий email домена" }, "catchallEmailDesc": { - "message": "Использовать общую почту домена." + "message": "Использовать общий email домена." }, "random": { "message": "Случайно", @@ -5562,13 +5578,13 @@ "message": "Удалить секреты" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Выберите проекты, с которыми будет связан секрет. Только пользователи организации, имеющие доступ к этим проектам, смогут увидеть секрет." }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "Введите или выберите проекты" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "Введите или выберите проекты" }, "project": { "message": "Проект" @@ -5832,7 +5848,7 @@ "message": "Эта пользователь может иметь доступ и изменять все элементы." }, "moreFromBitwarden": { - "message": "Дополнительная информация о Bitwarden" + "message": "Больше от Bitwarden" }, "switchProducts": { "message": "Изменить продукты" diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index f542c65ce74..cced7a5ba8d 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 969c444ffa9..39aaa5b8d7c 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Prejdite na prémium len za $PRICE$ /rok alebo získajte prémium kontá pre $FAMILYPLANUSERCOUNT$ používateľov a neobmedzené zdieľanie v rodine s ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden plán pre Rodiny." + }, "addons": { "message": "Doplnky" }, @@ -5562,13 +5578,13 @@ "message": "Delete Secrets" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "Vyberte projekty ku ktorým bude heslo patriť. Iba tí používatelia organizácie, ktorí majú prístup k zvoleným projektom budú môcť vidieť heslo." }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "Zadajte alebo vyberte Projekty" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "Zadajte alebo vyberte Projekt" }, "project": { "message": "Project" diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index bbd247e7b20..85fc447b606 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 473aef9c34b..90f6cf0a786 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Додаци" }, diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 37dcddccabb..5e32fd170b6 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Dodaci" }, diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 9152f17c9fa..df662ce400e 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -1609,7 +1609,7 @@ "message": "Rapport om avslöjade lösenord" }, "exposedPasswordsReportDesc": { - "message": "Avslöjade lösenord är lösenord som har äventyrats i kända dataintrång som släppts offentligt eller sålts av hackare på \"dark web\"." + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "Avslöjade lösenord hittades" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Tillägg" }, diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 048495a67be..721d86a092a 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 1416d03d830..89f1f024b2c 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 3117079f56b..6d9ca2b95b6 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Yalnızca $PRICE$/yıl karşılığında premium'a geçin veya $FAMILYPLANUSERCOUNT$ kullanıcıları için premium hesaplar ve bir kullanıcıyla sınırsız aile paylaşımı elde edin ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Aileleri planı." + }, "addons": { "message": "Eklentiler" }, @@ -5562,13 +5578,13 @@ "message": "Sırları Sil" }, "secretProjectAssociationDescription": { - "message": "Şifrenin ilişkilendirileceği projeleri seçin. Sadece erişimi olan organizasyon kullanıcıları bu projelere erişebilir ve şifreyi görebilir." + "message": "Sırrın ilişkilendirileceği projeleri seçin. Yalnızca bu projelere erişimi olan kuruluş kullanıcıları sırrı görebilir." }, "typeOrSelectProjects": { - "message": "Projelerinizi yazın ya da seçin" + "message": "Projeleri yazın veya seçin" }, "typeOrSelectProject": { - "message": "Projenizi yazın ya da seçin" + "message": "Projeyi yazın veya seçin" }, "project": { "message": "Proje" diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 11c64dbf561..ed0479e95cb 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Додатки" }, diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 37e4ce6ece6..5aa808b696d 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -609,19 +609,19 @@ "message": "Mật khẩu chính" }, "masterPassDesc": { - "message": "Mật khẩu chính là mật khẩu cho kho mật khẩu của bạn. Mật khẩu này rất quan trọng và bạn không nên quên nó của mình. Bạn sẽ không thể khôi phục lại mật khẩu chính của bạn nếu bạn quên nó." + "message": "Mật khẩu chính là mật khẩu bạn dùng để truy cập vào kho lưu trữ của bạn. Mật khẩu này rất quan trọng và bạn đừng nên quên mật khẩu chính này. Bạn sẽ không thể khôi phục mật khẩu chính trong trường hợp bạn quên nó." }, "masterPassImportant": { "message": "Master passwords cannot be recovered if you forget it!" }, "masterPassHintDesc": { - "message": "Gợi ý mật khẩu có thể giúp bạn nhớ lại mật khẩu chính của mình nếu bạn quên nó." + "message": "Gợi ý mật khẩu chính có thể giúp bạn nhớ lại mật khẩu của mình nếu bạn quên nó." }, "reTypeMasterPass": { - "message": "Vui lòng nhập lại mật khẩu chính" + "message": "Nhập lại mật khẩu chính" }, "masterPassHint": { - "message": "Gợi ý mật khẩu chính (không bắt buộc)" + "message": "Gợi ý mật khẩu chính (tùy chọn)" }, "masterPassHintLabel": { "message": "Gợi ý mật khẩu chính" @@ -924,10 +924,10 @@ "message": "Confirm format" }, "filePassword": { - "message": "File password" + "message": "Mật khẩu tập tin" }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "Xác nhận mật khẩu tập tin" }, "accountRestrictedOptionDescription": { "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." @@ -1055,7 +1055,7 @@ "message": "Thay đổi mật khẩu chính" }, "masterPasswordChanged": { - "message": "Đã thay đổi mật khẩu chính" + "message": "Đã lưu mật khẩu chính" }, "currentMasterPass": { "message": "Mật khẩu chính hiện tại" @@ -1163,7 +1163,7 @@ "message": "Nhập dữ liệu" }, "importError": { - "message": "Import error" + "message": "Lỗi nhập" }, "importErrorDesc": { "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." @@ -1196,10 +1196,10 @@ "message": "Chọn tập tin nhập" }, "chooseFile": { - "message": "Choose File" + "message": "Chọn tập tin" }, "noFileChosen": { - "message": "No file chosen" + "message": "Không có tệp nào được chọn" }, "orCopyPasteFileContents": { "message": "hoặc sao chép/dán để nhập nội dung file" @@ -1233,7 +1233,7 @@ "message": "Thay đổi ngôn ngữ của kho mạng." }, "enableFavicon": { - "message": "Show website icons" + "message": "Hiện biểu tượng trang web" }, "faviconDesc": { "message": "Show a recognizable image next to each login." @@ -1609,7 +1609,7 @@ "message": "Báo cáo mật khẩu bị rò rỉ" }, "exposedPasswordsReportDesc": { - "message": "Mật khẩu bị rò rĩ là mật khẩu đã bị hacker hack được trong các vụ rò rĩ dữ liệu được thông báo công khai hoặc được bán trên web đen( dark web) bởi hacker" + "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." }, "exposedPasswordsFound": { "message": "Phát hiện mật khẩu bị rò rĩ" @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "Addons" }, @@ -3680,7 +3696,7 @@ "message": "Tax information updated." }, "setMasterPassword": { - "message": "Thiết lập mật khẩu chính" + "message": "Đặt mật khẩu chính" }, "ssoCompleteRegistration": { "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 8a2c310bfd2..efc043dbe16 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden 家庭计划。" + }, "addons": { "message": "附加项目" }, @@ -5562,13 +5578,13 @@ "message": "删除机密" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret." + "message": "选择与机密相关联的项目。仅有权访问该项目的组织用户能查看该机密。" }, "typeOrSelectProjects": { - "message": "Type or select Projects" + "message": "输入或选择项目" }, "typeOrSelectProject": { - "message": "Type or select Project" + "message": "输入或选择项目" }, "project": { "message": "项目" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index c28ffc2e1a0..a22696950c8 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1822,6 +1822,22 @@ } } }, + "premiumPriceWithFamilyPlan": { + "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + }, + "familyplanusercount": { + "content": "$2", + "example": "6" + } + } + }, + "bitwardenFamiliesPlan": { + "message": "Bitwarden Families plan." + }, "addons": { "message": "附加項目" }, From 8121894b4468a61b65eb77aa0dbc282983f87618 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Jan 2023 14:22:49 +0100 Subject: [PATCH 166/205] Bumped desktop version to 2023.1.1 (#4463) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index f3b75f31c97..0482f9b91c4 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2023.1.0", + "version": "2023.1.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 0c31746a148..01e153472bd 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2023.1.0", + "version": "2023.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2023.1.0", + "version": "2023.1.1", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index ffc08270cf6..32c93dcb9d0 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2023.1.0", + "version": "2023.1.1", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 988b7991ba8..c4c997efdab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -217,7 +217,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2023.1.0", + "version": "2023.1.1", "hasInstallScript": true, "license": "GPL-3.0" }, From 6bb55c73207c19673b9f5e271759eb9ad8ed3ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Gon=C3=A7alves?= Date: Fri, 13 Jan 2023 15:40:47 +0000 Subject: [PATCH 167/205] SG-852 Removed msSaveOrOpenBlob (#4379) --- .../src/services/browserFileDownloadService.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/browser/src/services/browserFileDownloadService.ts b/apps/browser/src/services/browserFileDownloadService.ts index 273df1e22d3..89ae6722b59 100644 --- a/apps/browser/src/services/browserFileDownloadService.ts +++ b/apps/browser/src/services/browserFileDownloadService.ts @@ -29,16 +29,12 @@ export class BrowserFileDownloadService implements FileDownloadService { true ); } else { - if ((navigator as any).msSaveOrOpenBlob) { - (navigator as any).msSaveBlob(builder.blob, request.fileName); - } else { - const a = window.document.createElement("a"); - a.href = URL.createObjectURL(builder.blob); - a.download = request.fileName; - window.document.body.appendChild(a); - a.click(); - window.document.body.removeChild(a); - } + const a = window.document.createElement("a"); + a.href = URL.createObjectURL(builder.blob); + a.download = request.fileName; + window.document.body.appendChild(a); + a.click(); + window.document.body.removeChild(a); } } } From 3db82181aec7ace8f310c740a86e6e6a3a8e9bdf Mon Sep 17 00:00:00 2001 From: Brandon Maharaj Date: Fri, 13 Jan 2023 13:06:14 -0500 Subject: [PATCH 168/205] work: fallback value (#4429) --- .../organization-badge/organization-name-badge.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/vault/organization-badge/organization-name-badge.component.ts b/apps/web/src/app/vault/organization-badge/organization-name-badge.component.ts index e423a2cb8dc..33c659be559 100644 --- a/apps/web/src/app/vault/organization-badge/organization-name-badge.component.ts +++ b/apps/web/src/app/vault/organization-badge/organization-name-badge.component.ts @@ -33,7 +33,8 @@ export class OrganizationNameBadgeComponent implements OnInit { if (this.isMe) { this.color = await this.avatarService.loadColorFromState(); if (this.color == null) { - const userName = await this.tokenService.getName(); + const userName = + (await this.tokenService.getName()) ?? (await this.tokenService.getEmail()); this.color = Utils.stringToColor(userName.toUpperCase()); } } else { From 8e5d73dfc6b63fdc72ecfce3ff2eb3d4d78cb034 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 10:31:48 +0100 Subject: [PATCH 169/205] Bumped desktop version to 2023.1.2 (#4484) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 0482f9b91c4..2d8d1aa36c2 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2023.1.1", + "version": "2023.1.2", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 01e153472bd..e2abaf062ff 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2023.1.1", + "version": "2023.1.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2023.1.1", + "version": "2023.1.2", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 32c93dcb9d0..4b37292ba00 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2023.1.1", + "version": "2023.1.2", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index c4c997efdab..143abe51971 100644 --- a/package-lock.json +++ b/package-lock.json @@ -217,7 +217,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2023.1.1", + "version": "2023.1.2", "hasInstallScript": true, "license": "GPL-3.0" }, From c0a20e99367cc5f47082f1c512066d5bea5962aa Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 16 Jan 2023 12:11:06 +0100 Subject: [PATCH 170/205] [SM-347] Add Codeowners (#4457) * Add Codeowners --- .github/CODEOWNERS | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..48a87a94361 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# Please sort lines alphabetically, this will ensure we don't accidentally add duplicates. +# +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +bitwarden_license/bit-web/src/app/secrets-manager @bitwarden/pod-sm-dev From 11a30ea92c2b2c49ed2079be2dbcbd2608156273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Mon, 16 Jan 2023 18:45:20 +0100 Subject: [PATCH 171/205] Fix autobump workflow (#4488) --- .github/workflows/version-auto-bump.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index 7dee24e9202..5060156114b 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -25,8 +25,8 @@ jobs: env: RELEASE_TAG: ${{ github.ref }} run: | - CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/[a-z]*-v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/') - CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/[a-z]*-v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/') + CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/[a-z]*-v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/') + CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/[a-z]*-v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/') echo "Current Major: $CURR_MAJOR" echo "Current Patch: $CURR_PATCH" From a9db6b2d7d48d65c0bffe874424b49c7f01647db Mon Sep 17 00:00:00 2001 From: jcuenca95 <42175335+jcuenca95@users.noreply.github.com> Date: Tue, 17 Jan 2023 16:13:28 +0100 Subject: [PATCH 172/205] [PS-1872] remember me label triggers toggle (#4046) * remember me label triggers toggle * fix using new bitCheckbox * changes using merge * Update apps/web/src/app/accounts/login/login.component.html Co-authored-by: Daniel James Smith * CheckboxModule imported Co-authored-by: Daniel James Smith --- .../src/app/accounts/login/login.component.html | 16 ++++------------ apps/web/src/app/accounts/login/login.module.ts | 4 +++- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/apps/web/src/app/accounts/login/login.component.html b/apps/web/src/app/accounts/login/login.component.html index 7436bca7538..f12595ae418 100644 --- a/apps/web/src/app/accounts/login/login.component.html +++ b/apps/web/src/app/accounts/login/login.component.html @@ -32,18 +32,10 @@
-
- -
- - {{ "rememberEmail" | i18n }} - + + + {{ "rememberEmail" | i18n }} +
diff --git a/apps/web/src/app/accounts/login/login.module.ts b/apps/web/src/app/accounts/login/login.module.ts index 9ab8dfb3a1b..1dfbe925807 100644 --- a/apps/web/src/app/accounts/login/login.module.ts +++ b/apps/web/src/app/accounts/login/login.module.ts @@ -1,12 +1,14 @@ import { NgModule } from "@angular/core"; +import { CheckboxModule } from "@bitwarden/components"; + import { SharedModule } from "../../shared"; import { LoginWithDeviceComponent } from "./login-with-device.component"; import { LoginComponent } from "./login.component"; @NgModule({ - imports: [SharedModule], + imports: [SharedModule, CheckboxModule], declarations: [LoginComponent, LoginWithDeviceComponent], exports: [LoginComponent, LoginWithDeviceComponent], }) From f82a9f33bdaea46f2e6710f035bd1cb58257892b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Jan 2023 10:02:28 -0700 Subject: [PATCH 173/205] Bump Web version to 2023.1.1 (#4493) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/package.json | 2 +- package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 6b03c068620..a2764f4b79c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2023.1.0", + "version": "2023.1.1", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index 143abe51971..503ec6f6070 100644 --- a/package-lock.json +++ b/package-lock.json @@ -231,7 +231,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2023.1.0" + "version": "2023.1.1" }, "libs/angular": { "name": "@bitwarden/angular", From a6308042b6fff28dff97cfde2d8eca081ad6f376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hermann=20K=C3=A4ser?= Date: Wed, 18 Jan 2023 04:19:46 -0500 Subject: [PATCH 174/205] [PS-2263] Keeper CSV import: import TOTP to correct field (#4478) * Keeper CSV import: import TOTP to correct field * Fix small issue with notes import Notes field can be null, the ` + "\n"` coerces those to `"null"`. * Adds unit tests --- .../importers/keeper-csv-importer.spec.ts | 77 +++++++++++++++++++ .../test-data/keeper-csv/testdata.csv.ts | 4 + .../importers/keeper/keeper-csv-importer.ts | 13 +++- 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 libs/common/spec/importers/keeper-csv-importer.spec.ts create mode 100644 libs/common/spec/importers/test-data/keeper-csv/testdata.csv.ts diff --git a/libs/common/spec/importers/keeper-csv-importer.spec.ts b/libs/common/spec/importers/keeper-csv-importer.spec.ts new file mode 100644 index 00000000000..13935c8e974 --- /dev/null +++ b/libs/common/spec/importers/keeper-csv-importer.spec.ts @@ -0,0 +1,77 @@ +import { KeeperCsvImporter as Importer } from "@bitwarden/common/importers/keeper/keeper-csv-importer"; + +import { testData as TestData } from "./test-data/keeper-csv/testdata.csv"; + +describe("Keeper CSV Importer", () => { + let importer: Importer; + beforeEach(() => { + importer = new Importer(); + }); + + it("should parse login data", async () => { + const result = await importer.parse(TestData); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.name).toEqual("Bar"); + expect(cipher.login.username).toEqual("john.doe@example.com"); + expect(cipher.login.password).toEqual("1234567890abcdef"); + expect(cipher.login.uris.length).toEqual(1); + const uriView = cipher.login.uris.shift(); + expect(uriView.uri).toEqual("https://example.com/"); + expect(cipher.notes).toEqual("These are some notes."); + + const cipher2 = result.ciphers.shift(); + expect(cipher2.name).toEqual("Bar 1"); + expect(cipher2.login.username).toEqual("john.doe1@example.com"); + expect(cipher2.login.password).toEqual("234567890abcdef1"); + expect(cipher2.login.uris.length).toEqual(1); + const uriView2 = cipher2.login.uris.shift(); + expect(uriView2.uri).toEqual("https://an.example.com/"); + expect(cipher2.notes).toBeNull(); + + const cipher3 = result.ciphers.shift(); + expect(cipher3.name).toEqual("Bar 2"); + expect(cipher3.login.username).toEqual("john.doe2@example.com"); + expect(cipher3.login.password).toEqual("34567890abcdef12"); + expect(cipher3.notes).toBeNull(); + expect(cipher3.login.uris.length).toEqual(1); + const uriView3 = cipher3.login.uris.shift(); + expect(uriView3.uri).toEqual("https://another.example.com/"); + }); + + it("should import TOTP when present", async () => { + const result = await importer.parse(TestData); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.login.totp).toBeNull(); + + const cipher2 = result.ciphers.shift(); + expect(cipher2.login.totp).toBeNull(); + + const cipher3 = result.ciphers.shift(); + expect(cipher3.login.totp).toEqual( + "otpauth://totp/Amazon:me@company.com?secret=JBSWY3DPEHPK3PXP&issuer=Amazon&algorithm=SHA1&digits=6&period=30" + ); + }); + + it("should parse custom fields", async () => { + const result = await importer.parse(TestData); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.fields).toBeNull(); + + const cipher2 = result.ciphers.shift(); + expect(cipher2.fields.length).toBe(2); + expect(cipher2.fields[0].name).toEqual("Account ID"); + expect(cipher2.fields[0].value).toEqual("12345"); + expect(cipher2.fields[1].name).toEqual("Org ID"); + expect(cipher2.fields[1].value).toEqual("54321"); + + const cipher3 = result.ciphers.shift(); + expect(cipher3.fields[0].name).toEqual("Account ID"); + expect(cipher3.fields[0].value).toEqual("23456"); + }); +}); diff --git a/libs/common/spec/importers/test-data/keeper-csv/testdata.csv.ts b/libs/common/spec/importers/test-data/keeper-csv/testdata.csv.ts new file mode 100644 index 00000000000..a40e97ff3fd --- /dev/null +++ b/libs/common/spec/importers/test-data/keeper-csv/testdata.csv.ts @@ -0,0 +1,4 @@ +export const testData = `"Foo","Bar","john.doe@example.com","1234567890abcdef","https://example.com/","These are some notes.","" +"Foo","Bar 1","john.doe1@example.com","234567890abcdef1","https://an.example.com/","","","Account ID","12345","Org ID","54321" +"Foo\\Baz","Bar 2","john.doe2@example.com","34567890abcdef12","https://another.example.com/","","","Account ID","23456","TFC:Keeper","otpauth://totp/Amazon:me@company.com?secret=JBSWY3DPEHPK3PXP&issuer=Amazon&algorithm=SHA1&digits=6&period=30" +`; diff --git a/libs/common/src/importers/keeper/keeper-csv-importer.ts b/libs/common/src/importers/keeper/keeper-csv-importer.ts index 54ff4a3d4d9..ea082b99098 100644 --- a/libs/common/src/importers/keeper/keeper-csv-importer.ts +++ b/libs/common/src/importers/keeper/keeper-csv-importer.ts @@ -18,7 +18,12 @@ export class KeeperCsvImporter extends BaseImporter implements Importer { this.processFolder(result, value[0]); const cipher = this.initLoginCipher(); - cipher.notes = this.getValueOrDefault(value[5]) + "\n"; + + const notes = this.getValueOrDefault(value[5]); + if (notes) { + cipher.notes = `${notes}\n`; + } + cipher.name = this.getValueOrDefault(value[1], "--"); cipher.login.username = this.getValueOrDefault(value[2]); cipher.login.password = this.getValueOrDefault(value[3]); @@ -27,7 +32,11 @@ export class KeeperCsvImporter extends BaseImporter implements Importer { if (value.length > 7) { // we have some custom fields. for (let i = 7; i < value.length; i = i + 2) { - this.processKvp(cipher, value[i], value[i + 1]); + if (value[i] == "TFC:Keeper") { + cipher.login.totp = value[i + 1]; + } else { + this.processKvp(cipher, value[i], value[i + 1]); + } } } From dbb0bbb91c6e80f7331fdd5e89d9e02c889f2241 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 18 Jan 2023 13:20:14 -0500 Subject: [PATCH 175/205] adjust default kdf iterations to 350k (#4482) * adjust default kdf iterations to 350k * update test --- libs/common/spec/services/export.service.spec.ts | 4 ++-- libs/common/src/enums/kdfType.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/common/spec/services/export.service.spec.ts b/libs/common/spec/services/export.service.spec.ts index 92dbd16883e..78abf47a7ee 100644 --- a/libs/common/spec/services/export.service.spec.ts +++ b/libs/common/spec/services/export.service.spec.ts @@ -7,7 +7,7 @@ import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/enums/cipherType"; -import { KdfType } from "@bitwarden/common/enums/kdfType"; +import { KdfType, DEFAULT_KDF_ITERATIONS } from "@bitwarden/common/enums/kdfType"; import { Utils } from "@bitwarden/common/misc/utils"; import { Cipher } from "@bitwarden/common/models/domain/cipher"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; @@ -232,7 +232,7 @@ describe("ExportService", () => { }); it("specifies kdfIterations", () => { - expect(exportObject.kdfIterations).toEqual(100000); + expect(exportObject.kdfIterations).toEqual(DEFAULT_KDF_ITERATIONS); }); it("has kdfType", () => { diff --git a/libs/common/src/enums/kdfType.ts b/libs/common/src/enums/kdfType.ts index cc7fa7e0dcf..488010b1d2b 100644 --- a/libs/common/src/enums/kdfType.ts +++ b/libs/common/src/enums/kdfType.ts @@ -3,5 +3,5 @@ export enum KdfType { } export const DEFAULT_KDF_TYPE = KdfType.PBKDF2_SHA256; -export const DEFAULT_KDF_ITERATIONS = 100000; +export const DEFAULT_KDF_ITERATIONS = 350000; export const SEND_KDF_ITERATIONS = 100000; From 429b92003a3c2d2748476e14852bd1e4ec5e86ed Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 18 Jan 2023 21:51:37 +0300 Subject: [PATCH 176/205] Fix autofill of expiration date input for bank cards (#3768) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The problem was the incorrect identification of the input format. The input with `placeholder="ММ / ГГ"` got a value in `YYYY-MM` format, which is fallback in case when required format was not identified. It happened because `ММ` in the placeholder value had russian characters, but actual constant has english ones. --- apps/browser/src/services/autofillConstants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/services/autofillConstants.ts b/apps/browser/src/services/autofillConstants.ts index 94b54a71505..be29838646e 100644 --- a/apps/browser/src/services/autofillConstants.ts +++ b/apps/browser/src/services/autofillConstants.ts @@ -255,7 +255,7 @@ export class CreditCardAutoFillConstants { // Each index represents a language. These three arrays should all be the same length. // 0: English, 1: Danish, 2: German/Dutch, 3: French/Spanish/Italian, 4: Russian, 5: Portuguese - static readonly MonthAbbr = ["mm", "mm", "mm", "mm", "mm", "mm"]; + static readonly MonthAbbr = ["mm", "mm", "mm", "mm", "мм", "mm"]; static readonly YearAbbrShort = ["yy", "åå", "jj", "aa", "гг", "rr"]; static readonly YearAbbrLong = ["yyyy", "åååå", "jjjj", "aa", "гггг", "rrrr"]; } From 98cc28f3c3395160bdf4d15290b4833d8380dc5b Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 19 Jan 2023 10:29:20 +0100 Subject: [PATCH 177/205] [SM-447] Enable Secrets Manager in QA (#4485) --- apps/web/config/qa.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/config/qa.json b/apps/web/config/qa.json index ab22d03514c..3632c696856 100644 --- a/apps/web/config/qa.json +++ b/apps/web/config/qa.json @@ -10,7 +10,7 @@ "proxyEvents": "https://events.qa.bitwarden.pw" }, "flags": { - "secretsManager": false, + "secretsManager": true, "showPasswordless": true } } From f4dc7ca8b4983251a6e75f8cd366e6240bc89b8f Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 19 Jan 2023 17:01:07 +0100 Subject: [PATCH 178/205] [EC-647] OAVR v2 Feature Branch Merge (#3882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [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-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 * Remove old reference to bit-submit-button that no longer exists (#3927) * [EC-657] Hide Billing History and Payment Method for selfhosted orgs (#3935) * Merge master into feature/org-admin-refresh (#4072) * Remove DDG forwarder from SH (#3888) * [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 * Don't refresh org vault on filter change (#3879) * Autosync the updated translations (#3914) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#3915) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#3916) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Revert "[PS-1465] Fix #2806 - The "Import Data" page's file selector button cannot be translated (#3502)" (#3900) This reverts commit 768de03269882d0cd5f3b0d7803c819eaa219010. * Autosync the updated translations (#3919) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * [SM-260] Hide email verification prompt if already verified (#3922) Co-authored-by: Sammy Chang * 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 * 400s only log out on invalid grant error (#3924) * Fix rust tests apt-get install (#3933) * Added focus to the email and master password fields (#3934) * Ps 1754 community pr reviewed (#3929) * community PR reviewed, Update search cancel button to be visible in all themes * community PR reviewed, Update search cancel button to be visible in all themes 2 * Update search cancel button to be visible in all themes (#3876) * Adding the 'libs/**' directory back to the Desktop build pipeline PR trigger list (#3938) * Re-\added the focusInput method to allow desktop build run (#3937) * [EC-522] Improve handling of rxjs subjects (#3772) * [EC-522] feat: no public rxjs subjects * [EC-522] feat: improve null handling * [EC-552] fix: init subject with empty set instead of null * [EC-552] fix: don't push null into account subject * [EC-522] feat: remove null filter * [EC-641] Browser Ext UI Update (#3842) * more css changes * add icon button hover * Update apps/browser/src/popup/scss/box.scss Co-authored-by: Oscar Hinton * Update apps/desktop/src/scss/box.scss Co-authored-by: Oscar Hinton * feedback updates * restore desktop pseudo rule * update to include some variable fixes and deletions * updates per oscar * feedback updates more universal variable, adjusted box padding (per Kyle), and aligned footer text * changes per product design added border for selects, border around generator, and hover for solarizeddark * add more helper text space below for visual separation * group new variable * login page button fix Dflinn found an odd margin on the login page * Revert "Merge branch 'master' into browser-ext-ui-update-test" This reverts commit b8007102f9c91cac7dd1b4dc6de1c9ac878d2575, reversing changes made to 246768cb12d35bd1f538aa75371154e115aeaadf. * fix button height * revert file changes * test adjustments Co-authored-by: Oscar Hinton Co-authored-by: Kyle Spearrin * [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 * Tell eslint & prettier to ignore storybook-static (#3946) * [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 * [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 * Add padding to top of Safari extension (#3949) * Use correct provider icon instead of bank icon (#3950) * Fix undefined property error in event logs (#3947) EventService.policies was undefined because the service was erroneously using ngOnInit to subscribe to the policies observable * PS-1763 - handle undefined locale value that exists before a user sets their language (#3952) * Autosync the updated translations (#3968) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#3967) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * fixed typo in event log (#3962) * Org admin refresh translation nitpicks (#3971) * Fix use of personal in favor of individual vault * Fix capitalization according to #3577 * Fix capitalization on organizationInfo * Autosync the updated translations (#3974) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#3973) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * add csp and only pass hostname to duo init (#3972) * add csp and only pass hostname to duo init * expand style-src * Update apps/web/src/connectors/duo.html Co-authored-by: Oscar Hinton Co-authored-by: Oscar Hinton * Move hint button out of the formfield (#3960) * [PS-1734] Send saved urls to autofill script (#3861) * Send all saved url to autofill script * Handle array of matched urls in content script * Prompt at most once to override insecure autofill * Do not send never match URIs to content script We know these URIs did not cause the autofill match, so we can safely remove these from the list of potential matches. * [PS-1804] Display Organization tab for users with custom permissions (#3980) * [EC-584] Fixed OrganizationExportResponse to correctly parse data (#3641) * [EC-584] Fixed OrganizationExportResponse to correctly parse data and use CollectionResponse and CipherResponse constructors * [EC-584] Removed ListResponse from OrganizationExportResponse properties * Bumped web version to 2022.10.3 (#3957) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Revert "Bumped web version to 2022.10.3 (#3957)" This reverts commit 5d8d547cd2e0fae7255d29536ad00ee00fbfa514. * Web version bump to 2022.11.0 for QA testing * Revert "Web version bump to 2022.11.0 for QA testing" This reverts commit 484db431ed5ef7f115084e11143bbce1b8c82619. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Joseph Flinn * [EC-678] [EC-673] Fix active tab not showing selected while in child route (#3964) * [PS-1114] hide reporting sidebar if only events * [PS-1114] add orgRedirectGuard * [PS-1114] highlight tabs based on route subset * [PS-1114] redirect to correct child route on tab - Use new OrgRedirectGuard * [PS-1114] add settings redirect using guard - refactored guard to accept array of strings * [EC-678] [EC-673] remove remaining methods * [EC-678][EC-673] address PR feedback - change switch to if statements - remove ternary * [EC-672] Update SSO login page language (#3997) - Replace 'Organization Identifier' with 'SSO identifier' - Sentence case 'SSO identifier' - Add 'SSO' to SSO login page helper text * Autosync the updated translations (#3969) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * [EC-661] Add web worker code bundles to Safari browser extension (#3986) * Make browser bundle encrypt-worker.ts into a single named file * Add encrypt-worker bundle to xcode proj * Fixed EC reported event log copy bugs (#3977) * [EC-645] fix: web payment component breaking storybook compilation (#3906) * add run-name for releases to include their workflow trigger (#3996) * add run-name for releases to include their workflow trigger * add edit for linter error * Update .github/workflows/release-web.yml Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> * Extract and fix trigger for PR auto responses (Translation-PRs) (#3992) * Extract and fix trigger for PR auto responses * Fix permission used for job * [EC-650] Revert observable usage from ImportComponent (#4010) * Run enforce labels workflow on version bump in clients repo (#4006) * Fix version bump to run enforce labels workflow * Add login to Azure * Trigger enforce labels manually from bump version workflow * Update .github/workflows/enforce-labels.yml Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> * Update .github/workflows/version-bump.yml Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> * [EC-670] Update Members tab to support flex wrap (#4003) Use tailwind classes to style the Members page header so that it supports wrapping the controls to a new line should they exceed the width of the container. * [PS-1841] Fix org-* commands for CLI (#4013) * Add getFromState method * Added a method for CLI to get an org from state * Converted all CLI calls to `.get()` * Used `.getFromState` instead of `.get` * Deprecate getFromState method * Remove local vaultFilter (#4014) * Use vault filter item from vaultFilterService * [PS-1843] Sort organizations in `buildOrganizations` (#4015) * Sort organizations in buildOrganizations * Add sort by name to Organization Switcher * [EC-675] Display the Event for “Viewed Card Number for item item-identifier” (#3976) * [EC-675] Add missing Event capture for viewing item Card Number * [EC-675] Fix correct event type for viewing item Card Number * Update apps/web/src/locales/en/messages.json Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * [EC-449] Event log user for SCIM events (#3643) * [EC-449] Added EventSystemUser Enum and added systemUser property to eventResponse * [EC-449] Add systemUser property to BaseEventsComponent, EventExport and EventView * [EC-449] Set EventSystemUser as string on EventExport * [EC-449] Remove systemUser from EventExport * [EC-449] Rename EventSystemUser file to lowercase * [EC-449] Force git to rename EventSystemUser file * [EC-449] Rename EventSystemUser file to event-system-user.ts * [EC-449] Fix EventSystemUser reference on EventsComponent * [EC-449] Move installationId username logic to BaseEventsComponent * Update libs/common/src/enums/event-system-user.ts Add a note to warn about using the Enum key in the UI. Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * [EC-449] Remove EventSystemUser from provider events. Remove nested condition on events component Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * [PS-1840] - fix for covered dropdown on empty vault (#4019) * fix for covered dropdown on empty vault This could be done one of 2-3 ways. I think this might be the least problematic, but could also be done with just changing "position: absolute" to "relative on the ".no-items" class - base.css:461 For some reason, I'm unable to load the spinner to test. * rename class * Remove uses of rxjs in CLI (#4028) * [SM-327] Electron hard reset (#3988) * Add folders to whitelist (#3994) * Defect/sg 650 desktop pw/passphrase gen not auto updating on min value change (#4032) * SG-650 - Desktop - Pw Generation - Min value ctrls now use (change) instead of (blur) for better responsiveness when using arrows on input or arrow keys. Note: (input) has change detection issues for resetting the value to either max pw length or max value of 9 + passwordGeneration.service logic possibly needs refactoring to either enforce max of 9 or not * SG-650 - Desktop - Passphrase Gen - min words now uses (change) instead of (blur) for better responsiveness * Autosync the updated translations (#4035) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#4036) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Autosync the updated translations (#4037) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> * Expand serve origin protection warning (#4024) This warning was kept vague during fix rollout, but now that we're more than a release past, we can expand the explanation. * [SM-340] Fix share modal not closing on cancel (#4041) * [EC-739 / EC-740] Add null check to policyFilter on PolicyService (#4039) * [EC-739 / EC-740] Add null check to policyFilter on PolicyService * [EC-739 / EC-740] Add unit tests for policy filter * [PS-1805] BEEEP: Renamed importers based on agreed naming-convention (#3978) * Rename all importer related files Renamed all files based on our naming convention which we decided on with https://github.com/bitwarden/adr/blob/master/decisions/0012-angular-filename-convention.md * Removed entries from whitelist-capital-letters.txt * Rename missing safeInCloud test data * Fix broken import * Renamed folders (removed capital letters) * Fix filename of BitwardenCsvImporter * Fix imports of onepassword mac/win importer tests * Remove already renamed folders from whitelist * Rename dashlaneImporters to dashlane Rename the folder Fix all the imports Remove dashlaneImporters from white-list * Rename keeperImporters to keeper Rename the folder Fix all the imports Remove keeperImporters from white-list * Rename onepasswordImporters to onepassword Rename the folder Fix all the imports Remove onepasswordImporters from white-list * Rename safeinCloud test data folder * Fix onepassword importer type imports * [EC-744] Revert PolicyService back to clearing DecryptedPolicies on StateService (#4042) * [EC-746] Call BaseAddEditComponent.ngOnInit on Desktop AddEditComponent (#4044) * PS-1798 - ensure admin users can edit ciphers (#4025) * Use loginService to get and set remember email values (#3941) * SG-428 - Browser Extension - Send - Expiration / Deletion date calendar icon +… (#4034) * Browser Extension - Send - Expiration / Deletion date calendar icon + datepicker pop up now respect theme better in Chrome / Chromium based browsers and Safari (Firefox datepicker pop up doesn't seem to have an easy mechanism for theming) * SG-428 - Extension - Iconography for date inputs for Chromium browsers now reflects theme colors properly + hover states; icon not shown on non-Chromium browsers * Variables.scss - ran prettier locally after tweaking comments to pass eslint checks * [EC-743] Call super to ngOnInit to include policy observable changes (#4047) * Hide My Vault if Remove Individual Vault is on (#4052) * Devops 1039 update release flow dry run step names (#4016) * Updated workflows to not create Github deployment on Dry Run. (#4049) * Add organization-options menu to single org (#3678) (#4051) Re-apply commit 7c3255d (#3678) which was accidentally reverted by the Org Admin Refresh changes in commit 09c3bc8 (#3925) * SG-725 - Desktop - Moved DuckDuckGo setting down so that the Biometric browser settings are not separated (#4059) * [EC-750] Specify organizationId for credit and adjust payment components (#4061) * [SM-330] Disable managed environments for safari (#3953) * [EC-665] Fix biometrics button style (#3979) * fix biometrics button style * expand button to fill space this is a result of it being used outside the box-content * remove padding from box-footer * Added Mastodon to follow us menu (#4029) * Add branch check for Staged Rollout Desktop workflow (#4062) * [PS-1783] Fix file selector input bug from PS-1465 ( #3502 ) (#3928) * Fix file selector input * Add file selector state changes back * Remove async pipe * Revert "[EC-646] Org Admin Vault Refresh November Release Prep (#3913)" This reverts commit 4b57d28e28d852fb6d148403d0fa5b2cf535b76a. * [EC-646] Move missing billing components Some billing components were in the org settings module and needed to be moved the org billing module (cherry picked from commit 1c11695f4621a38a7429f0005e2a0ce81d3bb130) * [EC-646] Cherry-pick ae39afe to fix tab text color (cherry picked from commit 467f584b9e1a738c18e36c9a8b7337eb2ceb9117) * Make destroy$ protected to fix linting error Signed-off-by: Jacob Fink Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> Co-authored-by: Daniel James Smith Co-authored-by: Oscar Hinton Co-authored-by: Sammy Chang Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com> Co-authored-by: Gbubemi Smith Co-authored-by: Addison Beck Co-authored-by: Robyn MacCallum Co-authored-by: gbubemismith Co-authored-by: Kyle Spearrin Co-authored-by: Michał Chęciński Co-authored-by: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Co-authored-by: Scott McFarlane <91044021+scottmondo@users.noreply.github.com> Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Co-authored-by: Andreas Coroiu Co-authored-by: DanHillesheim <79476558+DanHillesheim@users.noreply.github.com> Co-authored-by: Kyle Spearrin Co-authored-by: Vincent Salucci Co-authored-by: Andreas Coroiu Co-authored-by: Jake Fink Co-authored-by: Thomas Rittson Co-authored-by: dgoodman-bw <109169446+dgoodman-bw@users.noreply.github.com> Co-authored-by: Danielle Flinn <43477473+danielleflinn@users.noreply.github.com> Co-authored-by: Matt Gibson Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> Co-authored-by: Joseph Flinn Co-authored-by: Opeyemi <54288773+Eebru-gzy@users.noreply.github.com> Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Co-authored-by: Ash Leece * [EC-16] Implement new Groups Tab (#3563) * [EC-16] Cleanup RxJS linting problems * [EC-16] Update Group tab to use table component and show collections. * [EC-16] Extract interface from GroupResponse and use it in the view * [EC-16] Remove heading underline * [EC-16] Cleanup i18n * [EC-16] More i18n cleanup * [EC-16] Fix bulk group request type name * [EC-16] Rename group details type * [EC-86] Clear collectionMap before populating it with new collections * [EC-86] Update initialization/loading logic to make better use of the Observable pattern * [EC-86] Make table cells use a pointer cursor * [EC-86] Use bitIconButton for row menu triggers * [EC-86] Refactor GroupDetailsRow interface to wrap GroupDetailsResponse. Remove response model interfaces. Cleanup GroupsComponent. * [EC-86] Add bit-badge-list component and tweak BadgeModule to support both the component and directive. Update mockI18nService to support templated strings. * [EC-86] Cleanup badge color and bitIconButton classes * [EC-86] Cleanup more styles * [EC-86] Add GroupApiService Add a new GroupApiService to replace Group Api calls in the ApiService. * [EC-86] Revisions for badge-list implementation. - Remove `| null` for maxItems according to ADR-0014 - Remove custom setter for items - Use ngOnChanges to update filteredItems - Fix sr-only tailwind class and show screen reader comma after last item if truncated. * [EC-86] Refactor badge-list module/component - Move the badge list component to its own module. - Extract badge list stories from badge stories. - Cleanup bade stories and module after refactor. * [EC-86] Refactor/rename GroupApiService - Re-name GroupApiService to GroupService as there is no need for a separate Api service (no sync or local data for admin services) - Add GroupView for use in the GroupService instead of raw API models - Update views to use GroupView instead of raw GroupResponse models * [EC-86] Refactor group API request models - Move organizationGroupBulkRequest to group requests folder - Fix relative imports in GroupService * [EC-86] Fix linting errors * Fix tab item text color Tab item text color broke after a merge from master and needs a fix to account for bootstrap styles in Web. * [EC-86] Rename new files using kebab-case * [EC-86] Fix group view file name * [EC-86] Fix group request/response file names * [EC-86] Cleanup badge stories per review suggestions * [EC-86] Use inline-flex for badge list container * [EC-86] Move GroupService and Views to Web org module - Move GroupService and GroupServiceAbstraction to Organization Module - Add GroupService provider to Organization Module - Move collection-add-edit.component, user-groups.component, group-add-edit.component, and groups.component into Organization Module as they now depend on GroupService - Remove moved components from Loose Component module * [EC-86] Fix Group table search Adds the id and name properties to GroupDetailsRow to support using the searchPipe (which cannot access nested values such as details.name for filtering). * [EC-86] Fix badge story controls * [EC-87] Edit Group Dialog (#3651) * [EC-87] Update the edit dialog to use content tabs * [EC-87] WIP FormListSelection abstract controller * [EC-87] WIP FormListSelection for members and collections * [EC-87] More WIP on FormListSelection * [EC-87] WIP Working FormSelectionList with initial value support * [EC-87] WIP SelectionList without FormControls and with i18n support for sorting * [EC-87] Final sorted SelectionList with FormArray support * [EC-87] Extract and document FormSelectionList * [EC-87] Functional edit group modal * [EC-87] Remove button icon padding for bitButton directives * [EC-87] Use new disablePadding attribute for Dialog component * [EC-87] Some more cleanup and finetuning * [EC-87] Move enum declaration to top * [EC-87] Remove inline style from access selector * [EC-87] Move Group components into Organization Module * [EC-87] Add MultiSelectModule to Shared Web module * [EC-87] Integrate AccessSelector component in GroupAddEdit modal - Remove duplicate permission / selection readonly helpers from GroupAddEdit component - Use access item views/values for collection and member lists - Replace access selector HTMl with the AccessSelector component * [EC-87] Update Group collections column to open Collection tab * [EC-87] Remove old FormSelectionList file * [EC-87] Fix missed file import changes after merge * [EC-87] Remove GroupAddEditComponent modal service registration Groups component is now using the DialogService which does not require explicit registration for lazy loaded components. * [EC-87] Use injected DIALOG_DATA for GroupAddEdit component - Add types for the GroupAddEdit dialog params, result, and tab indices - Add strongly typed helper method to open GroupAddEdit dialogs - Remove @Input()/@Output() properties. Replaced with the injected DIALOG_DATA params instead - Use dialogRef.close() and result type instead of event emitters * [EC-87] Rename collection tab type to collections * [EC-87] Refactor postGroup() and putGroup() from ApiService - Move postGroup() and putGroup() methods to GroupService - Remove postGroup() and putGroup() from ApiService - Move GroupResponse and GroupRequest into Web (from lib/common) * [EC-87] Remove required attribute * [EC-87] Use PascalCase for template Enums * [EC-87] Use group modal tab enum in template * [EC-87] Convert dialog result to promise * [EC-87] Refactor dialog positionStrategy - Add .top() to position strategy to allow clicking the backdrop to close the dialog - Move the positionStrategy option into the openGroupAddEditDialog helper * [EC-87] Remove [preserveContent] from tab group * [EC-87] Use new CL async actions - Update handlers to be arrow-functions - Remove old form and delete promises - Use [bitSubmit] directive on form - Use bitFormButton directive and [bitAction] for submit and delete buttons - Remove delete/spinner bwi icons as they are handled by the new async directives * [EC-87] Introduce CollectionAccessSelectionView Use a new view to replace the SelectionReadonlyResponse/Request classes. * [EC-87] Use new access selection view in GroupView - Change the collections type - Add members list to make the view more complete - Update the static fromResponse helper to properly map the GroupDetailsResponse to the new access selection view - Update access selector helpers to use new access selection view instead of response/request models * [EC-87] Update GroupService to have a single save() method that accepts a GroupView - Add save() method that checks for existing group id to determine which API method to use - Make post/put group methods private * [EC-87] Utilize the new save() method in the group modal * [EC-87] Use observables for fetching data - Introduce 3 observables for collections, members, and group details - Combine and subscribe to those observables in ngOnInit - Add destroy$ subject - Inject changeDetectorRef to handle quirk of patching the AccessSelector value before available items are set * [EC-73] edit collection modal (#3638) * [EC-16] Cleanup RxJS linting problems * [EC-16] Update Group tab to use table component and show collections. * [EC-16] Extract interface from GroupResponse and use it in the view * [EC-16] Remove heading underline * [EC-16] Cleanup i18n * [EC-16] More i18n cleanup * [EC-16] Fix bulk group request type name * [EC-16] Rename group details type * [EC-73] feat: add inital version of modal using dialog service * [EC-73] feat: create story for dialog * [EC-73] feat: setup story with support for injected data * [EC-73] feat: add inital version of subtitle * [EC-73] feat: add tabs * [EC-73] feat: initial version of collection info form * [EC-73] feat: start of working form * [EC-73] feat: add custom form validator * [EC-73] fix: dialog directive names after rebase * [EC-73] feat: use custom validator * [EC-73] fix: story * [EC-73] feat: allow parent picking * [EC-73] feat: remove tabs to allow for merging * [EC-73] feat: extend story with new and edit dialogs * [EC-73] feat: change title depending on if editing or not * [EC-73] fix: parent not connected to form * [EC-73] feat: add organizationId to dialog data * [EC-73] feat: only allow nesting within collections with access * [EC-73] feat: handle loading with spinner * [EC-73] feat: update collections on submit * [EC-73] feat: reload on save * [EC-73] feat: update story to work with latest changes * [EC-73] feat: always fetch collections from server * [EC-73] fix: do not submit if form invalid * [EC-73] feat: create new collections using new ui * [EC-73] fix: external id not being saved * [EC-73] chore: move calls to separete collection admin service * [EC-73] feat: use new admin views * [EC-73] feat: implement deletion * [EC-73] feat: add support for collection details in service * [EC-73] fix: story * [EC-73] fix: cancel button * [EC-73] feat: re-add tabs * [EC-73] fix: jslib service collection deps * [EC-73] chore: rename component to collection-dialog * [EC-73] chore: clean up collection api service which was replaced * [EC-73] chore: restore collection.service * [EC-73] chore: restore dialog component changes * [EC-73] fix: move subscription to ngOnInit * [EC-73] feat: disable padding when using tabbed content * [EC-73] fix: new lint rules after merge * 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-86] Clear collectionMap before populating it with new collections * [EC-86] Update initialization/loading logic to make better use of the Observable pattern * [EC-86] Make table cells use a pointer cursor * [EC-86] Use bitIconButton for row menu triggers * [EC-86] Refactor GroupDetailsRow interface to wrap GroupDetailsResponse. Remove response model interfaces. Cleanup GroupsComponent. * [EC-86] Add bit-badge-list component and tweak BadgeModule to support both the component and directive. Update mockI18nService to support templated strings. * [EC-86] Cleanup badge color and bitIconButton classes * [EC-86] Cleanup more styles * [EC-86] Add GroupApiService Add a new GroupApiService to replace Group Api calls in the ApiService. * [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-86] Revisions for badge-list implementation. - Remove `| null` for maxItems according to ADR-0014 - Remove custom setter for items - Use ngOnChanges to update filteredItems - Fix sr-only tailwind class and show screen reader comma after last item if truncated. * [EC-86] Refactor badge-list module/component - Move the badge list component to its own module. - Extract badge list stories from badge stories. - Cleanup bade stories and module after refactor. * [EC-86] Refactor/rename GroupApiService - Re-name GroupApiService to GroupService as there is no need for a separate Api service (no sync or local data for admin services) - Add GroupView for use in the GroupService instead of raw API models - Update views to use GroupView instead of raw GroupResponse models * [EC-86] Refactor group API request models - Move organizationGroupBulkRequest to group requests folder - Fix relative imports in GroupService * [EC-86] Fix linting errors * Fix tab item text color Tab item text color broke after a merge from master and needs a fix to account for bootstrap styles in Web. * [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-73] chore: re-add collections page * [EC-86] Rename new files using kebab-case * [EC-73] chore: move component to shared org module * Fix MultiSelect component styles and CSP error (#3841) * Update Web styles and CSP to support MultiSelect component - Include the MultiSelect module in the CL barrel file of exports - Import the MultiSelect scss into the Web styles.scss - Add the necessary sha256 hash to webpack CSP policy to support ngSelect inline styles * Undo removal of 127.0.0.1 from webpack CSP (cherry picked from commit 3ed1221f7f150928612f3fab01a2ae63a39f781a) * [EC-73] feat: add empty access selector * [EC-73] feat: add groups to access selector * [EC-73] chore: improve storybook support * [EC-73] feat: tweak item assignment * [EC-73] feat: add support for showing users * [EC-73] feat: use async actions * [EC-73] chore: clean up casting * [EC-73] fix: permissions not loading correctly in access selector * [EC-73] feat: implement saving group permissions * [EC-73] feat: rename to collection access selection view * [EC-73] feat: save users as well * [EC-73] fix: access selector usage * [EC-73] feat: new collection creation * [EC-73] feat: fetch users from collection details * [EC-73] chore: clean up * [EC-73] fix: circular dependency issues * [EC-73] fix: import shared module directly to workaround build issues * [EC-73] fix: missing dependencies in story * [EC-73] chore: move story * [EC-73] fix: manual cherry pick permission bug fix * [EC-73] feat: hide delete button if no permission * [EC-73] feat: properly handle orgs without groups * [EC-73] fix: use correct functions in template * [EC-73] feat: properly handle non-existing parent * [EC-73] chore: use double ngIf instead of else template * [EC-73] fix: add type to dialog ref * [EC-73] fix: restrict field modifiers * [EC-73] fix: use result enum directly * [EC-73] fix: simplify mapping logic * [EC-73] * [EC-73] feat: add story for free orgs without groups * [EC-73] fix: parametrized i18n * [EC-73] feat: create new shared org module * [EC-73] feat: move collection dialog to shared * [EC-73] feat: move access selector to shared * [EC-73] feat: create core organization module * [EC-73] feat: move collection admin service to web * [EC-73] feat: move collection admin views to web * [EC-73] fix: missing i18n * [EC-73] fix: refactor for type safety * [EC-73] fix: storybook not compiling again * [EC-73] feat: use helper function to open dialog * [EC-73] chore: remove comment * [EC-73] fix: revert permission fix * [EC-73] fix: only show delete if in edit mode * [EC-73] chore: remove ngIf else in template * [EC-73] fix: add missing appA11yTitle * [EC-73] chore: rename remove to delete * [EC-73] chore: refactor ngOnInit * [EC-73] fix: dialog position strategy * [EC-73] fix: revert spinner to old way of doing it Signed-off-by: Jacob Fink Co-authored-by: Shane Melton Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Thomas Rittson * Revert "[EC-73] edit collection modal (#3638)" This reverts commit 39655ebe29b9921fdbd6843cad1468dcf1509729. * Merge EC-73 Again After Rebase (#4104) * [EC-73] feat: add inital version of modal using dialog service * [EC-73] feat: create story for dialog * [EC-73] feat: setup story with support for injected data * [EC-73] feat: add inital version of subtitle * [EC-73] feat: add tabs * [EC-73] feat: initial version of collection info form * [EC-73] feat: start of working form * [EC-73] feat: add custom form validator * [EC-73] fix: dialog directive names after rebase * [EC-73] feat: use custom validator * [EC-73] fix: story * [EC-73] feat: allow parent picking * [EC-73] feat: remove tabs to allow for merging * [EC-73] feat: extend story with new and edit dialogs * [EC-73] feat: change title depending on if editing or not * [EC-73] fix: parent not connected to form * [EC-73] feat: add organizationId to dialog data * [EC-73] feat: only allow nesting within collections with access * [EC-73] feat: handle loading with spinner * [EC-73] feat: update collections on submit * [EC-73] feat: reload on save * [EC-73] feat: update story to work with latest changes * [EC-73] feat: always fetch collections from server * [EC-73] fix: do not submit if form invalid * [EC-73] feat: create new collections using new ui * [EC-73] fix: external id not being saved * [EC-73] chore: move calls to separete collection admin service * [EC-73] feat: use new admin views * [EC-73] feat: implement deletion * [EC-73] feat: add support for collection details in service * [EC-73] fix: story * [EC-73] fix: cancel button * [EC-73] feat: re-add tabs * [EC-73] fix: jslib service collection deps * [EC-73] chore: rename component to collection-dialog * [EC-73] chore: clean up collection api service which was replaced * [EC-73] chore: restore collection.service * [EC-73] chore: restore dialog component changes * [EC-73] fix: move subscription to ngOnInit * [EC-73] feat: disable padding when using tabbed content * [EC-73] chore: re-add collections page * [EC-73] chore: move component to shared org module * [EC-73] feat: add empty access selector * [EC-73] feat: add groups to access selector * [EC-73] chore: improve storybook support * [EC-73] feat: tweak item assignment * [EC-73] feat: add support for showing users * [EC-73] feat: use async actions * [EC-73] chore: clean up casting * [EC-73] fix: permissions not loading correctly in access selector * [EC-73] feat: implement saving group permissions * [EC-73] feat: rename to collection access selection view * [EC-73] feat: save users as well * [EC-73] fix: access selector usage * [EC-73] feat: new collection creation * [EC-73] feat: fetch users from collection details * [EC-73] chore: clean up * [EC-73] fix: circular dependency issues * [EC-73] fix: import shared module directly to workaround build issues * [EC-73] fix: missing dependencies in story * [EC-73] chore: move story * [EC-73] feat: hide delete button if no permission * [EC-73] feat: properly handle orgs without groups * [EC-73] fix: use correct functions in template * [EC-73] feat: properly handle non-existing parent * [EC-73] chore: use double ngIf instead of else template * [EC-73] fix: add type to dialog ref * [EC-73] fix: restrict field modifiers * [EC-73] fix: use result enum directly * [EC-73] fix: simplify mapping logic * [EC-73] * [EC-73] feat: add story for free orgs without groups * [EC-73] fix: parametrized i18n * [EC-73] feat: create new shared org module * [EC-73] feat: move collection dialog to shared * [EC-73] feat: move access selector to shared * [EC-73] feat: create core organization module * [EC-73] feat: move collection admin service to web * [EC-73] feat: move collection admin views to web * [EC-73] fix: missing i18n * [EC-73] fix: refactor for type safety * [EC-73] fix: storybook not compiling again * [EC-73] feat: use helper function to open dialog * [EC-73] chore: remove comment * [EC-73] fix: only show delete if in edit mode * [EC-73] chore: remove ngIf else in template * [EC-73] fix: add missing appA11yTitle * [EC-73] chore: rename remove to delete * [EC-73] chore: refactor ngOnInit * [EC-73] fix: dialog position strategy * [EC-73] fix: revert spinner to old way of doing it * Fix remaining errors after rebase/merge * fix: import shared module directly Co-authored-by: Andreas Coroiu * Fix missing Access Selector Module after merge * remove overlay to center dialogs again (#4146) * [EC-547] members details dialog improvements (#4161) * [EC-547] feat: mostly migrate to new CL dialogs * [EC-547] feat: move dialog to separate module * [EC-547] chore: rename to user dialog component * [CL-547] feat: replace footer buttons with CL buttons * [EC-547] chore: move nested checkbox component into dialog module * [EC-547] feat: migrate to async actions and remove form promise * [EC-547] feat: add tab layout * [EC-547] fix: dialog vertical overflow We were using `max-height: 100vh` and `margin: 1rem 0` on the same element which meant that our full height was 100vh + 1rem which pushed the dialog outside of the screen. * [EC-547] feat: change user to member in header * [EC-547] feat: add name to header * [EC-547] feat: add ability to specify initial tab * [EC-547] fix: copy pasta in comments * [EC-547] chore: rename user to member dialog * [EC-547] chore: simplify switch statement * Fix strictTemplating warnings/error after merge with master * Refactor GroupService into Core org module (#4112) * Refactor GroupService into Core org module - Move Group service folder into Core org folder - Remove GroupServiceAbstraction - Rename GroupService in components - Remove GroupService from list of Org Module providers (use @Injectable decorator instead) * Import/export SharedModule from SharedOrganizationModule * Move GroupView to core organization folder * Fix file names for org collection views * Cleanup core organization barrel files * [EC-15] Members Grid (#4097) * [EC-623] Introduce shared organization module and search input component * [EC-623] Add search input story * [EC-15] Introduce Members module - Add members module and members routing module - Move members only components into the members module and folder - Remove members only components from LooseComponents module - Update organization routing module to lazy load members module * [EC-15] Enable ToggleGroup component to support generic values Using a generic type for the ToggleGroup allows using both Strings and Enums as values without causing Typescript compiler warning/errors. * [EC-15] Force no bottom margin for Toggle button label * [EC-15] Update Members page header - Use bit-toggle for member status filter - Update bit-toggle Accepted button to say Needs Confirmation - Use bit-search-input - Update search placeholder text - Update invite member button style and text - Import ToggleGroupModule into ShareModule * [EC-15] Update members table - Use the CL bit-table component - Add new table headings - Replace cog options menu with bit-menu component - Add placeholder for groups/collection badges * [EC-15] Specify default generic type for ToggleGroup * [EC-15] Modify getOrganizationUsers() in Api service - Optionally allow the Api service to fetch org user groups and/or collections - Will eventually be moved to an organization user service, but kept here for now * [EC-15] Update member view to fetch groups/collections for users - Use the new Api service functionality - Fetch the organization's list of groups and decrypted collection for rendering their names in the table * [EC-15] Refresh table after editing user groups * [EC-15] Move new members dialog into members module * [EC-15] Show "All" in collections column for users with AccessAll flag * [EC-15] Update copy after talking with design/product * [EC-14] Part II: Add Collection Rows to Vault List (#3875) * [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] start adding header and collection rows * [EC-14] remove param ids if false * [EC-14] Make collection rows navigatable * [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] change name of edit collections method * [EC-14] add collection badges to item rows * [EC-14] show groups badge on collection rows * [EC-14] add bulk actions to header menu button * [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 * [EC-14] resolve merge conflicts * [EC-14] remove checkbox from end user vault collection rows * [EC-14] add owner column to collections in end user vault * [EC-14] add a11y titles for vault filters * 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] Resolve merge conflicts * [EC-14] fix merge conflicts * [EC-14] fix merge conflicts: org service protected and remove absolute path * [EC-14] separate org vault filter service observables * [EC-14] remove folder subject in vault filter service * [EC-14] remove collections subject from vault filter service * [EC-14] change collection api call name - getCollectionsWithDetails to getManyCollectionsWithDetails * [EC-14] add collection functionality - add endpoint to bulk delete collections - add logic to bulk delete both ciphers and collections - refresh ciphers list after making collection changes - stop making api calls from ciphers list each time a filter changes * [EC-14] get collections from vault filter service - for badge, instead of passing through @Input variable * [EC-14] only bulk delete collections if passed * [EC-14] fix deleting ciphers in org vault - reuse same logic from end user vault - call different api endpoints * [EC-14] include collections in MaxCheckedCount * [EC-14] add paging to collections * [EC-14] hide collections if searching * [EC-14] change vault table to new table component - removed a lot of scss classes to use tailwind alternatives - added getters for arrays in component that template can reference - imported and used new bitIconButton for options button * [EC-14] remove cursor pointer when checkbox not available * [EC-14] stop reloading cipher list too early * [EC-14] stop setting cipher component to loaded too early - loaded variable on cipher component hides the loaded indicator - when setting the default filter, we were triggering that variable - instead, we'll just set the active filter and let it grab the filter when ready * [EC-14] check/navigate collection when clicked * [EC-14] rename edit collections callback - used to be onEditCollection - renamed to onEditCipherCollections * [EC-14] remove showOrganizationBadge property - property used to tell template whether it was org vault or end user - replace with check for organization property * [EC-14] replace || with ?? in load function of ciphers * [EC-14] remove nested subscriptions - nested subscriptions = bad - the only dependency any of the subscriptions have is on the organization - use withLatestFrom to verify that the org has been set before firing * [EC-14] add getters and rename method * [EC-14] add null check in bulk delete component - some input variables can be null, so we can't just check the length * [EC-14] add ItemRow type - ItemRow can be either CipherView or CollectionFilter - Consolidated a large portion of selection logic * [EC-14] remove extra applyFilter override - Removed extra applyFIlter, allCiphers has already been filtered by org - Also reordered some of the methods to make more sense * [EC-14] remove extra collections uncheck * [EC-14] transition bulk delete to dialog service * [EC-14] transition bulk restore to dialog service * [EC-14] transition bulk move to dialog service * [EC-14] transition bulk share to dialog service * [EC-14] remove modal references * [EC-14] reload cipher list when changing orgs * [EC-14] add helper method to bulk delete dialog - Gives us built in typing instead of having to redeclare * [EC-14] add helper to open bulk restore dialog - Gives us typing without redeclaring * [EC-14] add open helper to bulk move dialog * [EC-14] add open helper to bulk share dialog - Adds typing to data - also removed the component refs from bulk actions * [EC-14] remove modal service from bulk actions * [EC-14] introduce VaultItemRow to combine cipher and collections * [EC-14] show loading indicator while switching orgs * [EC-14] remove indexing every time filter changes - also reverted back to using setter for changing org * [EC-14] allow searching by function in search pipe - this allows us to search parent properties in objects Co-authored-by: Andreas Coroiu * [EC-14] make collections searchable - used search pipe to filter based on search text * [EC-14] consolidate bulk dialogs in single module * [EC-14] remove form promise from bulk dialogs * [EC-14] stop casting dialog return type - we now have a helper function that gives us typing on result * [EC-14] add length check to array guard * [EC-14] remove extra false assignment * [EC-14] move to sentence case * [EC-14] address pr feedback * [EC-14] add back the default assignment to deleted - we need this default assignment to check for null or undefined values * [EC-14] remove optional chaining - everything is initialized to an empty array so it should never be null * [EC-14] remove manager check to show org vault - this is fixed upstream in a more comprehensive way * [EC-686] add tests and comments to serviceUtils (#4092) * [EC-686] add tests and comments to serviceUtls * [EC-686] whitelist spec filename from linter * [EC-686] fix prettier * [EC-14] use new collection admin service * [EC-14] fix groups searching * [EC-14] use new groups service and models * [EC-14] fix shared module * [EC-14] remove leftover empty vault filter service * [EC-14] remove CollectionGroupDetailsView models * [EC-14] replace GroupDetails with AdminView - Collections in vault filter now use admin view to get access details - Collections shown in cipher list use admin view for access details * [EC-14] add back the dialog to shared module * [EC-14] hide org vault if lacking permissions * [EC-14] add edit collection dialog to vault * [EC-14] add screen reader label to share dialog * [EC-14] moved sync call below subscription - the subscription gives a callback for when we finish a sync - by awaiting the sync before we weren't using the callback to refresh * [EC-14] move cipher params check to switchMap - we want to avoid async subscriptions * [EC-14] clean up subscriptions in org vault - added takeUntil - use combineLatest * [EC-14] clean up vault subscriptions - remove nested subscriptions - use takeUntil * [EC-14] init ciphers component first * [EC-14] fix view vault tab permissions - CanViewAssignedCollections doesn't include CanViewAllCollections - CanViewAssignedCollections does include IsManager * [EC-14] reduce nesting * [EC-14] rename bulk action dialogs selectors * [EC-14] fix permissions for collection management - users with custom admin permissions should be able to edit as well * [EC-14] prettier * [EC-14] use percentages for table columns widths * [EC-14] use GetCollectionAccessDetails in cli - renamed api call Signed-off-by: Jacob Fink Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Thomas Rittson Co-authored-by: Andreas Coroiu * [EC-549] Member details collections tab (#4207) * [EC-784] Introduce OrganizationUserService and abstraction * [EC-784] Move API response models into abstraction folder * [EC-784] Register OrganizationUserService in JsLib * [EC-784] Add OrganizationUserService to CLI Main * [EC-784] Move getOrganizationUser() - Move getOrganizationUser() implementation to OrganizationUserService - Update any references to the API service in the CLI and Web projects * [EC-784] Move getOrganizationUserGroups() * [EC-784] Move and rename getOrganizationUsers() * [EC-784] Move getOrganizationUserResetPasswordDetails() * [EC-784] Move OrganizationUser API request models into abstraction folder * [EC-784] Move postOrganizationUserInvite() * [EC-784] Move postOrganizationUserReinvite() * [EC-784] Move postManyOrganizationUserReinvite() Also tweak the signature to avoid exposing the API request model * [EC-784] Move postOrganizationUserAccept() * [EC-784] Move postOrganizationUserConfirm() * [EC-784] Move postOrganizationUsersPublicKey() Also modify signature to avoid exposing API request model * [EC-784] Move postOrganizationUserBulkConfirm() * [EC-784] Move putOrganizationUser() * [EC-784] Move putOrganizationUserGroups() * [EC-784] Update abstraction method definitions to use abstract keyword * [EC-784] Move putOrganizationUserResetPasswordEnrollment() * [EC-784] Move putOrganizationUserResetPassword() * [EC-784] Move deleteOrganizationUser() * [EC-784] Move deleteManyOrganizationUsers() * [EC-784] Move revokeOrganizationUser() * [EC-784] Move revokeManyOrganizationUsers() * [EC-784] Move restoreOrganizationUser() * [EC-784] Move restoreManyOrganizationUsers() * [EC-784] Move internal OrganizationUserBulkRequest model out of service abstraction * [EC-784] Rename organizationUser folder to organization-user * [EC-549] feat: add unconnected access selector * [EC-549] fix: old user group dialog not working * [EC-549] feat: add support for showing collections * [EC-549] feat: rewrite and implement saving and inviting * [EC-549] feat: implement support for access all collections * [EC-549] feat: remove collection form from role tab * [EC-549] chore: clean up comments * [EC-549] fix: revert changes to access selector story * [EC-549] feat: handle organizations that dont use groups Co-authored-by: Shane Melton * [EC-424] top level vault (#4267) * [EC-424] remove cog menu and header hr * [EC-424] change "Add item" to "New item" * [EC-424] include text for "New item" * [EC-424] add new item dropdown to org vault - add parent collection to dialog params * [EC-14] show Add Item if missing permissions * fix: broken password input toggle tests * [EC-63] Implement breadcrumb component (#3762) * [EC-63] feat: scaffold breadcrumb module * [EC-63] feat: add first very basic structure * [EC-63] feat: dynamically rendered crumbs with styling * [EC-63] feat: implement overflow logic * [EC-63] feat: hide overflow and show ellipsis * [EC-63] feat: fully working with links * [EC-63] feat: add support for only showing last crumb * [EC-63] chore: fix missing template * [EC-63] chore: refactor and add test case * [EC-63] refactor: change parent type to treenode * [EC-63] feat: add breadcrumbs to org vault * [EC-63] feat: add links to breadcrumbs (dont work yet) * [EC-63] feat: add support for click handler in breadcrumbs * [EC-63] feat: working breadcrumb links * [EC-63] feat: add collections group head * [EC-63] feat: add breadcrumbs to personal vault * [EC-63] feat: use icon button * [EC-63] feat: use small icon button * [EC-63] fix: add margin to breadcrumb links The reason for this fix is that the bitIconButton used to open the overflow menu is much taller than the rest of the elements in the list. This causes the whole component to grow and shrink depending on if it contains too many breadcrumbs or not. In the web vault this causes the cipher list to jump up and down while navigating. This increases the height of the entire component so that the icon button no longer affects it. * [EC-63] fix: tests using wrong parent * [EC-63] feat: use ngIf instead of else * [EC-63] refactor: attempt to improve tree node factory readability * [EC-548] Member Details Group Tab (#4273) * [EC-784] Introduce OrganizationUserService and abstraction * [EC-784] Move API response models into abstraction folder * [EC-784] Register OrganizationUserService in JsLib * [EC-784] Add OrganizationUserService to CLI Main * [EC-784] Move getOrganizationUser() - Move getOrganizationUser() implementation to OrganizationUserService - Update any references to the API service in the CLI and Web projects * [EC-784] Move getOrganizationUserGroups() * [EC-784] Move and rename getOrganizationUsers() * [EC-784] Move getOrganizationUserResetPasswordDetails() * [EC-784] Move OrganizationUser API request models into abstraction folder * [EC-784] Move postOrganizationUserInvite() * [EC-784] Move postOrganizationUserReinvite() * [EC-784] Move postManyOrganizationUserReinvite() Also tweak the signature to avoid exposing the API request model * [EC-784] Move postOrganizationUserAccept() * [EC-784] Move postOrganizationUserConfirm() * [EC-784] Move postOrganizationUsersPublicKey() Also modify signature to avoid exposing API request model * [EC-784] Move postOrganizationUserBulkConfirm() * [EC-784] Move putOrganizationUser() * [EC-784] Move putOrganizationUserGroups() * [EC-784] Update abstraction method definitions to use abstract keyword * [EC-784] Move putOrganizationUserResetPasswordEnrollment() * [EC-784] Move putOrganizationUserResetPassword() * [EC-784] Move deleteOrganizationUser() * [EC-784] Move deleteManyOrganizationUsers() * [EC-784] Move revokeOrganizationUser() * [EC-784] Move revokeManyOrganizationUsers() * [EC-784] Move restoreOrganizationUser() * [EC-784] Move restoreManyOrganizationUsers() * [EC-784] Move internal OrganizationUserBulkRequest model out of service abstraction * [EC-784] Rename organizationUser folder to organization-user * [EC-549] feat: add unconnected access selector * [EC-549] fix: old user group dialog not working * [EC-549] feat: add support for showing collections * [EC-549] feat: rewrite and implement saving and inviting * [EC-549] feat: implement support for access all collections * [EC-549] feat: remove collection form from role tab * [EC-549] chore: clean up comments * [EC-549] fix: revert changes to access selector story * [EC-549] feat: handle organizations that dont use groups * [EC-548] Add groups to request models * [EC-548] Add groups to the user admin service and view * [EC-548] Add group access selector * [EC-548] Cleanup data fetching * [EC-548] Update i18n - Add new keys - Update copy - Remove duplicates * [EC-548] Rename collection access items * [EC-548] Move shared fields to parent response class Move the collections and groups fields to the parent OrganizationUserResponse class as it was being duplicated by both children. * [EC-548] Add option to include groups in org user details query * [EC-548] Use groups from user query in member dialog Co-authored-by: Andreas Coroiu * [EC-824] Fix Group table opening two dialogs (#4287) * [EC-824] Stop button click event propagation to prevent opening the modal twice Keeping the (click) event on the table cell allows for users to miss the text and still open the group. * [EC-824] Drop click event handler from button The button still triggers the click event for the parent cell by both click and keyboard interaction so there's no need to prevent event propagation, we can just remove the button event handler. * [EC-550] members role tab (#4297) * [EC-550] rename user type to member role * [EC-550] rename user admin view to org user admin view * [EC-550] add user type to reactive forms * Update ngOnInit to properly handle inviting new members (#4298) * [EC-550] use checkbox component in members dialog * [EC-550] use bitInput for emails and add to form control * [EC-550] set all hint font size to 14px * [EC-550] feat: migrate role radio group * [EC-855] refactor permissions checkboxes - use reactive forms - remove bootstrap Co-authored-by: Andreas Coroiu * [EC-550] hookup new permissions form properties * [EC-550] update [disabled] to [attr.disabled] Co-authored-by: Shane Melton Co-authored-by: Andreas Coroiu Co-authored-by: Andreas Coroiu * [EC-864] fix: inconsistent dialog size (#4303) * chore: Remove collection dialog stories (#4302) * Sort collections by name before building the node tree (#4308) * [EC-828] Access selector layout bugs (#4301) * [EC-828] fix: permission column offset * [EC-828] fix: focus border width * [EC-828] feat: add border on hover Border matches the hover border for the icon button. * [EC-828] fix: properly align permission column Chrome adds extra padding to select elements and the only way to remove it is using `appearence: none`. Unfortunately Firefox does not do this, meaning that we have different behavior when trying to use some of the built in select styles. * [EC-828] feat: re-add select chevron chevron is removed when setting `appearence: none`. We now have the different chevrons on a single screen thought... * [EC-828] fix: chevron looking off-center in chrome * [EC-828] fix: multi-select height Min-height seems like a very hacky solution but I think we need to properly go through these styles when we have more time. Would be nice if we could change the chevron to be the same everywhere for example. * [EC-828] fix: multi-select csp issues * [EC-845] Fix group modal error handling (#4299) * [EC-550] rename user type to member role * [EC-550] rename user admin view to org user admin view * [EC-550] add user type to reactive forms * Update ngOnInit to properly handle inviting new members (#4298) * [EC-550] use checkbox component in members dialog * [EC-845] Remove try/catch from action handlers The [bitAction] directive is responsible for handling any exceptions that arise from the API request. * [EC-845] Add form validators to match server requirements * [EC-550] use bitInput for emails and add to form control * [EC-550] set all hint font size to 14px * [EC-550] feat: migrate role radio group * [EC-845] Remove try/catch for member dialog actions Co-authored-by: Jacob Fink Co-authored-by: Andreas Coroiu * Fix failing vault-filter service tests * [EC-862] Member dialog collections tab fails to save when trying to remove collection access (#4313) * [EC-862] Force clear form array value if disabled * [EC-862] Use form value instead of reading controls directly * fix csp issues * [EC-862] Avoid clearing disabled form array in access selector (#4332) * [EC-862] Do not clear the form array when disabled Clearing the form array breaks the form selection list and is not necessary * [EC-862] Add comments clarifying change * [EC-883] Fix badge list "+n more" message (#4333) * [EC-883] Modify logic to avoid showing a +1 more badge Show the last item in a badge list instead of showing "+1 more". The "+n more" will now only show if there are 2 or more items that exceed the max. * [EC-883] Update max items for people/groups tables * [EC-882] Show "All" when a group has access to all collections (#4334) * [EC-876] Remove old group modal (#4336) * [EC-876] Update click events to use new dialog Use the new dialog service for all member row click events and specify a starting tab * [EC-876] Remove the old user groups modal * [EC-872] Collection dialog success toasts (#4337) * [EC-872] Show success toast when saving a collection * [EC-872] Show success toast when deleting a collection * [EC-870] Add temporary css rule for web app-vault-icon img to restrict height in the bit-table component (#4344) * [EC-897] Update group modal header text * [EC-877] Fix missing collection breadcrumbs (#4339) * [EC-877] Rename ng-template to ng-container * [EC-877] Remove breadcrumb array slice to support showing current collection * [EC-896] Fix bulk group deletion message count (#4350) * [EC-896] Ignore the result from the deleteMany method Instead, use the number of requested groups for the toast as the deleteMany is an all or nothing request * [EC-896] Cleanup deleteMany() in GroupService deleteMany() originally supported a response from the server, but that was scrapped server side and was leftover in the client service * [EC-871] Use bit-badge-list component for collection group column (#4341) * [EC-885] Add ability to exclude cipher types from vault filter (#4340) * [EC-878] Use label for permission dropdown arrow container (#4338) Using the label tag will allow clicking the arrow to activate the dropdown. It also causes the outline to appear on hover. * [EC-906] add bitLink to item names (#4381) - changed from a to button to allow keyboard navigation * [EC 911] Prevent Table from overflowing (#4377) * [EC-911] add word break to table component * [EC-911] let badge column shrink * [EC-911] set badges to be inline-block - prevents them from wrapping in the middle * [EC-911] remove word break style from table component * [EC-911] go back to inline for badge; fix nowrap * [EC-905] Vault row alignment (#4401) * [EC-905] Middle align vault row content * [EC-905] Prevent center text align for vault item names * [EC-828] fix: misaligned selects (#4385) * [EC-907] set name font size to normal (#4410) * fix: collection breadcrumbs not visible in vault (#4434) * [EC-887] Fix Managers can see options to edit/delete Collections they aren't assigned to (#4395) * [EC-887] Introduce 'assigned' property to collection admin view/response The 'assigned' property is set by the server to indicate that the collection has been explicitly assigned to the acting user. Can be used to determine if the collection can be modified/deleted by managers. * [EC-887] Update logic to show/hide collection vault controls Only show checkbox and ellipsis button for collections the user has access to delete and/or edit. Otherwise, hide them to avoid confusion or allowing the user to attempt actions they do not have permission to. * [EC-887] Add missing permission message visibility property * [EC-887] Add missing permission message to template * [EC-887] Check for null id for the 'unassigned' collection * update OAVR feature branch with bit-table changes (#4465) * [EC-939] feat: switch to CL breadcrumbs (#4432) * OAVR Misc Changes (#4496) * hide missing collections placeholder if not at least admin * various ui fixes - consolidate text size and style across pages - right align icon buttons in tables - sentence case multi-select placeholder * [EC-969] "New" button border color (#4498) * [EC-969] remove bootstrap styling from new button * [EC-969] add select row click events to all columns * [EC-969] remove bootstrap from new dropdown * Align icons with images and make all muted color (#4505) Signed-off-by: Jacob Fink Co-authored-by: Shane Melton Co-authored-by: Vincent Salucci Co-authored-by: Jake Fink Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Thomas Rittson Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> Co-authored-by: Daniel James Smith Co-authored-by: Oscar Hinton Co-authored-by: Sammy Chang Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com> Co-authored-by: Gbubemi Smith Co-authored-by: Addison Beck Co-authored-by: Robyn MacCallum Co-authored-by: gbubemismith Co-authored-by: Kyle Spearrin Co-authored-by: Michał Chęciński Co-authored-by: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Co-authored-by: Scott McFarlane <91044021+scottmondo@users.noreply.github.com> Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Co-authored-by: DanHillesheim <79476558+DanHillesheim@users.noreply.github.com> Co-authored-by: Kyle Spearrin Co-authored-by: dgoodman-bw <109169446+dgoodman-bw@users.noreply.github.com> Co-authored-by: Danielle Flinn <43477473+danielleflinn@users.noreply.github.com> Co-authored-by: Matt Gibson Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> Co-authored-by: Joseph Flinn Co-authored-by: Opeyemi <54288773+Eebru-gzy@users.noreply.github.com> Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Co-authored-by: Ash Leece Co-authored-by: Andreas Coroiu --- .github/whitelist-capital-letters.txt | 1 + apps/cli/src/commands/get.command.ts | 2 +- .../vault/vault-filter/vault-filter.module.ts | 8 +- .../src/app/common/base.people.component.ts | 17 +- .../components/nested-checkbox.component.html | 30 -- .../components/nested-checkbox.component.ts | 32 -- .../core/core-organization.module.ts | 4 + apps/web/src/app/organizations/core/index.ts | 3 + .../core/services/collection-admin.service.ts | 126 +++++ .../core/services/group/group.service.ts | 106 ++++ .../services/group/requests}/group.request.ts | 3 +- .../organization-group-bulk.request.ts | 7 + .../group/responses}/group.response.ts | 4 +- .../app/organizations/core/services/index.ts | 3 + .../core/services/user-admin.service.ts | 88 ++++ .../views/collection-access-selection.view.ts | 25 + .../core/views/collection-admin.view.ts | 32 ++ .../organizations/core/views/group.view.ts | 25 + .../src/app/organizations/core/views/index.ts | 5 + .../views/organization-user-admin-view.ts | 19 + .../core/views/organization-user.view.ts | 40 ++ .../organization-layout.component.html | 13 +- .../layouts/organization-layout.component.ts | 10 +- .../manage/collection-add-edit.component.html | 162 ------ .../manage/collection-add-edit.component.ts | 182 ------- .../manage/collections.component.html | 10 + .../manage/collections.component.ts | 40 +- .../manage/group-add-edit.component.html | 261 +++------ .../manage/group-add-edit.component.ts | 336 ++++++++---- .../manage/groups.component.html | 190 ++++--- .../organizations/manage/groups.component.ts | 307 ++++++++--- .../manage/people.component.html | 288 ---------- .../manage/user-add-edit.component.html | 450 ---------------- .../manage/user-add-edit.component.ts | 337 ------------ .../manage/user-groups.component.html | 60 --- .../manage/user-groups.component.ts | 92 ---- .../bulk/bulk-confirm.component.html | 0 .../bulk/bulk-confirm.component.ts | 0 .../bulk/bulk-remove.component.html | 0 .../components}/bulk/bulk-remove.component.ts | 0 .../bulk/bulk-restore-revoke.component.html | 0 .../bulk/bulk-restore-revoke.component.ts | 0 .../bulk/bulk-status.component.html | 0 .../components}/bulk/bulk-status.component.ts | 0 .../members/components/member-dialog/index.ts | 2 + .../member-dialog.component.html | 353 +++++++++++++ .../member-dialog/member-dialog.component.ts | 495 ++++++++++++++++++ .../member-dialog/member-dialog.module.ts | 15 + .../nested-checkbox.component.html | 29 + .../nested-checkbox.component.ts | 62 +++ .../components}/reset-password.component.html | 0 .../components}/reset-password.component.ts | 0 .../src/app/organizations/members/index.ts | 1 + .../members/members-routing.module.ts | 26 + .../organizations/members/members.module.ts | 31 ++ .../members/people.component.html | 321 ++++++++++++ .../{manage => members}/people.component.ts | 183 ++++--- .../organization-routing.module.ts | 82 ++- .../app/organizations/organization.module.ts | 14 +- .../access-selector.component.html | 48 +- .../access-selector.component.spec.ts | 2 +- .../access-selector.component.ts | 6 + .../access-selector/access-selector.models.ts | 24 +- .../access-selector/access-selector.module.ts | 2 +- .../access-selector.stories.ts | 2 +- .../components/access-selector/index.ts | 0 .../access-selector/user-type.pipe.ts | 0 .../collection-dialog.component.html | 94 ++++ .../collection-dialog.component.ts | 293 +++++++++++ .../collection-dialog.module.ts | 13 + .../components/collection-dialog/index.ts | 2 + .../web/src/app/organizations/shared/index.ts | 1 + .../shared/shared-organization.module.ts | 8 +- .../collection-badge.module.ts | 13 + .../collection-name-badge.component.html | 6 + .../collection-name.badge.component.ts | 24 + .../vault/group-badge/group-badge.module.ts | 13 + .../group-name-badge.component.html | 1 + .../group-badge/group-name-badge.component.ts | 27 + .../vault-filter/vault-filter.component.ts | 81 ++- .../vault/vault-filter/vault-filter.module.ts | 8 + .../vault-filter/vault-filter.service.ts | 90 ++++ .../vault/vault-items.component.ts | 275 +++++++++- .../vault/vault-routing.module.ts | 7 +- .../organizations/vault/vault.component.html | 64 ++- .../organizations/vault/vault.component.ts | 238 +++++---- .../app/organizations/vault/vault.module.ts | 22 +- .../src/app/shared/loose-components.module.ts | 62 +-- apps/web/src/app/shared/shared.module.ts | 14 +- .../bulk-delete-dialog.component.html | 25 + .../bulk-delete-dialog.component.ts | 134 +++++ .../bulk-dialogs.module.ts | 25 + .../bulk-move-dialog.component.html | 24 + .../bulk-move-dialog.component.ts | 85 +++ .../bulk-restore-dialog.component.html | 14 + .../bulk-restore-dialog.component.ts | 63 +++ .../bulk-share-dialog.component.html | 73 +++ .../bulk-share-dialog.component.ts} | 65 ++- .../src/app/vault/bulk-actions.component.html | 55 -- .../src/app/vault/bulk-actions.component.ts | 172 ------ .../src/app/vault/bulk-delete.component.html | 39 -- .../src/app/vault/bulk-delete.component.ts | 62 --- .../src/app/vault/bulk-move.component.html | 37 -- apps/web/src/app/vault/bulk-move.component.ts | 40 -- .../src/app/vault/bulk-restore.component.html | 36 -- .../src/app/vault/bulk-restore.component.ts | 29 - .../src/app/vault/bulk-share.component.html | 85 --- .../vault/pipes/get-collection-name.pipe.ts | 13 + .../app/vault/pipes/get-group-name.pipe.ts | 13 + .../pipes/get-organization-name.pipe.ts | 0 apps/web/src/app/vault/pipes/pipes.module.ts | 11 + .../app/vault/shared/pipes/pipes.module.ts | 9 - .../app/vault/shared/vault-shared.module.ts | 20 - .../web/src/app/vault/shared/vault.service.ts | 29 - .../link-sso.component.html | 0 .../link-sso.component.ts | 0 .../organization-options.component.html | 0 .../organization-options.component.ts | 9 +- .../components/vault-filter.component.html | 35 ++ .../components/vault-filter.component.ts | 353 +++++++++++++ .../organization-filter.component.html | 200 ------- .../organization-filter.component.ts | 34 -- .../abstractions/vault-filter.service.ts | 31 ++ .../services/vault-filter.service.spec.ts | 288 ++++++++++ .../services/vault-filter.service.ts | 256 +++++++++ .../collection-filter.component.html | 74 --- .../collection-filter.component.ts | 9 - .../vault-filter-section.component.html | 141 +++++ .../vault-filter-section.component.ts | 137 +++++ .../folder-filter.component.html | 82 --- .../folder-filter/folder-filter.component.ts | 9 - .../models/vault-filter-section.type.ts | 50 ++ .../shared/models/vault-filter.model.spec.ts | 332 ++++++++++++ .../shared/models/vault-filter.model.ts | 127 +++++ .../shared/models/vault-filter.type.ts | 15 + .../status-filter.component.html | 33 -- .../status-filter/status-filter.component.ts | 9 - .../type-filter/type-filter.component.html | 60 --- .../type-filter/type-filter.component.ts | 9 - .../shared/vault-filter-shared.module.ts | 31 +- .../shared/vault-filter.service.ts | 85 --- .../vault-filter/vault-filter.component.html | 79 --- .../vault-filter/vault-filter.component.ts | 35 -- .../vault/vault-filter/vault-filter.module.ts | 14 +- .../src/app/vault/vault-items.component.html | 241 +++++++-- .../src/app/vault/vault-items.component.ts | 368 +++++++++++-- apps/web/src/app/vault/vault.component.html | 41 +- apps/web/src/app/vault/vault.component.ts | 285 +++++----- apps/web/src/app/vault/vault.module.ts | 23 +- apps/web/src/locales/en/messages.json | 206 +++++++- apps/web/src/scss/base.scss | 2 +- apps/web/src/scss/pages.scss | 2 +- apps/web/src/scss/tables.scss | 8 + apps/web/src/scss/vault-filters.scss | 33 +- apps/web/webpack.config.js | 1 + .../manage/bulk/bulk-confirm.component.ts | 6 +- .../manage/bulk/bulk-remove.component.ts | 4 +- .../providers/manage/people.component.html | 4 +- .../app/providers/manage/people.component.ts | 2 +- clients.code-workspace | 2 +- .../deprecated-vault-filter.service.ts | 20 + .../src/components/icon.component.html | 2 +- .../src/components/vault-items.component.ts | 10 +- libs/angular/src/jslib.module.ts | 5 +- libs/angular/src/pipes/search.pipe.ts | 67 ++- libs/angular/src/pipes/user-type.pipe.ts | 29 + .../components/vault-filter.component.ts | 6 +- .../models/dynamic-tree-node.model.ts | 6 +- .../services/vault-filter.service.ts | 5 +- libs/common/src/abstractions/api.service.ts | 22 +- .../organization-user.service.ts | 13 +- .../organization-user-invite.request.ts | 1 + .../organization-user-update.request.ts | 1 + .../responses/organization-user.response.ts | 17 +- .../organization.service.abstraction.ts | 18 +- libs/common/src/misc/serviceUtils.spec.ts | 72 +++ libs/common/src/misc/serviceUtils.ts | 65 ++- libs/common/src/misc/utils.ts | 4 + libs/common/src/models/domain/organization.ts | 4 + libs/common/src/models/domain/tree-node.ts | 11 +- .../request/collection-bulk-delete.request.ts | 9 + .../src/models/request/collection.request.ts | 1 + .../models/response/collection.response.ts | 15 +- .../common/src/models/view/collection.view.ts | 4 +- libs/common/src/services/api.service.ts | 92 +--- .../common/src/services/collection.service.ts | 6 +- ...rganization-user.service.implementation.ts | 30 +- .../async-actions/form-button.directive.ts | 3 +- .../src/badge-list/badge-list.component.html | 9 + .../src/badge-list/badge-list.component.ts | 35 ++ .../src/badge-list/badge-list.module.ts | 13 + .../src/badge-list/badge-list.stories.ts | 53 ++ libs/components/src/badge-list/index.ts | 1 + libs/components/src/badge/badge.directive.ts | 2 +- libs/components/src/badge/badge.stories.ts | 9 +- libs/components/src/badge/index.ts | 2 +- .../src/callout/callout.component.spec.ts | 2 +- .../src/dialog/dialog/dialog.component.html | 2 +- .../src/dialog/dialog/dialog.component.ts | 4 +- .../src/form-field/bit-validators.stories.ts | 56 ++ .../forbidden-characters.validator.spec.ts | 45 ++ .../forbidden-characters.validator.ts | 23 + .../src/form-field/bit-validators/index.ts | 1 + .../src/form-field/error.component.ts | 2 + libs/components/src/form-field/index.ts | 1 + .../form-field/password-input-toggle.spec.ts | 9 +- libs/components/src/index.ts | 2 + libs/components/src/multi-select/index.ts | 1 + .../src/multi-select/scss/bw.theme.scss | 2 +- .../toggle-group/toggle-group.component.ts | 8 +- .../src/toggle-group/toggle.component.ts | 11 +- .../components/src/utils/i18n-mock.service.ts | 8 +- 212 files changed, 7810 insertions(+), 4324 deletions(-) delete mode 100644 apps/web/src/app/components/nested-checkbox.component.html delete mode 100644 apps/web/src/app/components/nested-checkbox.component.ts create mode 100644 apps/web/src/app/organizations/core/core-organization.module.ts create mode 100644 apps/web/src/app/organizations/core/index.ts create mode 100644 apps/web/src/app/organizations/core/services/collection-admin.service.ts create mode 100644 apps/web/src/app/organizations/core/services/group/group.service.ts rename {libs/common/src/models/request => apps/web/src/app/organizations/core/services/group/requests}/group.request.ts (51%) create mode 100644 apps/web/src/app/organizations/core/services/group/requests/organization-group-bulk.request.ts rename {libs/common/src/models/response => apps/web/src/app/organizations/core/services/group/responses}/group.response.ts (81%) create mode 100644 apps/web/src/app/organizations/core/services/index.ts create mode 100644 apps/web/src/app/organizations/core/services/user-admin.service.ts create mode 100644 apps/web/src/app/organizations/core/views/collection-access-selection.view.ts create mode 100644 apps/web/src/app/organizations/core/views/collection-admin.view.ts create mode 100644 apps/web/src/app/organizations/core/views/group.view.ts create mode 100644 apps/web/src/app/organizations/core/views/index.ts create mode 100644 apps/web/src/app/organizations/core/views/organization-user-admin-view.ts create mode 100644 apps/web/src/app/organizations/core/views/organization-user.view.ts delete mode 100644 apps/web/src/app/organizations/manage/collection-add-edit.component.html delete mode 100644 apps/web/src/app/organizations/manage/people.component.html delete mode 100644 apps/web/src/app/organizations/manage/user-groups.component.html delete mode 100644 apps/web/src/app/organizations/manage/user-groups.component.ts rename apps/web/src/app/organizations/{manage => members/components}/bulk/bulk-confirm.component.html (100%) rename apps/web/src/app/organizations/{manage => members/components}/bulk/bulk-confirm.component.ts (100%) rename apps/web/src/app/organizations/{manage => members/components}/bulk/bulk-remove.component.html (100%) rename apps/web/src/app/organizations/{manage => members/components}/bulk/bulk-remove.component.ts (100%) rename apps/web/src/app/organizations/{manage => members/components}/bulk/bulk-restore-revoke.component.html (100%) rename apps/web/src/app/organizations/{manage => members/components}/bulk/bulk-restore-revoke.component.ts (100%) rename apps/web/src/app/organizations/{manage => members/components}/bulk/bulk-status.component.html (100%) rename apps/web/src/app/organizations/{manage => members/components}/bulk/bulk-status.component.ts (100%) create mode 100644 apps/web/src/app/organizations/members/components/member-dialog/index.ts create mode 100644 apps/web/src/app/organizations/members/components/member-dialog/member-dialog.component.html create mode 100644 apps/web/src/app/organizations/members/components/member-dialog/member-dialog.component.ts create mode 100644 apps/web/src/app/organizations/members/components/member-dialog/member-dialog.module.ts create mode 100644 apps/web/src/app/organizations/members/components/member-dialog/nested-checkbox.component.html create mode 100644 apps/web/src/app/organizations/members/components/member-dialog/nested-checkbox.component.ts rename apps/web/src/app/organizations/{manage => members/components}/reset-password.component.html (100%) rename apps/web/src/app/organizations/{manage => members/components}/reset-password.component.ts (100%) create mode 100644 apps/web/src/app/organizations/members/index.ts create mode 100644 apps/web/src/app/organizations/members/members-routing.module.ts create mode 100644 apps/web/src/app/organizations/members/members.module.ts create mode 100644 apps/web/src/app/organizations/members/people.component.html rename apps/web/src/app/organizations/{manage => members}/people.component.ts (75%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.component.html (71%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.component.spec.ts (98%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.component.ts (96%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.models.ts (78%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.module.ts (83%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.stories.ts (99%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/index.ts (100%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/user-type.pipe.ts (100%) create mode 100644 apps/web/src/app/organizations/shared/components/collection-dialog/collection-dialog.component.html create mode 100644 apps/web/src/app/organizations/shared/components/collection-dialog/collection-dialog.component.ts create mode 100644 apps/web/src/app/organizations/shared/components/collection-dialog/collection-dialog.module.ts create mode 100644 apps/web/src/app/organizations/shared/components/collection-dialog/index.ts create mode 100644 apps/web/src/app/organizations/vault/collection-badge/collection-badge.module.ts create mode 100644 apps/web/src/app/organizations/vault/collection-badge/collection-name-badge.component.html create mode 100644 apps/web/src/app/organizations/vault/collection-badge/collection-name.badge.component.ts create mode 100644 apps/web/src/app/organizations/vault/group-badge/group-badge.module.ts create mode 100644 apps/web/src/app/organizations/vault/group-badge/group-name-badge.component.html create mode 100644 apps/web/src/app/organizations/vault/group-badge/group-name-badge.component.ts create mode 100644 apps/web/src/app/organizations/vault/vault-filter/vault-filter.service.ts create mode 100644 apps/web/src/app/vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.html create mode 100644 apps/web/src/app/vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts create mode 100644 apps/web/src/app/vault/bulk-action-dialogs/bulk-dialogs.module.ts create mode 100644 apps/web/src/app/vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.html create mode 100644 apps/web/src/app/vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts create mode 100644 apps/web/src/app/vault/bulk-action-dialogs/bulk-restore-dialog/bulk-restore-dialog.component.html create mode 100644 apps/web/src/app/vault/bulk-action-dialogs/bulk-restore-dialog/bulk-restore-dialog.component.ts create mode 100644 apps/web/src/app/vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.html rename apps/web/src/app/vault/{bulk-share.component.ts => bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts} (69%) delete mode 100644 apps/web/src/app/vault/bulk-actions.component.html delete mode 100644 apps/web/src/app/vault/bulk-actions.component.ts delete mode 100644 apps/web/src/app/vault/bulk-delete.component.html delete mode 100644 apps/web/src/app/vault/bulk-delete.component.ts delete mode 100644 apps/web/src/app/vault/bulk-move.component.html delete mode 100644 apps/web/src/app/vault/bulk-move.component.ts delete mode 100644 apps/web/src/app/vault/bulk-restore.component.html delete mode 100644 apps/web/src/app/vault/bulk-restore.component.ts delete mode 100644 apps/web/src/app/vault/bulk-share.component.html create mode 100644 apps/web/src/app/vault/pipes/get-collection-name.pipe.ts create mode 100644 apps/web/src/app/vault/pipes/get-group-name.pipe.ts rename apps/web/src/app/vault/{shared => }/pipes/get-organization-name.pipe.ts (100%) create mode 100644 apps/web/src/app/vault/pipes/pipes.module.ts delete mode 100644 apps/web/src/app/vault/shared/pipes/pipes.module.ts delete mode 100644 apps/web/src/app/vault/shared/vault-shared.module.ts delete mode 100644 apps/web/src/app/vault/shared/vault.service.ts rename apps/web/src/app/vault/vault-filter/{organization-filter => components}/link-sso.component.html (100%) rename apps/web/src/app/vault/vault-filter/{organization-filter => components}/link-sso.component.ts (100%) rename apps/web/src/app/vault/vault-filter/{organization-filter => components}/organization-options.component.html (100%) rename apps/web/src/app/vault/vault-filter/{organization-filter => components}/organization-options.component.ts (93%) create mode 100644 apps/web/src/app/vault/vault-filter/components/vault-filter.component.html create mode 100644 apps/web/src/app/vault/vault-filter/components/vault-filter.component.ts delete mode 100644 apps/web/src/app/vault/vault-filter/organization-filter/organization-filter.component.ts create mode 100644 apps/web/src/app/vault/vault-filter/services/abstractions/vault-filter.service.ts create mode 100644 apps/web/src/app/vault/vault-filter/services/vault-filter.service.spec.ts create mode 100644 apps/web/src/app/vault/vault-filter/services/vault-filter.service.ts delete mode 100644 apps/web/src/app/vault/vault-filter/shared/collection-filter/collection-filter.component.html delete mode 100644 apps/web/src/app/vault/vault-filter/shared/collection-filter/collection-filter.component.ts create mode 100644 apps/web/src/app/vault/vault-filter/shared/components/vault-filter-section.component.html create mode 100644 apps/web/src/app/vault/vault-filter/shared/components/vault-filter-section.component.ts delete mode 100644 apps/web/src/app/vault/vault-filter/shared/folder-filter/folder-filter.component.html delete mode 100644 apps/web/src/app/vault/vault-filter/shared/folder-filter/folder-filter.component.ts create mode 100644 apps/web/src/app/vault/vault-filter/shared/models/vault-filter-section.type.ts create mode 100644 apps/web/src/app/vault/vault-filter/shared/models/vault-filter.model.spec.ts create mode 100644 apps/web/src/app/vault/vault-filter/shared/models/vault-filter.model.ts create mode 100644 apps/web/src/app/vault/vault-filter/shared/models/vault-filter.type.ts delete mode 100644 apps/web/src/app/vault/vault-filter/shared/status-filter/status-filter.component.html delete mode 100644 apps/web/src/app/vault/vault-filter/shared/status-filter/status-filter.component.ts delete mode 100644 apps/web/src/app/vault/vault-filter/shared/type-filter/type-filter.component.html delete mode 100644 apps/web/src/app/vault/vault-filter/shared/type-filter/type-filter.component.ts delete mode 100644 apps/web/src/app/vault/vault-filter/shared/vault-filter.service.ts delete mode 100644 apps/web/src/app/vault/vault-filter/vault-filter.component.html delete mode 100644 apps/web/src/app/vault/vault-filter/vault-filter.component.ts create mode 100644 libs/angular/src/abstractions/deprecated-vault-filter.service.ts create mode 100644 libs/angular/src/pipes/user-type.pipe.ts create mode 100644 libs/common/src/misc/serviceUtils.spec.ts create mode 100644 libs/common/src/models/request/collection-bulk-delete.request.ts create mode 100644 libs/components/src/badge-list/badge-list.component.html create mode 100644 libs/components/src/badge-list/badge-list.component.ts create mode 100644 libs/components/src/badge-list/badge-list.module.ts create mode 100644 libs/components/src/badge-list/badge-list.stories.ts create mode 100644 libs/components/src/badge-list/index.ts create mode 100644 libs/components/src/form-field/bit-validators.stories.ts create mode 100644 libs/components/src/form-field/bit-validators/forbidden-characters.validator.spec.ts create mode 100644 libs/components/src/form-field/bit-validators/forbidden-characters.validator.ts create mode 100644 libs/components/src/form-field/bit-validators/index.ts diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index a6d2f96079f..2339c1756bd 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -60,6 +60,7 @@ ./libs/common/src/misc/nodeUtils.ts ./libs/common/src/misc/linkedFieldOption.decorator.ts ./libs/common/src/misc/serviceUtils.ts +./libs/common/src/misc/serviceUtils.spec.ts ./libs/common/src/types/twoFactorResponse.ts ./libs/common/src/types/authResponse.ts ./libs/common/src/types/syncEventArgs.ts diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 6cd57fe7cc1..92d7e31258e 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -416,7 +416,7 @@ export class GetCommand extends DownloadCommand { throw new Error("No encryption key for this organization."); } - const response = await this.apiService.getCollectionDetails(options.organizationId, id); + const response = await this.apiService.getCollectionAccessDetails(options.organizationId, id); const decCollection = new CollectionView(response); decCollection.name = await this.cryptoService.decryptToUtf8( new EncString(response.name), diff --git a/apps/desktop/src/app/vault/vault-filter/vault-filter.module.ts b/apps/desktop/src/app/vault/vault-filter/vault-filter.module.ts index 6442a2b7b8b..996bdf807ab 100644 --- a/apps/desktop/src/app/vault/vault-filter/vault-filter.module.ts +++ b/apps/desktop/src/app/vault/vault-filter/vault-filter.module.ts @@ -1,6 +1,7 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; +import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "@bitwarden/angular/abstractions/deprecated-vault-filter.service"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { VaultFilterService } from "@bitwarden/angular/vault/vault-filter/services/vault-filter.service"; @@ -22,6 +23,11 @@ import { VaultFilterComponent } from "./vault-filter.component"; TypeFilterComponent, ], exports: [VaultFilterComponent], - providers: [VaultFilterService], + providers: [ + { + provide: DeprecatedVaultFilterServiceAbstraction, + useClass: VaultFilterService, + }, + ], }) export class VaultFilterModule {} diff --git a/apps/web/src/app/common/base.people.component.ts b/apps/web/src/app/common/base.people.component.ts index a76b5817c8e..7464a41877f 100644 --- a/apps/web/src/app/common/base.people.component.ts +++ b/apps/web/src/app/common/base.people.component.ts @@ -7,7 +7,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/abstractions/organization-user/responses"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; @@ -20,6 +19,7 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ProviderUserUserDetailsResponse } from "@bitwarden/common/models/response/provider/provider-user.response"; +import { OrganizationUserView } from "../organizations/core/views/organization-user.view"; import { UserConfirmComponent } from "../organizations/manage/user-confirm.component"; type StatusType = OrganizationUserStatusType | ProviderUserStatusType; @@ -28,7 +28,7 @@ const MaxCheckedCount = 500; @Directive() export abstract class BasePeopleComponent< - UserType extends ProviderUserUserDetailsResponse | OrganizationUserUserDetailsResponse + UserType extends ProviderUserUserDetailsResponse | OrganizationUserView > { @ViewChild("confirmTemplate", { read: ViewContainerRef, static: true }) confirmModalRef: ViewContainerRef; @@ -110,7 +110,7 @@ export abstract class BasePeopleComponent< ) {} abstract edit(user: UserType): void; - abstract getUsers(): Promise>; + abstract getUsers(): Promise | UserType[]>; abstract deleteUser(id: string): Promise; abstract revokeUser(id: string): Promise; abstract restoreUser(id: string): Promise; @@ -125,9 +125,14 @@ export abstract class BasePeopleComponent< this.statusMap.set(status, []); } - this.allUsers = response.data != null && response.data.length > 0 ? response.data : []; + if (response instanceof ListResponse) { + this.allUsers = response.data != null && response.data.length > 0 ? response.data : []; + } else if (Array.isArray(response)) { + this.allUsers = response; + } + this.allUsers.sort( - Utils.getSortFunction( + Utils.getSortFunction( this.i18nService, "email" ) @@ -176,7 +181,7 @@ export abstract class BasePeopleComponent< this.didScroll = this.pagedUsers.length > this.pageSize; } - checkUser(user: OrganizationUserUserDetailsResponse, select?: boolean) { + checkUser(user: UserType, select?: boolean) { (user as any).checked = select == null ? !(user as any).checked : select; } diff --git a/apps/web/src/app/components/nested-checkbox.component.html b/apps/web/src/app/components/nested-checkbox.component.html deleted file mode 100644 index 9f585e66427..00000000000 --- a/apps/web/src/app/components/nested-checkbox.component.html +++ /dev/null @@ -1,30 +0,0 @@ -
-
- - -
-
-
- - -
-
-
diff --git a/apps/web/src/app/components/nested-checkbox.component.ts b/apps/web/src/app/components/nested-checkbox.component.ts deleted file mode 100644 index eebd01e25d1..00000000000 --- a/apps/web/src/app/components/nested-checkbox.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Component, EventEmitter, Input, Output } from "@angular/core"; - -import { Utils } from "@bitwarden/common/misc/utils"; - -@Component({ - selector: "app-nested-checkbox", - templateUrl: "nested-checkbox.component.html", -}) -export class NestedCheckboxComponent { - @Input() parentId: string; - @Input() checkboxes: { id: string; get: () => boolean; set: (v: boolean) => void }[]; - @Output() onSavedUser = new EventEmitter(); - @Output() onDeletedUser = new EventEmitter(); - - get parentIndeterminate() { - return !this.parentChecked && this.checkboxes.some((c) => c.get()); - } - - get parentChecked() { - return this.checkboxes.every((c) => c.get()); - } - - set parentChecked(value: boolean) { - this.checkboxes.forEach((c) => { - c.set(value); - }); - } - - pascalize(s: string) { - return Utils.camelToPascalCase(s); - } -} diff --git a/apps/web/src/app/organizations/core/core-organization.module.ts b/apps/web/src/app/organizations/core/core-organization.module.ts new file mode 100644 index 00000000000..57362e01d7c --- /dev/null +++ b/apps/web/src/app/organizations/core/core-organization.module.ts @@ -0,0 +1,4 @@ +import { NgModule } from "@angular/core"; + +@NgModule({}) +export class CoreOrganizationModule {} diff --git a/apps/web/src/app/organizations/core/index.ts b/apps/web/src/app/organizations/core/index.ts new file mode 100644 index 00000000000..2f4f7e585bc --- /dev/null +++ b/apps/web/src/app/organizations/core/index.ts @@ -0,0 +1,3 @@ +export * from "./core-organization.module"; +export * from "./services"; +export * from "./views"; diff --git a/apps/web/src/app/organizations/core/services/collection-admin.service.ts b/apps/web/src/app/organizations/core/services/collection-admin.service.ts new file mode 100644 index 00000000000..d89343ffd48 --- /dev/null +++ b/apps/web/src/app/organizations/core/services/collection-admin.service.ts @@ -0,0 +1,126 @@ +import { Injectable } from "@angular/core"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncString } from "@bitwarden/common/models/domain/enc-string"; +import { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; +import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; +import { + CollectionAccessDetailsResponse, + CollectionResponse, +} from "@bitwarden/common/models/response/collection.response"; + +import { CoreOrganizationModule } from "../core-organization.module"; +import { CollectionAdminView } from "../views/collection-admin.view"; + +@Injectable({ providedIn: CoreOrganizationModule }) +export class CollectionAdminService { + constructor(private apiService: ApiService, private cryptoService: CryptoService) {} + + async getAll(organizationId: string): Promise { + const collectionResponse = await this.apiService.getManyCollectionsWithAccessDetails( + organizationId + ); + + if (collectionResponse?.data == null || collectionResponse.data.length === 0) { + return []; + } + + return await this.decryptMany(organizationId, collectionResponse.data); + } + + async get( + organizationId: string, + collectionId: string + ): Promise { + const collectionResponse = await this.apiService.getCollectionAccessDetails( + organizationId, + collectionId + ); + + if (collectionResponse == null) { + return undefined; + } + + const [view] = await this.decryptMany(organizationId, [collectionResponse]); + + return view; + } + + async save(collection: CollectionAdminView): Promise { + const request = await this.encrypt(collection); + + let response: CollectionResponse; + if (collection.id == null) { + response = await this.apiService.postCollection(collection.organizationId, request); + collection.id = response.id; + } else { + response = await this.apiService.putCollection( + collection.organizationId, + collection.id, + request + ); + } + + // TODO: Implement upsert when in PS-1083: Collection Service refactors + // await this.collectionService.upsert(data); + return; + } + + async delete(organizationId: string, collectionId: string): Promise { + await this.apiService.deleteCollection(organizationId, collectionId); + } + + private async decryptMany( + organizationId: string, + collections: CollectionResponse[] | CollectionAccessDetailsResponse[] + ): Promise { + const orgKey = await this.cryptoService.getOrgKey(organizationId); + + const promises = collections.map(async (c) => { + const view = new CollectionAdminView(); + view.id = c.id; + view.name = await this.cryptoService.decryptToUtf8(new EncString(c.name), orgKey); + view.externalId = c.externalId; + view.organizationId = c.organizationId; + + if (isCollectionAccessDetailsResponse(c)) { + view.groups = c.groups; + view.users = c.users; + view.assigned = c.assigned; + } + + return view; + }); + + return await Promise.all(promises); + } + + private async encrypt(model: CollectionAdminView): Promise { + if (model.organizationId == null) { + throw new Error("Collection has no organization id."); + } + const key = await this.cryptoService.getOrgKey(model.organizationId); + if (key == null) { + throw new Error("No key for this collection's organization."); + } + const collection = new CollectionRequest(); + collection.externalId = model.externalId; + collection.name = (await this.cryptoService.encrypt(model.name, key)).encryptedString; + collection.groups = model.groups.map( + (group) => new SelectionReadOnlyRequest(group.id, group.readOnly, group.hidePasswords) + ); + collection.users = model.users.map( + (user) => new SelectionReadOnlyRequest(user.id, user.readOnly, user.hidePasswords) + ); + return collection; + } +} + +function isCollectionAccessDetailsResponse( + response: CollectionResponse | CollectionAccessDetailsResponse +): response is CollectionAccessDetailsResponse { + const anyResponse = response as any; + + return anyResponse?.groups instanceof Array && anyResponse?.users instanceof Array; +} diff --git a/apps/web/src/app/organizations/core/services/group/group.service.ts b/apps/web/src/app/organizations/core/services/group/group.service.ts new file mode 100644 index 00000000000..ba7c56a75a9 --- /dev/null +++ b/apps/web/src/app/organizations/core/services/group/group.service.ts @@ -0,0 +1,106 @@ +import { Injectable } from "@angular/core"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + +import { CoreOrganizationModule } from "../../core-organization.module"; +import { GroupView } from "../../views/group.view"; + +import { GroupRequest } from "./requests/group.request"; +import { OrganizationGroupBulkRequest } from "./requests/organization-group-bulk.request"; +import { GroupDetailsResponse, GroupResponse } from "./responses/group.response"; + +@Injectable({ providedIn: CoreOrganizationModule }) +export class GroupService { + constructor(private apiService: ApiService) {} + + async delete(orgId: string, groupId: string): Promise { + await this.apiService.send( + "DELETE", + "/organizations/" + orgId + "/groups/" + groupId, + null, + true, + false + ); + } + + async deleteMany(orgId: string, groupIds: string[]): Promise { + await this.apiService.send( + "DELETE", + "/organizations/" + orgId + "/groups", + new OrganizationGroupBulkRequest(groupIds), + true, + true + ); + } + + async get(orgId: string, groupId: string): Promise { + const r = await this.apiService.send( + "GET", + "/organizations/" + orgId + "/groups/" + groupId + "/details", + null, + true, + true + ); + + return GroupView.fromResponse(new GroupDetailsResponse(r)); + } + + async getAll(orgId: string): Promise { + const r = await this.apiService.send( + "GET", + "/organizations/" + orgId + "/groups", + null, + true, + true + ); + + const listResponse = new ListResponse(r, GroupDetailsResponse); + + return listResponse.data?.map((gr) => GroupView.fromResponse(gr)) ?? []; + } + + async save(group: GroupView): Promise { + const request = new GroupRequest(); + request.name = group.name; + request.externalId = group.externalId; + request.accessAll = group.accessAll; + request.users = group.members; + request.collections = group.collections.map( + (c) => new SelectionReadOnlyRequest(c.id, c.readOnly, c.hidePasswords) + ); + + if (group.id == undefined) { + return await this.postGroup(group.organizationId, request); + } else { + return await this.putGroup(group.organizationId, group.id, request); + } + } + + private async postGroup(organizationId: string, request: GroupRequest): Promise { + const r = await this.apiService.send( + "POST", + "/organizations/" + organizationId + "/groups", + request, + true, + true + ); + return GroupView.fromResponse(new GroupResponse(r)); + } + + private async putGroup( + organizationId: string, + id: string, + request: GroupRequest + ): Promise { + const r = await this.apiService.send( + "PUT", + "/organizations/" + organizationId + "/groups/" + id, + request, + true, + true + ); + return GroupView.fromResponse(new GroupResponse(r)); + } +} diff --git a/libs/common/src/models/request/group.request.ts b/apps/web/src/app/organizations/core/services/group/requests/group.request.ts similarity index 51% rename from libs/common/src/models/request/group.request.ts rename to apps/web/src/app/organizations/core/services/group/requests/group.request.ts index f3cb6d85e6f..e49c7978d33 100644 --- a/libs/common/src/models/request/group.request.ts +++ b/apps/web/src/app/organizations/core/services/group/requests/group.request.ts @@ -1,8 +1,9 @@ -import { SelectionReadOnlyRequest } from "./selection-read-only.request"; +import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; export class GroupRequest { name: string; accessAll: boolean; externalId: string; collections: SelectionReadOnlyRequest[] = []; + users: string[] = []; } diff --git a/apps/web/src/app/organizations/core/services/group/requests/organization-group-bulk.request.ts b/apps/web/src/app/organizations/core/services/group/requests/organization-group-bulk.request.ts new file mode 100644 index 00000000000..d8f1b5876dc --- /dev/null +++ b/apps/web/src/app/organizations/core/services/group/requests/organization-group-bulk.request.ts @@ -0,0 +1,7 @@ +export class OrganizationGroupBulkRequest { + ids: string[]; + + constructor(ids: string[]) { + this.ids = ids == null ? [] : ids; + } +} diff --git a/libs/common/src/models/response/group.response.ts b/apps/web/src/app/organizations/core/services/group/responses/group.response.ts similarity index 81% rename from libs/common/src/models/response/group.response.ts rename to apps/web/src/app/organizations/core/services/group/responses/group.response.ts index bb87a4e29bb..a825b40fe13 100644 --- a/libs/common/src/models/response/group.response.ts +++ b/apps/web/src/app/organizations/core/services/group/responses/group.response.ts @@ -1,5 +1,5 @@ -import { BaseResponse } from "./base.response"; -import { SelectionReadOnlyResponse } from "./selection-read-only.response"; +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { SelectionReadOnlyResponse } from "@bitwarden/common/models/response/selection-read-only.response"; export class GroupResponse extends BaseResponse { id: string; diff --git a/apps/web/src/app/organizations/core/services/index.ts b/apps/web/src/app/organizations/core/services/index.ts new file mode 100644 index 00000000000..1e670faccd6 --- /dev/null +++ b/apps/web/src/app/organizations/core/services/index.ts @@ -0,0 +1,3 @@ +export * from "./group/group.service"; +export * from "./collection-admin.service"; +export * from "./user-admin.service"; diff --git a/apps/web/src/app/organizations/core/services/user-admin.service.ts b/apps/web/src/app/organizations/core/services/user-admin.service.ts new file mode 100644 index 00000000000..f94298d51dc --- /dev/null +++ b/apps/web/src/app/organizations/core/services/user-admin.service.ts @@ -0,0 +1,88 @@ +import { Injectable } from "@angular/core"; + +import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service"; +import { + OrganizationUserInviteRequest, + OrganizationUserUpdateRequest, +} from "@bitwarden/common/abstractions/organization-user/requests"; +import { OrganizationUserDetailsResponse } from "@bitwarden/common/abstractions/organization-user/responses"; + +import { CoreOrganizationModule } from "../core-organization.module"; +import { OrganizationUserAdminView } from "../views/organization-user-admin-view"; + +@Injectable({ providedIn: CoreOrganizationModule }) +export class UserAdminService { + constructor(private organizationUserService: OrganizationUserService) {} + + async get( + organizationId: string, + organizationUserId: string + ): Promise { + const userResponse = await this.organizationUserService.getOrganizationUser( + organizationId, + organizationUserId, + { + includeGroups: true, + } + ); + + if (userResponse == null) { + return undefined; + } + + const [view] = await this.decryptMany(organizationId, [userResponse]); + + return view; + } + + async save(user: OrganizationUserAdminView): Promise { + const request = new OrganizationUserUpdateRequest(); + request.accessAll = user.accessAll; + request.permissions = user.permissions; + request.type = user.type; + request.collections = user.collections; + request.groups = user.groups; + + await this.organizationUserService.putOrganizationUser(user.organizationId, user.id, request); + } + + async invite(emails: string[], user: OrganizationUserAdminView): Promise { + const request = new OrganizationUserInviteRequest(); + request.emails = emails; + request.accessAll = user.accessAll; + request.permissions = user.permissions; + request.type = user.type; + request.collections = user.collections; + request.groups = user.groups; + + await this.organizationUserService.postOrganizationUserInvite(user.organizationId, request); + } + + private async decryptMany( + organizationId: string, + users: OrganizationUserDetailsResponse[] + ): Promise { + const promises = users.map(async (u) => { + const view = new OrganizationUserAdminView(); + + view.id = u.id; + view.organizationId = organizationId; + view.userId = u.userId; + view.type = u.type; + view.status = u.status; + view.accessAll = u.accessAll; + view.permissions = u.permissions; + view.resetPasswordEnrolled = u.resetPasswordEnrolled; + view.collections = u.collections.map((c) => ({ + id: c.id, + hidePasswords: c.hidePasswords, + readOnly: c.readOnly, + })); + view.groups = u.groups; + + return view; + }); + + return await Promise.all(promises); + } +} diff --git a/apps/web/src/app/organizations/core/views/collection-access-selection.view.ts b/apps/web/src/app/organizations/core/views/collection-access-selection.view.ts new file mode 100644 index 00000000000..38191605fd1 --- /dev/null +++ b/apps/web/src/app/organizations/core/views/collection-access-selection.view.ts @@ -0,0 +1,25 @@ +import { View } from "@bitwarden/common/models/view/view"; + +interface SelectionResponseLike { + id: string; + readOnly: boolean; + hidePasswords: boolean; +} + +export class CollectionAccessSelectionView extends View { + readonly id: string; + readonly readOnly: boolean; + readonly hidePasswords: boolean; + + constructor(response?: SelectionResponseLike) { + super(); + + if (!response) { + return; + } + + this.id = response.id; + this.readOnly = response.readOnly; + this.hidePasswords = response.hidePasswords; + } +} diff --git a/apps/web/src/app/organizations/core/views/collection-admin.view.ts b/apps/web/src/app/organizations/core/views/collection-admin.view.ts new file mode 100644 index 00000000000..dd7be147a3f --- /dev/null +++ b/apps/web/src/app/organizations/core/views/collection-admin.view.ts @@ -0,0 +1,32 @@ +import { CollectionView } from "@bitwarden/common/models/view/collection.view"; +import { CollectionAccessDetailsResponse } from "@bitwarden/common/src/models/response/collection.response"; + +import { CollectionAccessSelectionView } from "./collection-access-selection.view"; + +export class CollectionAdminView extends CollectionView { + groups: CollectionAccessSelectionView[] = []; + users: CollectionAccessSelectionView[] = []; + + /** + * Flag indicating the user has been explicitly assigned to this Collection + */ + assigned: boolean; + + constructor(response?: CollectionAccessDetailsResponse) { + super(response); + + if (!response) { + return; + } + + this.groups = response.groups + ? response.groups.map((g) => new CollectionAccessSelectionView(g)) + : []; + + this.users = response.users + ? response.users.map((g) => new CollectionAccessSelectionView(g)) + : []; + + this.assigned = response.assigned; + } +} diff --git a/apps/web/src/app/organizations/core/views/group.view.ts b/apps/web/src/app/organizations/core/views/group.view.ts new file mode 100644 index 00000000000..f91c5854760 --- /dev/null +++ b/apps/web/src/app/organizations/core/views/group.view.ts @@ -0,0 +1,25 @@ +import { View } from "@bitwarden/common/src/models/view/view"; + +import { GroupDetailsResponse, GroupResponse } from "../services/group/responses/group.response"; + +import { CollectionAccessSelectionView } from "./collection-access-selection.view"; + +export class GroupView implements View { + id: string; + organizationId: string; + name: string; + accessAll: boolean; + externalId: string; + collections: CollectionAccessSelectionView[] = []; + members: string[] = []; + + static fromResponse(response: GroupResponse): GroupView { + const view: GroupView = Object.assign(new GroupView(), response) as GroupView; + + if (response instanceof GroupDetailsResponse && response.collections != undefined) { + view.collections = response.collections.map((c) => new CollectionAccessSelectionView(c)); + } + + return view; + } +} diff --git a/apps/web/src/app/organizations/core/views/index.ts b/apps/web/src/app/organizations/core/views/index.ts new file mode 100644 index 00000000000..e7ba6859901 --- /dev/null +++ b/apps/web/src/app/organizations/core/views/index.ts @@ -0,0 +1,5 @@ +export * from "./collection-access-selection.view"; +export * from "./collection-admin.view"; +export * from "./group.view"; +export * from "./organization-user.view"; +export * from "./organization-user-admin-view"; diff --git a/apps/web/src/app/organizations/core/views/organization-user-admin-view.ts b/apps/web/src/app/organizations/core/views/organization-user-admin-view.ts new file mode 100644 index 00000000000..df5c985f2d1 --- /dev/null +++ b/apps/web/src/app/organizations/core/views/organization-user-admin-view.ts @@ -0,0 +1,19 @@ +import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; +import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; +import { PermissionsApi } from "@bitwarden/common/models/api/permissions.api"; + +import { CollectionAccessSelectionView } from "./collection-access-selection.view"; + +export class OrganizationUserAdminView { + id: string; + userId: string; + organizationId: string; + type: OrganizationUserType; + status: OrganizationUserStatusType; + accessAll: boolean; + permissions: PermissionsApi; + resetPasswordEnrolled: boolean; + + collections: CollectionAccessSelectionView[] = []; + groups: string[] = []; +} diff --git a/apps/web/src/app/organizations/core/views/organization-user.view.ts b/apps/web/src/app/organizations/core/views/organization-user.view.ts new file mode 100644 index 00000000000..6c15c91af20 --- /dev/null +++ b/apps/web/src/app/organizations/core/views/organization-user.view.ts @@ -0,0 +1,40 @@ +import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/abstractions/organization-user/responses"; +import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; +import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; +import { PermissionsApi } from "@bitwarden/common/models/api/permissions.api"; + +import { CollectionAccessSelectionView } from "./collection-access-selection.view"; + +export class OrganizationUserView { + id: string; + userId: string; + type: OrganizationUserType; + status: OrganizationUserStatusType; + accessAll: boolean; + permissions: PermissionsApi; + resetPasswordEnrolled: boolean; + name: string; + email: string; + twoFactorEnabled: boolean; + usesKeyConnector: boolean; + + collections: CollectionAccessSelectionView[] = []; + groups: string[] = []; + + groupNames: string[] = []; + collectionNames: string[] = []; + + static fromResponse(response: OrganizationUserUserDetailsResponse): OrganizationUserView { + const view = Object.assign(new OrganizationUserView(), response) as OrganizationUserView; + + if (response.collections != undefined) { + view.collections = response.collections.map((c) => new CollectionAccessSelectionView(c)); + } + + if (response.groups != undefined) { + view.groups = response.groups; + } + + return view; + } +} 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 8ed9f0e80fc..0d537a4b2a3 100644 --- a/apps/web/src/app/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/organizations/layouts/organization-layout.component.html @@ -7,10 +7,15 @@ [activeOrganization]="organization" > - {{ "vault" | i18n }} - - {{ "manage" | i18n }} - + {{ + "vault" | i18n + }} + {{ + "members" | i18n + }} + {{ + "groups" | i18n + }} {{ getReportTabLabel(organization) | 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 d8f2fdd8a33..69a04b68a59 100644 --- a/apps/web/src/app/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/organizations/layouts/organization-layout.component.ts @@ -5,10 +5,10 @@ import { map, mergeMap, Observable, Subject, takeUntil } from "rxjs"; import { canAccessBillingTab, canAccessGroupsTab, - canAccessManageTab, canAccessMembersTab, canAccessReportingTab, canAccessSettingsTab, + canAccessVaultTab, getOrganizationById, OrganizationService, } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; @@ -45,12 +45,12 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { this._destroy.complete(); } - canShowSettingsTab(organization: Organization): boolean { - return canAccessSettingsTab(organization); + canShowVaultTab(organization: Organization): boolean { + return canAccessVaultTab(organization); } - canShowManageTab(organization: Organization): boolean { - return canAccessManageTab(organization); + canShowSettingsTab(organization: Organization): boolean { + return canAccessSettingsTab(organization); } canShowMembersTab(organization: Organization): boolean { diff --git a/apps/web/src/app/organizations/manage/collection-add-edit.component.html b/apps/web/src/app/organizations/manage/collection-add-edit.component.html deleted file mode 100644 index 41368d589a7..00000000000 --- a/apps/web/src/app/organizations/manage/collection-add-edit.component.html +++ /dev/null @@ -1,162 +0,0 @@ - diff --git a/apps/web/src/app/organizations/manage/collection-add-edit.component.ts b/apps/web/src/app/organizations/manage/collection-add-edit.component.ts index 1a26d21fba2..e69de29bb2d 100644 --- a/apps/web/src/app/organizations/manage/collection-add-edit.component.ts +++ b/apps/web/src/app/organizations/manage/collection-add-edit.component.ts @@ -1,182 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; -import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; -import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -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 { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; -import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; -import { GroupResponse } from "@bitwarden/common/models/response/group.response"; - -@Component({ - selector: "app-collection-add-edit", - templateUrl: "collection-add-edit.component.html", -}) -export class CollectionAddEditComponent implements OnInit { - @Input() collectionId: string; - @Input() organizationId: string; - @Input() canSave: boolean; - @Input() canDelete: boolean; - @Output() onSavedCollection = new EventEmitter(); - @Output() onDeletedCollection = new EventEmitter(); - - loading = true; - editMode = false; - accessGroups = false; - title: string; - name: string; - externalId: string; - groups: GroupResponse[] = []; - formPromise: Promise; - deletePromise: Promise; - - private orgKey: SymmetricCryptoKey; - - constructor( - private apiService: ApiService, - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private cryptoService: CryptoService, - private logService: LogService, - private organizationService: OrganizationService - ) {} - - async ngOnInit() { - const organization = await this.organizationService.get(this.organizationId); - this.accessGroups = organization.useGroups; - this.editMode = this.loading = this.collectionId != null; - if (this.accessGroups) { - const groupsResponse = await this.apiService.getGroups(this.organizationId); - this.groups = groupsResponse.data - .map((r) => r) - .sort(Utils.getSortFunction(this.i18nService, "name")); - } - this.orgKey = await this.cryptoService.getOrgKey(this.organizationId); - - if (this.editMode) { - this.editMode = true; - this.title = this.i18nService.t("editCollection"); - try { - const collection = await this.apiService.getCollectionDetails( - this.organizationId, - this.collectionId - ); - this.name = await this.cryptoService.decryptToUtf8( - new EncString(collection.name), - this.orgKey - ); - this.externalId = collection.externalId; - if (collection.groups != null && this.groups.length > 0) { - collection.groups.forEach((s) => { - const group = this.groups.filter((g) => !g.accessAll && g.id === s.id); - if (group != null && group.length > 0) { - (group[0] as any).checked = true; - (group[0] as any).readOnly = s.readOnly; - (group[0] as any).hidePasswords = s.hidePasswords; - } - }); - } - } catch (e) { - this.logService.error(e); - } - } else { - this.title = this.i18nService.t("addCollection"); - } - - this.groups.forEach((g) => { - if (g.accessAll) { - (g as any).checked = true; - } - }); - - this.loading = false; - } - - check(g: GroupResponse, select?: boolean) { - if (g.accessAll) { - return; - } - (g as any).checked = select == null ? !(g as any).checked : select; - if (!(g as any).checked) { - (g as any).readOnly = false; - (g as any).hidePasswords = false; - } - } - - selectAll(select: boolean) { - this.groups.forEach((g) => this.check(g, select)); - } - - async submit() { - if (this.orgKey == null) { - throw new Error("No encryption key for this organization."); - } - - const request = new CollectionRequest(); - request.name = (await this.cryptoService.encrypt(this.name, this.orgKey)).encryptedString; - request.externalId = this.externalId; - request.groups = this.groups - .filter((g) => (g as any).checked && !g.accessAll) - .map( - (g) => new SelectionReadOnlyRequest(g.id, !!(g as any).readOnly, !!(g as any).hidePasswords) - ); - - try { - if (this.editMode) { - this.formPromise = this.apiService.putCollection( - this.organizationId, - this.collectionId, - request - ); - } else { - this.formPromise = this.apiService.postCollection(this.organizationId, request); - } - await this.formPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.editMode ? "editedCollectionId" : "createdCollectionId", this.name) - ); - this.onSavedCollection.emit(); - } catch (e) { - this.logService.error(e); - } - } - - async delete() { - if (!this.editMode) { - return; - } - - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t("deleteCollectionConfirmation"), - this.name, - this.i18nService.t("yes"), - this.i18nService.t("no"), - "warning", - false, - "app-collection-add-edit .modal-content" - ); - if (!confirmed) { - return false; - } - - try { - this.deletePromise = this.apiService.deleteCollection(this.organizationId, this.collectionId); - await this.deletePromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("deletedCollectionId", this.name) - ); - this.onDeletedCollection.emit(); - } catch (e) { - this.logService.error(e); - } - } -} diff --git a/apps/web/src/app/organizations/manage/collections.component.html b/apps/web/src/app/organizations/manage/collections.component.html index 976a63fe6d4..69fc9e9896e 100644 --- a/apps/web/src/app/organizations/manage/collections.component.html +++ b/apps/web/src/app/organizations/manage/collections.component.html @@ -65,6 +65,16 @@